1. 程式人生 > >將標準C++動態連結庫封裝到.NET程式集dll全攻略

將標準C++動態連結庫封裝到.NET程式集dll全攻略

/* ********cppDll.h********* */
#ifdef CPPDLL_EXPORTS
#define  CPPDLL_API __declspec(dllexport)
#else
#define  CPPDLL_API __declspec(dllimport)
#endif


class  CPPDLL_API CcppDll  {
public :
 CcppDll(
void );
 
//  TODO: 在此新增您的方法。
void  ChangeValue( int  i);
    
int  GetValue();
private :
    
int  var;
    CcppDll 
*  CreateCcppDll();
}
;

extern  CPPDLL_API  int  ncppDll;

CPPDLL_API 
int  fncppDll( void );
首先,在標準C++中使用標準C++dll的通常做法是預編譯時匯入lib檔案,於是有人希望能夠開一個managed C++ dll,用這種方法匯入標準C++ dll,然後再在 Winform等其他.NET程式中呼叫,實際上這是不可能的,因為.NET程式在呼叫這個庫時根本找不到入口.下面我一2個例子詳細說明,其中標準C++庫(cppDll)一個,C#dll(CSDll)一個,以及一個測試的C#WinForm(CSFormTest)一個
   實際上 標準C++生成的dll檔案本身是可以查詢匯出函式入口的,這一點比較麻煩,如果是C函式,還強一點,因為可以在前面宣告extern "C" 這樣 Dll裡面的函式名就是入口,只要標頭檔案就可以知道入口了.例如這樣

extern"C" __declspec(dllexport) int fncppDll(void)
{
    
return42;
}

但是在類中的成員函式就不能宣告為extern "C"了,因此我們必須查詢他的入口,舉個簡單例子 


/***********cppDll.cpp********/


#include 
"stdafx.h"
#include 
"cppDll.h"


#ifdef _MANAGED
#pragma managed(push, off)
#endif

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
      )
{
 
switch (ul_reason_for_call)
 
{
 
case DLL_PROCESS_ATTACH:
 
case DLL_THREAD_ATTACH:
 
case DLL_THREAD_DETACH:
 
case DLL_PROCESS_DETACH:
  
break;
 }

    
return TRUE;
}


#ifdef _MANAGED
#pragma managed(pop)
#endif

/***************cppDll.cpp*******/

// 這是匯出變數的一個示例
CPPDLL_API int ncppDll=0;

CPPDLL_API 
struct VS *vs;
// 這是匯出函式的一個示例。
CPPDLL_API int fncppDll(void)
{
 
return42;
}

// 這是已匯出類的建構函式。
// 有關類定義的資訊,請參閱 cppDll.h
CcppDll::CcppDll()
{
   
return;
}


void CcppDll::ChangeValue(int i)
{
    var
=i;
}

int CcppDll::GetValue()
{
    
return var;
}

CcppDll 
* CcppDll::CreateCcppDll()
{
     CcppDll 
* cs=new CcppDll();
     
return cs;
}

/***************************/

前頭的外部函式和變數您不用看了,相信您只要會用DllImport的基本語法就明白怎麼導,這裡主要看看3個CcppDll的成員函式怎麼封裝.注意這個CcppDll::CreateCcppDll()這裡相當於顯式呼叫建構函式,這個函式是我們封裝到.NET的關鍵,必須有,至於為什麼後文再說.我寫了幾個讀寫變數的函式以便您新建幾個物件測試.
    然後簡要介紹一下DllImport在C#中的語法

2007年1月10日

DllImport的第一個引數是庫完整檔名,第二個就是函式入口了,正如剛才所說C++類成員函式不能被宣告為extern "C" 因此入口不是dll中定義的函式名,而是如下

6    5 000111D6 [email protected]@@[email protected] = @ILT+465([email protected]@@[email protected])
而匯入以後的函式定義部分的函式名可以任意指定.

