一文梳理vs2017中dll的使用

Windows下有靜態鏈接(lib)庫和動態鏈接庫(dll)兩種共享代碼的方式。

本文將介紹dll的應用場景,以及在vs2017平臺下的生成和使用。


[What] dll是什麼

動態鏈接庫(Dynamic Link Library)又稱爲“應用程序擴展”,在windows系統中,大多數應用程序並非僅有一個可執行文件exe,同時也包含一些相對獨立(模塊化)的dll文件。dll中存放函數代碼實現,exe中存放dll中相應函數代碼的地址,而且dll中的代碼可以被多個exe調用而在內存中僅保留一份拷貝,從而節省了內存空間。

[How] 如何生成dll

1. 新建一個dll項目

選擇“具有導出項的(DLL)動態鏈接庫”,vs會幫我們自動創建與項目同名的.cpp文件和.h文件,並在.h文件中定義好相關導出符號;如果選擇“動態鏈接庫(DLL)”則不會創建上述文件。
dll新建項目

創建完成後,可以看到vs已經幫我們完成導出符號和預處理器的定義:
dll導出符號定義
dll預處理器定義

2. dll兩種導出方式

vs官方文檔中提供了兩種方式可以導出dll中的函數:
dll兩種導出方式

  • 模塊定義文件(.def):通用性(指給其他語言eg. Java、C#調用)好,但操作相對複雜;

  • 關鍵字__declspec(dllexport):操作簡單,但通用性較差。可見,vs創建dll項目時默認使用了該方式。

下面依次介紹這兩種導出方式。

3. 模塊定義文件(.def)

  • 新建.def文件dll新建def文件vs會自動添加.def文件爲鏈接器輸入:
    dll添加def爲鏈接器輸入

  • 實現一個dll函數
    dll函數實現_def文件

  • 模塊定義語句規則

    模塊定義語句有許多規則,這裏只用到EXPORTS關鍵字:
    dll模塊定義語句規則_EXPORTS

  • 編寫.def文件如下:
    dll編寫def文件

4. 關鍵字__declspec(dllexport)

分別以C++和C的方式實現兩個導出函數,後面會看到這兩個函數的不同之處:
dll函數實現_dllexport

5. 生成結果

dll生成結果

這裏除了MYDLL.dll還生成了MYDLL.lib文件,它是dll的導入庫,用於隱式鏈接dll

Visual Studio 命令提示符處啓動DUMPBIN工具,執行dumpbin -exports path\to\dll命令分析生成的dll,查看編譯器產生的函數修飾名:
dll_DUMPBIN分析

可以得出以下兩個結論:

  • VC++編譯器會針對C++函數使用名稱修飾,而不會修飾以C方式聲明的函數(其實也修飾了,在函數名前面加了一個下劃線前綴,這是C語言的函數調用約定默認爲__cdecl所導致);

  • .def文件的作用就是將被修飾過的C++函數重命名,使其可以被其他語言調用,具有通用性。

[How] 如何調用dll

新建一個控制檯應用,在其中調用上述生成的dll。

調用dll有兩種鏈接方式:隱式鏈接顯式鏈接,無論哪種方式都要求將dll和exe放在同一目錄下。

1. 隱式鏈接

  • 隱式鏈接需要三個文件:.h文件.lib文件.dll文件

  • 可以使用上一篇文章介紹的項目配置編譯語句的方式將.h文件和.lib文件添加到項目中;如果dll項目和控制檯項目在同一解決方案下,也可以採取直接引用的方式將dll項目添加到控制檯項目。

  • 這裏使用編譯語句的方式,調用結果如下:
    dll隱式鏈接調用結果

2. 顯式鏈接

  • 顯式鏈接只需要一個文件:.dll文件

  • 所謂顯式鏈接,就是直接調用WIN32 API函數LoadLibraryGetProcAddressFreeLibrary顯式地裝載、卸載dll。它們的作用如下:dll顯式鏈接 WIN32 API函數

  • 調用代碼:

#include <iostream>
#include <Windows.h> // 必要頭文件

void dllLinkExplicitly() {
    typedef void(*LPFNDLLFUNC)(std::ostream &);
    // equal to
    // using LPFNDLLFUNC = void(*)(std::ostream &);

    HINSTANCE hDLL;                     // Handle to DLL
    LPFNDLLFUNC lpfnMyDLLWithDllExport; // Function pointer
    LPFNDLLFUNC lpfnMyDLLWithExternC;   // Function pointer
    LPFNDLLFUNC lpfnMyDLLWithDefFile;   // Function pointer

    hDLL = LoadLibrary("MyDLL.dll");
    if (hDLL != NULL) {
        lpfnMyDLLWithDllExport = (LPFNDLLFUNC)GetProcAddress(hDLL, "fnMyDLLWithDllExport");
        lpfnMyDLLWithExternC = (LPFNDLLFUNC)GetProcAddress(hDLL, "fnMyDLLWithExternC");
        lpfnMyDLLWithDefFile = (LPFNDLLFUNC)GetProcAddress(hDLL, "fnMyDLLWithDefFile");

        if (!lpfnMyDLLWithDllExport) { // handle the error
            std::cout << "fnMyDLLWithDllExport load error.\n";
        }
        else { // call the function
            lpfnMyDLLWithDllExport(std::cout);
        }

        if (!lpfnMyDLLWithExternC) {
            std::cout << "fnMyDLLWithExternC load error.\n";
        }
        else {
            lpfnMyDLLWithExternC(std::cout);
        }

        if (!lpfnMyDLLWithDefFile) {
            std::cout << "fnMyDLLWithDefFile load error.\n";
        }
        else {
            lpfnMyDLLWithDefFile(std::cout);
        }
    }
    FreeLibrary(hDLL);
}

int main() {
    dllLinkExplicitly();
    std::cout << "this is my exe.\n";
    system("PAUSE");
}
  • 調用結果:
    dll顯式鏈接調用結果
    可以看出,由於fnMyDLLWithDllExport的函數名被編譯器修飾過,已經無法通過原來的函數名調用。這也暴露出顯式鏈接的一個弊端:要求開發人員必須清楚地知道調用函數的導出名稱傳參格式

3. 小結

  • 隱式鏈接是程序載入內存時加載所需的dll,且該dll隨主進程始終佔用內存。

    • 優點:配置好.h文件和.lib文件的路徑後調用方式簡單直接;

    • 缺點:需要藉助.h文件和.lib文件獲得所需函數的入口(如使用#pragma comment語句),注意這裏的lib是dll導入庫,與靜態鏈接庫lib有所不同。

  • 顯式鏈接是在程序運行過程中需要時使用LoadLibrary加載,不需要時則使用FreeLibrary將其卸載。如果加載時該dll已經在內存,則只需將其引用計數加1,如果其引用計數爲0則移出內存。

    • 優點:不需要.h文件和.lib文件,可以動態加載、卸載dll;

    • 缺點:調用WIN32 API函數增加了編碼量,需要開發人員對dll很瞭解。

[Why] dll的優缺點

  • 優點:相比lib加載全量代碼到exe中,dll節省了內存空間資源;當程序更新時,僅需要以補丁或擴展的形式發佈新的dll即可。

  • 缺點:dll需隨exe一起發佈,否則程序在運行時會產生錯誤。

[Summary] lib和dll的比較

# lib dll
調用時需要的文件 .h.lib 隱式鏈接需要.h.lib.dll,顯式鏈接僅需要.dll
多次調用同一函數 內存中有多份拷貝 只有一份拷貝
能否再包含其他lib或dll ✔️
程序發佈是否需要提供 ✔️
程序後續更新 全量更新 增量更新
適用場景 一次性交付,不保證後續更新 持續性交付,系統有模塊化要求

[Github] 代碼

項目實例均在vs2017上測試,並上傳至GitHub: https://github.com/lyandut/EXE_LIB_DLL

[Reference] 參考

https://www.cnblogs.com/alantu2018/p/8470976.html
https://www.cnblogs.com/TenosDoIt/p/3203137.html
https://blog.csdn.net/weixin_43118068/article/details/88760001
https://blog.csdn.net/cynophile/article/details/79749524
https://blog.csdn.net/freeking101/article/details/104632710
https://blog.csdn.net/Hilavergil/article/details/78544424

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