天天看點

編寫C#調用的C++DLL

最近一段時間,經常遇到這些問題,前一陣子研究了一下,沒有記下來,沒想到最近研究又有些不記得了,今天把它寫下來以備忘。

一般我們提供給其他語言調用的DLL,都是用C或者C++編寫,然後封裝。我這邊也是采用的C++。

首先有幾個注意點:

1、如果功能很簡單,或者不使用第三方庫(如MFC自帶的庫),建立一個win32的控制台程式就可以了,然後把項目生成改為DLL。值得一提的是,代碼生成裡面

          運作時庫分四種:

                    (1)多線程MTD(靜态庫,編譯之後,你的lib帶有調試功能)——> debug時用

                    (2)多線程MT(靜态庫,沒有調試功能)                           ——> release時用

                    (3)多線程DLL MTD(動态庫,帶有調試功能)                  ——> debug時用

                    (4)多線程DLL MT(動态庫,沒有有調試功能)。              ——> release時用s

          既然封裝DLL,那調試的時候用(3),釋出的時候用(4)。

2、設定為導出函數,并采用C風格。函數前加extern "C" __declspec(dllexport)。定義函數在退出前自己清空堆棧,在函數前加__stdcall。

      如extern "C" __declspec(dllexport) int __stdcall add(int x,int y);

      具備上述條件時,生成的DLL就含有導出函數的功能了,不過此時DLL中的函數名稱不是規則的,使用編譯器自定義的,可能是這樣一個名字[email protected],具體的可以用VS的Depends工具檢視一下。

3、把導出函數名稱變為标準名稱,需加子產品定義檔案,就是.def檔案。

内容如下:(需要注釋,前面加分号就可以了,注釋需要單獨行)

LIBRARY "TEST"

     EXPORTS

                ;add函數

                adds

LIBRARY 庫名稱

EXPORTS 需要導出的各個函數名稱

     重新編譯之後,再用Depends工具看一下,函數已經變成标準add,而不是[email protected]。這個在動态加載時很有用,特别是在GetProcAddress函數尋找入庫函數的時候。

4、C#調用C++ DLL,介紹兩種方法

     (1)靜态加載

             [DllImport("TEST.dll", EntryPoint = "add")]

             public int add(int x,int y);//與dll中一緻 

             注意如果需要傳回字元串可以這樣

             C++中

             int getString(const char* source,char* dest);

             C#中

             int getString(string source,StringBuilder sbr);

             切記調用的時候給StringBuilder 配置設定空間,否則會報錯。

             如dest 長度為10,可以這樣。

             StringBuilder sbr=new StringBuilder(10);

             getString("hello",sbr);

             如果你希望C++的dll還能被VB等語言調用,建議将字串寫成com的形式

             如

             C++中

             int getString(BSTR source,BSTR dest);//BSTR就是一個com形式的字元數組,相當于字元串

             C#中

             int getString(string source,StringBuilder sbr);

             VB中

             Declare Function getString Lib "TEST.dll" (ByVal source As String, ByVal dest As String) As Integer;         

     (2)動态加載

             [DllImport("kernel32.dll")]

             private extern static IntPtr LoadLibrary(String path);//path 就是dll路徑 傳回結果為0表示失敗。

             [DllImport("kernel32.dll")]

             private extern static IntPtr GetProcAddress(IntPtr lib, String funcName);//lib是LoadLibrary傳回的句柄,funcName 是函數名稱 傳回結果為0辨別失敗。

             [DllImport("kernel32.dll")]

             private extern static bool FreeLibrary(IntPtr lib);

             //聲明委托

             delegate int ADD(int x,int y);

             //使用動态加載

             IntPtr hLib = LoadLibrary(dllPath);//加載函數

             IntPtr apiFunction = GetProcAddress(hLib, apiName);//擷取函數位址

             int i = Marshal.GetLastWin32Error();

             if (apiFunction.ToInt32() == 0)//0表示函數沒找到

                 return null;

             //擷取函數接口,相當于函數指針

             ADD add = (Delegate)Marshal.GetDelegateForFunctionPointer(apiFunction, typeof(ADD)) as ADD;          

             //調用函數

             add(1,2);

             //釋放句柄

             FreeLibrary(hLib );

    最後,

            1)C++在傳回字元串時,切記最後添加/0,不然在C#等中調用,會顯示部分亂碼。    

            2)C++動态申請的記憶體,需在出函數之前就必須釋放,否則會報意想不到的錯誤。比如記憶體寫入錯誤等等。