Introduction
網路上能夠找到許多和DLL相關的教學。但我相信大家看了之後依然會存在許多疑問,或者想只要會用就好並不會去了解更深一層的意義。有時我會想: 別人所撰寫的API,我們是不是應該至少要去了解其原理或設計呢? 如果一直當個只懂Call API的Programmer,而沒去學習更多解決問題的方法,只要換個語言應該就和廢人無異了吧! 言歸正傳,今天這篇教學是特地寫給Albert兄的,教導大家DLL使用的兩種方法: 裝載期間動態連結與執行期間動態連結。
Write a DLL
寫DLL相當簡單。如果是VC的用戶,直接透過Wizard產生出DDL的專案,基本的Skeleton都有了。其中的2個重點如下: 匯出函式宣告與DLL檔定義。
匯出的函式我們必須使用_declspec(dllexport)或_declspec(dllimport)宣告,目的是為了讓裝載期間動態連結能夠找到DLL對應的函式。
#ifdef MYDLLHELLOWORLD_EXPORTS #define MYDLLHELLOWORLD_API __declspec(dllexport) #else #define MYDLLHELLOWORLD_API __declspec(dllimport) #endif // Declare export functions MYDLLHELLOWORLD_API void showHelloWorldString( LPCTSTR s);
如果是在執行期間動態連結,如果不是使用__declspec(dllexport),我們就必須宣告def檔。VC中新增def的畫面如下:
接著將要匯出的函式加入def中:LIBRARY "DLLHelloWolrd" EXPORTS showHelloWorldString @1
在編譯之後,會產生DLLHelloWolrd.dll與DLLHelloWolrd.lib,這兩個檔案在使用DLL時是所被需要的。
裝載期間動態連結
使用裝載期間動態連結的重點如下:
- 將lib放置專案目錄下。
- 宣告_declspec(dllexport) void showHelloWorldString( LPCTSTR s)或_declspec(dllimport) void showHelloWorldString( LPCTSTR s)。一般會直接將DDL的header file include進來,目前看過third-party的library都會附上dll、lib與header file。
- 加入#pragma comment宣告我們所要載入的lib。如果這個lib不正確,在編譯時就會發生連結錯了。lib與dll相同也有32位元與64位元之區別。
#include "stdafx.h" #include "DLLHelloWorld.h" #include <atlbase.h> #include <iostream> using namespace std; #pragma comment(lib, "DLLHelloWolrd") int _tmain(int argc, _TCHAR* argv[]) { LPCWSTR pw = W2T( L"Hello world!"); showHelloWorldString(pw); system("pause"); return 0; }
執行期間動態連結
使用執行期間動態連結的重點如下:
宣告欲呼叫之函式之function pointer,如typedef void (*PFNEXPORTFUNC)(LPCTSTR)。
LoadLibrary與FreeLibrary。使用DLL之前,必須使用LoadLibrary將它載入,如果不是傳路徑給檔而是使用檔名,它會有一定的搜尋路徑。如果Load有問題,可確認HMODULE是否為NULL,最後用完當然要釋放資源。
透過GetProcAddress取得函式在DLL中的執行位置並呼叫。如GetProcAddress( hModule, “showHelloWorldString”),showHelloWorldString為要呼叫的函式名稱。也可透過判斷取得結果是否為NULL去確認呼叫是否正確。
#include "stdafx.h" #include <atlbase.h> using namespace std; LPCWSTR hwdll = W2T(L"DLLHelloWolrd.dll"); typedef void (*PFNEXPORTFUNC)(LPCTSTR); int _tmain(int argc, _TCHAR* argv[]) { HMODULE hModule = ::LoadLibrary(hwdll); if( hModule != NULL ){ PFNEXPORTFUNC fnHelloWorldDLL = (PFNEXPORTFUNC)::GetProcAddress( hModule, "showHelloWorldString"); if( fnHelloWorldDLL != NULL ){ fnHelloWorldDLL( W2T( L"Hello world!")); } else { cout << "Init function failure!" << endl; } FreeLibrary(hModule); } else { cout << "Load " << hwdll << " failure!" << endl; } system("pause"); return 0; }
總結
到底用哪一個方法好呢? 裝載期間動態連接的優點是: 我們不需要去宣告有的沒的函式指標,也不用擔心介面宣告錯誤的問題。 但缺點是: 如果DDL檔案一但不見,這隻程式會永遠起不來。執行期間動態連結的缺點是: 一堆有的沒的函式指標宣告,稍微打錯就ㄘㄨㄚˋ賽了。 但優點是: 由於是在執行期間載入,如果讀取不到會獲得一個NULL,程式設計人員也可以根據這種情況做些例外處置。舉例來說,支援32與64位元的程式,會載入32與64位元的DLL。假如它所使用的名稱如example32.dll與example64.dll,我們可以透過先讀取example32.dll是否成功,再決定是否讀取example64.dll。另外一種使用情況也許是對於功能的alternative選擇。
另外大家看完這篇文章後,可能還有以下疑問:
- dllexport與dllimport差在哪?
- showHelloWorldString @1的@1有何用途?
第一個問題經過Silvius的指導, dllexport與dllimport的函義如下:
- 以上述的例子而言,DLL專案在前處理器會加入MYDLLHELLOWORLD_EXPORTS一同編譯。這將使得函式宣告為dllexport以做匯出之用。
- 而使用此DLL的Client,會使用dllimport宣告,將DLL函式做匯入之用。
第二個問題,可以參考此篇,是為了相容於舊系統使用序數的載入方式。
Reference
- Windows程式設計 - 王艷平 編著
- 使用 .DEF 檔從 DLL 匯出
- EXPORTS
留言
張貼留言