1. 程式人生 > >VS下動態庫dll的顯式呼叫(動態呼叫)

VS下動態庫dll的顯式呼叫(動態呼叫)

VS下動態庫dll的顯式呼叫

動態庫的載入分兩種形式:分為靜態載入和動態載入。靜態載入時,對應的標頭檔案、DLL,和LIB缺一不可,並且生產的EXE沒有找到DLL檔案就會導致“應用程式初始化失敗”。動態載入只需要dll,通過LoadLibrary()函式進行載入,但該方式對生成的dll的規範有一定的要求否則容易出錯。

Dll的動態呼叫常規程式碼如下:

//#include <Windows.h>
#include <afxwin.h>
#include <string>
#include <vector>
//#define  _AFxDLL

typedef int (*DLLfun)(int, int);

int main(){
	DLLfun fun1;
	HINSTANCE hdll;
	hdll=LoadLibrary("Image Enhance.dll"); //載入dll
	printf("hdll:%p\n",hdll);//列印dll地址
	if(hdll==NULL){
		FreeLibrary(hdll);//釋放dll
	}
	fun1 = (DLLfun)GetProcAddress(hdll, "sharpen");//獲取函式地址
	printf("fun1:%p\n",fun1);
	if(fun1==NULL){
		FreeLibrary(hdll);
	}
	int r;
	r = fun1(4,5);//函式地址
	return 0;
}

從上面的程式可以看出,dll動態呼叫主要用到三個函式LoadLibrary,GetProcAddress以及FreeLibrary。

1. LoadLibrary是用來載入dll的,格式為

HINSTANCE hdll;
	hdll=LoadLibrary("Image Enhance.dll");

呼叫成功則返回函式地址,否則返回0或NULL。

(1)當其調用出錯時,用GetLastError()得到返回值一般為126。使用LoadLibrary()動態載入dll失敗的原因一般分兩種:

1)路徑不對(程式與dll要放在同一資料夾)

2) dll本身錯誤(依賴其他dll)

解決方法:

a. 將DLL與exe放於同一目錄

b. Loadlibrary()與LoadlibraryEx()

c. DLL本身依賴使用depends.exe/Dependency Walker(depends)檢視該DLL依賴哪些DLL

“depends.exe 使用說明”:http://blog.csdn.net/scythe666/article/details/47165533

2. GetProcAddress()是用來獲取函式地址的,呼叫格式

 fun1 =(DLLfun)GetProcAddress(hdll, "sharpen");


具體看上面程式或者百度。通常若dll生成規範,可以直接執行成功。失敗則返回0或NULL。

(1)當其調用出錯時,用GetLastError()得到返回值一般為127,表示函式地址獲取失敗。原因基本上是因為函式名(如“sharpen”)在dll中不存在。當函式名沒有拼寫出錯的時候,則是因為dll在生成過程中把函式按不同標準進行修改,如改成了“?? 0sharpen @@

[email protected]”(通過depends.exe工具可以檢視具體函式名),這時候呼叫引數改成下面的形式就可以呼叫成功。


fun1 = (DLLfun)GetProcAddress(hdll, "?? 0sharpen @@[email protected] ");

這種方式呼叫起來很不方便,每次都要去檢視dll,所以我們最好在生成dll的時候就讓函式名變得規範,下文會對dll生成中的注意點進行介紹。

3. FreeLibrary()是用來釋放載入dll時佔用的空間的,由於Loadlibrary()為對dll的顯式載入(又叫動態載入),這種方式不會在用完dll後自動清理dll所佔用的空間,所以我們要手動清除dll所佔用的空間。否則會導致記憶體洩漏。呼叫格式如下:

FreeLibrary(hdll);

以上三個函式的具體使用還可以參考:

“動態載入DLL所需要的三個函式詳解(LoadLibrary,GetProcAddress,FreeLibrary)”

http://www.cnblogs.com/westsoft/p/5936092.html

4.規範化生成dll的注意點

(1)標頭檔案中定義__declspec(dllexport)  時,要加上extern "C",從而規範dll的輸出符合C標準,否則容易生成帶@之類的字串。extern"C"使得在C++中使用C編譯方式成為可能。在“C++”下定義“C”函式,需要加extern“C”關鍵詞。用extern "C"來指明該函式使用C編譯方式。加上extern C後,輸出函式的形式為"sharpen"符合預期標準。格式如下:(其中IMG_EXPORTS為自己定義輸出巨集

#define IMG_EXPORTS extern "C" __declspec(dllexport)  
//或者
extern "C" _declspec(dllimport) int calculateLineNum(CString filePath); 

但是該過程需要注意一點避免出錯,例如下面生成dll的標頭檔案程式碼中,函式輸出應該寫在AAA出現的位置,而不是BBB位置,在需要輸出到dll的函式前加上IMG_EXPORTS,如下:


#ifdef IMG_API_EXPORTS  
#define IMG_EXPORTS extern "C" __declspec(dllexport)  
AAAAAAAAAAAAAAAAA
IMG_EXPORTS int fun1(int a,int b);
#else  
#define IMG_EXPORTS __declspec(dllimport)  
#endif  
BBBBBBBBBBBBBBBBBB

注:使用微軟專用的_declspec (dllexport) 

cpp檔案在編譯為OBJ檔案時要對函式進行重新命名,C語言會把函式name重新命名為_name,而C++會重新命名為[email protected]@decoration,extern "C"表示用C語言的格式將函式重新命名,要輸出整個的類,對類使用_declspec(_dllexpot);要輸出類的成員函式,則對該函式使用_declspec(_dllexport)。

注:extern C的作用詳解參考部落格:http://blog.csdn.net/jiqiren007/article/details/5933599

(2)不加extern "C"的情況下,輸出函式名容易變形,靜態呼叫dll完全沒問題,但是顯式呼叫dll容易出錯。除了extern "C",我們還可以新增的標準約定,如_stdcall等。

1)__stdcall呼叫約定

在輸出函式名前加上一個下劃線字首,後面加上一個“@”符號和其引數的位元組數,格式為[email protected]

2__cdecl

呼叫約定僅在輸出函式名前加上一個下劃線字首,格式為_functionname

3__fastcall呼叫約定在輸出函式名前加上一個“@”符號,後面也是一個“@”符號和其引數的位元組數,格式為@[email protected]

它們均不改變輸出函式名中的字元大小寫,這和PASCAL呼叫約定不同,PASCAL約定輸出的函式名無任何修飾且全部大寫。

注:關於dll匯出函式名的方式還可以參考以下部落格:

dll匯出函式名的那些事:http://blog.csdn.net/qq_16209077/article/details/51989114







相關推薦

no