最近一段時間,經常遇到這些問題,前一陣子研究了一下,沒有記下來,沒想到最近研究又有些不記得了,今天把它寫下來以備忘。
一般我們提供給其他語言調用的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++動态申請的記憶體,需在出函數之前就必須釋放,否則會報意想不到的錯誤。比如記憶體寫入錯誤等等。