動態連結庫開發說明
本文章轉載至:http://www.cnblogs.com/hanford/p/6177904.html#_Toc425102248
目錄
第1章基本概念
1.1 一個簡單的例子
下面將使用VC++建立一個動態連結庫檔案。這個檔案將匯出兩個函式StringReverseA、StringReverseW,前者將一個ANSI字串逆序,後者將一個Unicode字串逆序。
1.1.1 新建一個VC++專案
對於VC++6.0而言,專案型別請選擇
在接下來的介面裡,選擇"An empty DLL project",然後單擊"Finish"按鈕。
在接下來的介面裡單擊"OK"按鈕完成專案建立。
對於VC++9.0(即VC++2008)而言,專案型別請選擇Win32。輸入專案名稱後,單擊"確定"按鈕。
在接下來的介面裡,請選擇"應用程式設定"下的"DLL"和"空專案"。單擊"完成"按鈕完成專案建立。
1.1.2 新增原始檔
對於VC++6.0而言,在Workspace 視窗的 FileView
輸入原始檔名後,單擊"OK"按鈕
彈出對話方塊裡詢問是否在專案裡增加Test.c這個檔案的引用。請單擊"是"按鈕。
此時滑鼠雙擊Test.c。因為這個檔案還不存在,VC++6.0會提示是否建立,請單擊"是"按鈕。
對於VC++9.0而言,在解決方案資源管理器裡,右鍵單擊"Test",在右鍵選單裡單擊【新增】【新建項】選單項。
接下來的介面內,請選擇"C++檔案(.cpp)",並輸入原始檔名Test.c,然後單擊"新增"按鈕。完成
1.1.3 輸入原始碼
在Test.c裡輸入如下原始碼:
#include <windows.h> /***************************************************************************\ 將一個 Unicode 字串逆序 \***************************************************************************/ __declspec(dllexport) wchar_t* WINAPI StringReverseW(wchar_t*wzStr) { if(wzStr) { int p1 = 0; int p2 = wcslen(wzStr) - 1; wchar_t t; while(p1 < p2) { t = wzStr[p1]; wzStr[p1++] = wzStr[p2]; wzStr[p2--] = t; } } return wzStr; } /***************************************************************************\ 將一個 ANSI 字串逆序 \***************************************************************************/ __declspec(dllexport) char* WINAPI StringReverseA(char*szStr) { if(szStr) { int nLenA = strlen(szStr) + 1; int nLenW = MultiByteToWideChar(CP_ACP,0,szStr,nLenA,NULL,0); wchar_t*pStrW = (wchar_t*)malloc(nLenW * sizeof(wchar_t)); MultiByteToWideChar(CP_ACP,0,szStr,nLenA,pStrW,nLenW); StringReverseW(pStrW); WideCharToMultiByte(CP_ACP,0,pStrW,nLenW,szStr,nLenA,NULL,NULL); free(pStrW); } return szStr; } |
1.1.4 __declspec(dllexport)
__declspec(dllexport)修飾符用來匯出函式StringReverseA和StringReverseW。它還可以匯出變數和類,這個後面介紹。
1.1.5 WINAPI
WINAPI 其實就是__stdcall。以StringReverseA為例,呼叫它時,引數szStr將被壓入棧中,從StringReverseA返回時,引數szStr需要出棧。__stdcall表示由StringReverseA自己執行出棧操作。假如將__stdcall去掉或換為__cdecl,則由呼叫StringReverseA的函式負責執行出棧操作。說了這麼多,最重要的是:某些語言,如VB6.0只支援__stdcall,所以為了讓這個dll被儘可能多的程式語言支援,請使用WINAPI。
1.1.6 匯出符號
現在可以編譯程式,生成Test.dll了。使用eXeScope6.30開啟Test.dll,可以看到Test.dll確實匯出了兩個函式。請注意:每個匯出函式都有一個序號,它是一個正整數。
不過有意思的是:匯出函式的名稱並不是StringReverseA和StringReverseW,而是[email protected]和[email protected]。
如果把Test.c改名為Test.cpp,則匯出的名稱更為複雜。請參考下圖:
這是什麼原因呢?因為Test.c的副檔名為c,VC++使用C編譯器進行編譯。Test.cpp的副檔名為cpp,VC++使用C++編譯器進行編譯。C++為了實現函式過載,編譯時會根據引數型別和個數對函式名進行再次命名。
1.1.7 DEF檔案
如何防止VC++編譯器生成dll時將匯出函式名更改掉?答案就是使用模組定義檔案。請在VC++專案裡增加模組定義檔案Test.def。這個檔名可以是1.def、A.def……只要副檔名是def即可。編輯Test.def,使其內容如下:
EXPORTS StringReverseA StringReverseW |
上述內容表示:匯出函式StringReverseA和StringReverseW。此時,這兩個函式前面的__declspec(dllexport)修飾符將不再需要。
DEF檔案的功能還有很多,具體請參考MSDN。
1.2 呼叫動態庫
生成的動態庫檔案可以被多種程式語言使用。限於篇幅下面僅介紹VC++如何呼叫動態庫。
1.2.1 隱式連結
編譯動態庫檔案時,同時會生成Lib檔案。使用動態庫的VC++程式可以連結這個Lib檔案,這就是隱式連結。可參考的程式碼如下:
#include <windows.h> #include <stdio.h> __declspec(dllimport) char* WINAPI StringReverseA(char*szStr); #pragma comment(lib,"D:/VC6/Test/Debug/Test.lib") void main() { char szStr[] = "隱式連結動態庫"; puts(StringReverseA(szStr)); } |
__declspec(dllimport) char* WINAPI StringReverseA(char*szStr); 是函式宣告。修飾符__declspec(dllimport)表示這是一個匯入函式。去除這個修飾符不影響程式的編譯、執行,但有了__declspec(dllimport)之後,生成的程式碼更小,執行更快。
注意:對於C++程式而言,可能需要這樣宣告函式:
extern "C"
{
__declspec(dllimport) char* WINAPI StringReverseA(char*szStr);
}
extern "C" 的作用是:告訴C++編譯器連線時不要以C++語法修改StringReverseA的名稱。為什麼說是"可能"需要extern "C"呢?這與dll的編譯有關係。如果使用C編譯器編譯dll,則需要extern "C";如果使用C++編譯器編譯dll,則不需要extern "C"。
#pragma comment(lib,"D:/VC6/Test/Debug/Test.lib")表示連結的時候使用D:\VC6\Test\Debug\Test.Lib檔案。就是編譯動態庫時產生的那個Lib檔案。
採用隱式連結,執行程式的時候動態庫檔案首先被載入至記憶體。系統如何定位dll檔案呢?其搜尋順序為:exe所在目錄、當前目錄(GetCurrentDirectory)、System32目錄(GetSystemDirectory)、Windows目錄(GetWindowsDirectory)、環境變數PATH指定的目錄。不用記這麼多,最保險的做法就是將dll和exe放在同一資料夾下。如果為了多個exe程式共享一個dll,請將這個dll檔案複製到System32目錄下。
1.2.2 顯式連結
顯式連結可以靈活控制動態庫檔案的載入、解除安裝。其使用步驟如下:
1、使用LoadLibrary函式載入動態庫檔案至記憶體;
2、使用GetProcAddress函式獲得匯出函式的地址;
3、呼叫匯出函式;
4、使用FreeLibrary解除安裝動態庫檔案。
可參考如下程式碼:
#include <windows.h> #include <stdio.h> void main() { HINSTANCE hDll = LoadLibrary("Test.dll"); //載入動態庫檔案 if(hDll) {//載入成功 |