從問號開始等號以前是剛才哪個CCppDll::ChangeValue的真實入口,其中@後是類明,@@後是編譯器產生的.
檢視這些入口可以用VS的dumpbin命令,您可以選擇標準輸出或者輸出到檔案,輸出到檔案的好處我最後在討論.

注意:使用

dumpbin -exports 檔案完整路徑

不要用/exports否則看不到入口





下面是C#的dll程式碼

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace CSDLL
{
    
publicclass Class1
    
{
        [DllImport(
@"D:/dotNetApps/Client Applications/cppDll/debug/cppDll.dll", EntryPoint ="[email protected]@@[email protected]", CallingConvention = CallingConvention.ThisCall)]
        
privateexternstaticvoid ChangeValue(IntPtr pThis,int i);
        [DllImport(
@"D:/dotNetApps/Client Applications/cppDll/debug/cppDll.dll", EntryPoint ="[email protected]@@QAEHXZ", CallingConvention = CallingConvention.ThisCall)]
        
privateexternstaticint GetValue(IntPtr pThis);
        [DllImport(
@"D:/dotNetApps/Client Applications/cppDll/debug/cppDll.dll", EntryPoint ="[email protected]@@[email protected]", CallingConvention = CallingConvention.Winapi)]
        
privateexternstatic IntPtr CreateCppDll();

        
public Class1()
        
{
            p 
= CreateCppDll();
        }

        
publicint Value
        
{
            
getreturn GetValue(p); }
            
set{ ChangeValue(p, (int)value); }
        }

        
public IntPtr p;
    }

}




其中using System.Runtime.InteropServices;使您能夠使用DllImport
下面我們來分析一下這段程式碼.在匯入函式部分,三個外部函式均聲明瞭庫的絕對路徑,和入口全稱,還有就是呼叫方式.您可能認為就算函式可以匯入,全部宣告為static extern 還如何以面向物件方式呼叫? 難道只能當作靜態函式,全域性只用一個物件,顯然不能實現原有的C++庫的功能.解決這個問題關鍵就在呼叫方式.
CallingConvention列舉有4個值,我們主要使用2種,一種是預設的CallingConvention.StdCall(windows下與Winapi等效),相當於靜態呼叫,另一種則是CallingConvention.ThisCall其中  CreateCppDll相當於建構函式,是為物件開闢記憶體空間的,因此在呼叫它以前物件還沒有被分配,所以它必須使用這種呼叫方式,

[DllImport(@"D:/dotNetApps/Client Applications/cppDll/debug/cppDll.dll", EntryPoint ="[email protected]@@[email protected]", CallingConvention = CallingConvention.Winapi)]
        
privateexternstatic IntPtr CreateCppDll();

在封裝這個標準C++類的C#類中,建構函式就應呼叫它以得到一個指向原C++物件的指標

public Class1()
{
      p 
= CreateCppDll();
}

public IntPtr p;

而Class1的成員變數p就是用來儲存這個物件指標的,注意由於安全程式碼C#物件中禁止使用指標,這裡無論C++是何種型別 C#一律使用平臺指標IntPtr,定義外部函式也是如此.
然後其他所有的成員非靜態函式則使用 CallingConvention.ThisCall進行匯入,如下
 [DllImport(@"D:/dotNetApps/Client Applications/cppDll/debug/cppDll.dll", EntryPoint = "[email protected]@@[email protected]", CallingConvention = CallingConvention.ThisCall)]
        private  extern static void ChangeValue(IntPtr pThis,int i);
        [DllImport(@"D:/dotNetApps/Client Applications/cppDll/debug/cppDll.dll", EntryPoint = "[email protected]@@QAEHXZ", CallingConvention = CallingConvention.ThisCall)]
        private  extern static int GetValue(IntPtr pThis);
注意到剛才我們定義標準C++庫時ChangeValue有一個引數 GetValue沒有引數,而這次宣告的時候ChangeValue有兩個引數,GetValue有一個引數.這個多處來的引數就是真實的物件指標,因此,在Class1中封裝這些函式時,每次呼叫都把p傳給這些函式(注意,前提是p已經分配)如下(這裡為了符合C#風格,乾脆封到屬性裡面去了,反正性質是一樣的):
       

publicint Value
        
{
            
getreturn GetValue(p); }
            
set{ ChangeValue(p, (int)value); }
        }

