差異處
這裏顯示兩個版本的差異處。
cpp:dll:windows:howtousedll [2017/03/15 21:05] tony [Reference] |
cpp:dll:windows:howtousedll [2023/06/25 09:48] |
||
---|---|---|---|
行 1: | 行 1: | ||
- | {{tag>cpp dll}} | ||
- | ====== How to use DLL? ====== | ||
- | ===== Introduction ===== | ||
- | 網路上能夠找到許多和DLL相關的教學。但我相信大家看了之後依然會存在許多疑問,或者想只要會用就好並不會去了解更深一層的意義。有時我會想: 別人所撰寫的API,我們是不是應該至少要去了解其原理或設計呢? 如果一直當個只懂Call API的Programmer,而沒去學習更多解決問題的方法,只要換個語言應該就和廢人無異了吧! 言歸正傳,今天這篇教學是特地寫給Albert兄的,教導大家DLL使用的兩種方法: **裝載期間動態連結**與**執行期間動態連結**。我以範例來說明,讓大家可以迅速了解該如何使用(千言萬語抵不過Example Code吧?)。\\ | ||
- | {{:cpp:dll:windows:howtouserdll.zip|Download SourceCode}} | ||
- | ===== Write a DLL ===== | ||
- | 寫DLL相當簡單。如果是VC的用戶,直接透過Wizard產生出DDL的專案,基本的Skeleton都有了。其中的2個重點如下: 匯出函式宣告與DLL檔定義。\\ | ||
- | 匯出的函式我們必須使用_declspec(dllexport)或_declspec(dllimport)宣告,目的是為了讓裝載期間動態連結能夠找到DLL對應的函式。 | ||
- | <code cpp> | ||
- | #ifdef MYDLLHELLOWORLD_EXPORTS | ||
- | #define MYDLLHELLOWORLD_API __declspec(dllexport) | ||
- | #else | ||
- | #define MYDLLHELLOWORLD_API __declspec(dllimport) | ||
- | #endif | ||
- | // Declare export functions | ||
- | MYDLLHELLOWORLD_API void showHelloWorldString( LPCTSTR s); | ||
- | </code> | ||
- | 如果是在執行期間動態連結,我們必須宣告def檔。VC中新增def的畫面如下:\\ | ||
- | {{.:dll_add_def.png?700|}}\\ | ||
- | 接著將要匯出的函式加入def中(其實已經可以不需要def了): | ||
- | <code cpp> | ||
- | LIBRARY "DLLHelloWolrd" | ||
- | EXPORTS | ||
- | showHelloWorldString @1 | ||
- | </code> | ||
- | 在編譯之後,會產生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位元之區別。 | ||
- | <code cpp> | ||
- | #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; | ||
- | } | ||
- | </code> | ||
- | ===== 執行期間動態連結 ===== | ||
- | 使用執行期間動態連結的重點如下: | ||
- | - 宣告欲呼叫之函式之function pointer,如**typedef void (*PFNEXPORTFUNC)(LPCTSTR)**。 | ||
- | - LoadLibrary與FreeLibrary。使用DLL之前,必須使用LoadLibrary將它載入,如果不是傳路徑給檔而是使用檔名,它會有一定的[[java:jna:dll_search_path|搜尋路徑]]。如果Load有問題,可確認HMODULE是否為NULL,最後用完當然要釋放資源。 | ||
- | - 透過GetProcAddress取得函式在DLL中的執行位置並呼叫。如**GetProcAddress( hModule, "showHelloWorldString")**,**showHelloWorldString**為要呼叫的函式名稱。也可透過判斷取得結果是否為NULL去確認呼叫是否正確。 | ||
- | <code cpp> | ||
- | #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; | ||
- | } | ||
- | </code> | ||
- | ===== 總結 ===== | ||
- | 到底用哪一個方法好呢? 裝載期間動態連接的優點是: 我們不需要去宣告有的沒的函式指標,也不用擔心介面宣告錯誤的問題。 但缺點是: 如果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以做匯出之用。(前處理器的用途可參考[[cpp:mixed:preprocessor|這篇]]) | ||
- | * 而使用此DLL的Client,會使用dllimport宣告,將DLL函式做匯入之用。 | ||
- | {{:cpp:dll:windows:dll_preproc.png?800|}} | ||
- | |||
- | |||
- | |||
- | 第二個問題,等之後我有時間且完全了解後,會替大家解答。 | ||
- | |||
- | ===== Reference ===== | ||
- | * Windows程式設計 - 王艷平 編著 | ||
- | * [[http://msdn.microsoft.com/zh-tw/library/1ez7dh12(v=vs.80).aspx|MSDN - DLL]] | ||
- | * [[https://msdn.microsoft.com/zh-tw/library/d91k01sh.aspx|使用 .DEF 檔從 DLL 匯出]] | ||
- | |||
- | ===== ===== | ||
- | ---- | ||
- | \\ | ||
- | ~~DISQUS~~ |