動態加載DLL實現不同DLL的相同導出函數調用問題

假設這樣的情景:

        一個exe加載了兩個dll(dll1和dll2,並使用隱式加載)。dll和dll2都同時向外導出一個函數:

        int add(int a, int b);

        如果在exe中調用了add函數,那麼它調用的會是哪個dll的add函數呢?

        爲了驗證這個結果,寫了一個測試的例子。兩個dll(win32的dll)分別取名位MyDll1和MyDll2,並讓他們導出同樣的上述add函數,只是在實現的時候加了一個打印輸出,用以區分到底是哪個dll調用。

        最終發現是和在工程配置中的lib順序相關的。也就是說,如果在【Link】的Object/library modules中的順序爲MyDll1.lib MyDll2.lib(MyDll1在前),主程序中調用的是MyDll1中的函數實現;相反,如果工程中配置的是MyDll2.libMyDll1.lib(MyDll2在前),主程序中調用的是MyDll2中的函數實現

       看到結果後想,這似乎是合理的。exe發現一個函數調用,而且是在dll中的。它就會遍歷自己所加載的dll去搜索,這個應該就是順序關鍵性的所在,找到第一個匹配的,就執行調用。所以,誰在前,調用誰。

       對此情形,想到另外一個問題。對於較大的程序來說,往往要分成若干個模塊。而常常爲了接口的統一,需要在dll中導出相同的函數。那麼主程序怎麼才能調用想要執行的模塊中對應的函數呢?

        一個最簡單也直接的辦法就是使用動態加載,並在LoadLibrary後,使用GetProcAddress。



加載DLL的方法主要有兩種:一種是隱式鏈接,另外一種是動態加載。

    隱式鏈接會把DLL中所有標誌爲_declspec(dllexport)的函數都加載,如果有多個DLL加載時,可能會影響到程序執行的效率。而用動態加載DLL的方式則可以根據需要去加載用到的函數。
    動態加載DLL的方法:
    1.生成dll過程:把生成的.DLL文件複製到測試工程DLLTest目錄下。這裏假設該.DLL文件爲add.dll,主要代碼是:
_declspec(dllexport) int add(int x, int y)
{
    return x + y;
}
 
    2.使用1生成的dll:在DLLTest工程中添加DllTest.cpp文件.
首先使用LoadLibrary("add.dll")加載add.dll文件:
HMODULE hmod = LoadLibrary("add.dll");
 
然後定義一個函數指針的類型:
typedef int (*AddAddr)(int x, int y);
 
注意,這裏的參數與返回類型務必與add.dll文件中函數add的聲明一樣。
 
接着:
AddAddr Add = (AddAddr)GetProcAddress(hmod, "add");
 
如果Add值爲空,則獲取函數的地址失敗!
if(!Add)
{
    printf("獲取函數地址失敗!");
    return;
}
 
最後,可以測試一下:
printf("test add(): 1+2=%d", add(1,2));
 
運行結果一看,會出現“獲取函數地址失敗!”。爲什麼會這樣?
 
打開命令行,用cd命令到add.dll工程目錄的debug目錄下,然後使用命令:
dumpbin -exports add.dll
 
則會看到add.dll文件中的add函數的名稱爲“”,而不是函數名add,這是C++編譯器的命名改編機制。 修改原來的代碼:
AddAddr Add = (AddAddr)GetProcAddress(hmod, "");
這時運行就成功了。但如果按這樣去動態加載DLL,那每次獲取函數地址都要使用dumpbin命令去獲取,則會很麻煩。
 
那怎樣可以直接使用add而不是 這個長長的字符串呢,修改add.dll的add函數,在函數前加上extern "C",再編譯add.dll文件所在的工程,複製新生成的add.dll覆蓋DLLTest工程目錄下的add.dll,原來的代碼獲取函數地址時使用add,結果運行就成功了。
 
而再使用dumpbin -exports add.dll命令,顯示add.dll的中的add函數的名稱變成了add.
 
 
如果dll沒有對應的.lib文件,那麼就只能使用動態加載的方式了。
動態調用動態庫步驟:
1、創建一個函數指針,其指針數據類型要與調用的DLL引出函數相吻合。
2、通過Win32 API函數LoadLibrary()顯式的調用DLL,此函數返回DLL的實例句柄。
3、通過Win32 API函數GetProcAddress()獲取要調用的DLL的函數地址,把結果賦給自定義函數的指針類型。
4、使用函數指針來調用DLL函數。
5、最後調用完成後,通過Win32 API函數FreeLibrary()釋放DLL函數。

例如:
假設函數的聲明爲:void message(int a);
//函數指針聲明
typedef void (WINAPI MESSAGE)(int a);
MESSAGE *pMessage = 0;

//加載a.dll
HINSTANCE hDLLDrv = LoadLibrary("a.dll");

//獲取message函數的指針
if(hDLLDrv)
{
pMessage = (MESSAGE *)GetProcAddress(hDLLDrv, "message");
}

然後就可以這樣調用函數了:
pMessage(1);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章