這樣就完全做到了形式上用C函式呼叫,實際上是物件在呼叫自己的成員函式,由於然後經過這一道封裝後,在呼叫這個庫的應用程式中所看到的仍然是面向物件的類方式.這樣就完全達到了我們預期的目的.
下面是一個C#WinForm呼叫這個dll的示例:
//....................預設包含的庫我省了

using CSDLL;

namespace CSFormTest
{
    
public partial class Form1 : Form
    
{
        
public Form1()
        
{
            InitializeComponent();
        }


        
privatevoid button1_Click(object sender, EventArgs e)
        
{
            Class1 c1 
=new Class1();
            Class1 c2 
=new Class1();
            c1.Value 
=3;
            c2.Value 
=4;
            MessageBox.Show(
"c1="+ c1.Value +",c2="+ c2.Value);
        }

    }

}

執行結果:物件c1,c2的屬性Value都得到改變,證明他們是2個不同物件呼叫各自成員函式的結果,
                     由此證明我們的思路完全正確

 

相關推薦

標準C++動態連結封裝到.NET程式dll

/**/ /* ********cppDll.h********* */ #ifdef CPPDLL_EXPORTS #define  CPPDLL_API __declspec(dllexport) #else #define  CPPDLL_API __declspec(

python中呼叫 C#動態連結問題記錄

程式[摘自https://blog.csdn.net/LTG01/article/details/80700513]  import clr clr.FindAssembly("PythonNetTest.dll") ## 載入c#dll檔案 from PythonNetTest import *

python3使用ctypes在windows中訪問CC++動態連結函式示例

python3使用ctypes在windows中訪問C和C++動態連結庫函式示例 這是我們的第一個示例,我們儘量簡單,不傳參,不返回,不訪問其他的動態連結庫 一 測試環境介紹和準備 測試環境: 作業系統:windows10 Python版本:3.7.0 VS版本:vs2015社群版(免費) 相關

C++動態連結的製作

輸入函式__declspec(dllimport) 與輸出函式__declspec(dllexport) 有什麼區別呢?我知道他們不同,但差別在哪呢?我用的全是__declspec(dllexport) , __declspec(dllimport)一般在

tensorflow之編譯使用c/c++動態連結

tensorflow主流介面雖然是python,但是其也支援C語言的介面供大家呼叫,並且對於影象處理等方面c++是一種更為合適的語音。因此本文主要介紹一下如何安裝並使用c版本的tensorflow。 一、 編譯。  編譯的目的主要是為了產生對應的標頭檔案和動態連結so檔案。編譯

JAVA通過JNI呼叫C++動態連結CLL(一)

簡介 本文筆者,詳細的演示JAVA通過JNI呼叫C++動態連結庫CLL的開發過程和涉及到知識點介紹,入門級簡單易懂 (一)Java本機介面(Java Native Interfa

Unity呼叫c++動態連結注意事項

Unity通過ndk呼叫java,java通過jni呼叫c++ .so 1:Jni的.so是獨立的,還是依賴其他.so庫或靜態庫? 答:都可以,但具體的.so拷貝到unity中後,是否還能正常呼叫,有待測試。 2:C++中jni與java回撥函式,這裡只舉例靜態函式呼叫

C#呼叫C/C++動態連結(.dll)詳解

第一篇編譯C的動態連線庫 在實際工作中,我們經常會將C語言中的.lib和.h檔案(靜態庫)編譯成動態連線庫.dll檔案(這裡只提供這兩種檔案,沒有完整的工程),以提供給其他語言平臺呼叫。 1,必須有.lib檔案,只有.h檔案是無法編譯動態連線庫的。 2,我使用的是V

python3.2下呼叫C動態連結

python和C,我覺得這簡直是無敵的組合啊。一般性的業務邏輯用python快速出模型,而碰到python執行緩慢的操作,則可以通過呼叫C編譯好的連結庫來完成。在python3.2下,可以通過ctype模組單純的訪問C連結庫,也可以通過傳統方式訪問。ctype模組固然方便,但

淺談JAVA呼叫C++動態連結

如:      public native 返回型別 方法名(引數列表); 注意,這些方法沒有方法體。。 程式中呼叫這裡的方法和呼叫平常的方法的方式是一樣的。 --------------------------------------------------------------------------

electron 使用 node-ffi 呼叫 C++ 動態連結DLL

一、為什麼需要使用DLL 需要使用系統 API 操作或擴充套件應用程式; 需要呼叫第三方的介面API,特別是與硬體裝置進行通訊,而這些介面 API 基本上都是通過 C++ 動態連結庫(DLL)實現的; 需要呼叫C++實現的一些複雜演算法等。 二、node-ffi 是什麼 n

Ubuntu X86編譯tensorflow C++動態連結

  以下方法在x86上親測通過,在Nvidia TX 系列第三步會出錯。但是會生成動態連結庫,有興趣的可以試試能不能用,我測試是可以用的。 環境(16.04LTS cuda8.0 cudnn6.0.10 tf1.3 python2 ) 1.安裝依賴項

C++ 動態連結和靜態連結

typedef int (*DllFunc)(int, int); int _tmain(int argc, _TCHAR* argv[]) { DllFunc dllFunc; HINSTANCE hInstLib = LoadLibrary(L"DllTest.dll"); if (hInstLi

java 呼叫c++動態連結

        JNI其實是Java Native Interface的簡稱,也就是java本地介面。它提供了若干的API實現了和Java和其他語言的通訊(主要是C&C++)。也許不少人覺得Java已經足夠強大,為什麼要需要JNI這種東西呢?我們知道Java是一種

Linux環境下使用eclipse開發C++動態連結程式

Linux中也有類似windows中DLL的變成方法,只不過名稱不同而已。在Linux中,動態連結叫做Standard Object,生成的動態連結檔案為*.so。詳細請參考相關文件。 開發環境:Eclipse 3.4.2 G++:4.3.2 1. 建立動態連結庫

lua——alien實現lua呼叫C動態連結dll、so)

我們知道,lua通過lua_State堆疊可以很方便的與C語言進行互動 也可以呼叫專門為lua呼叫而封裝的C庫。 具體步驟: 1.原C檔案中引入lua相關標頭檔案 #include "lua.h" #include "lualib.h" #include "lauxli

C#總結(七)動態載入C++動態連結

  C#呼叫C++ 連結庫的方式分為靜態呼叫和動態呼叫這兩種方式。靜態呼叫之前的文章裡面都有介紹,使用.net 提供的DllImport 匯入相關的C++ 庫即可。請看之前的文章,https://www.cnblogs.com/zhangweizhong/p/8119340.html 。 今天介紹動

Native C++藉助CLR動態載入並呼叫.NET程式

Native C++程式碼和託管.NET程式碼互操作並不是什麼難事, 資料也很多, 但是有些方法複雜繁瑣, 本文介紹了一種簡單的可行、支援動態載入的基於CLR的互動方法. 1.首先是動態載入目標程式集和類: try { auto assembly = Ass

訓練好的caffe模型封裝動態連結提供C++API

<<2018.12.11 照例先po出兩篇部落格 https://blog.csdn.net/maweifei/article/details/72811413 https://blog.csdn.net/jiongnima/article/details/70199480

ApolloStudio高手之路(8):用Python呼叫.NetC#、VB.Net等)開發的動態連結DLL檔案)實現相互協作

ApolloStudio是基於.Net與Python雙架構下的實現,這樣的架構體系使得其具有傳統定製軟體無法比擬的超強拓展性,在本文中我們將介紹這兩者是如何在ApolloStudio平臺上實現優勢互補的。由於在ApolloStudio中使用的更易學習的Python作為主導指令碼語言,這裡我們將介