動態連接庫

1. 動態連接庫的分類html

  Visual C++支持三種DLL,它們分別是Non-MFC DLL(非MFC動態庫)、 MFC Regular DLL(MFC規則DLL)、 MFC Extension DLL(MFC擴展DLL)。ios

  (1) 非MFC動態庫:不採用MFC類庫結構,其導出函數爲標準的C接口,能被非MFC或MFC編寫的應用程序所調用。windows

  (2) MFC規則DLL:包含一個繼承自CWinApp的類,但其無消息循環。安全

  (3) MFC擴展DLL:採用MFC的動態連接版本建立,它只能被用MFC類庫所編寫的應用程序所調用。ide

2. DLL中導出函數的聲明方式函數

  一種方式是:在函數聲明中加上__declspec(dllexport)
  另一種方式是:採用模塊定義(.def)文件聲明,(.def)文件爲連接器提供了有關被連接程序的導出、屬性及其餘方面的信息。測試

  (1) 方式一:在函數聲明中加上__declspec(dllexport)編碼

1 #pragma once
2 
3 extern "C" __declspec(dllexport) int add(int x, int y);
.h
1 #include "stdafx.h"
2 #include "dllTest.h"
3 
4 int add(int x, int y)
5 {
6     return (x + y);
7 }
.cpp

   (2) 方式二:採用模塊定義(.def)文件聲明spa

    ①首先建立 一個DLL程序(DllTestDef)3d

    ②在*.cpp中添加須要導出的接口函數

 1 #include "stdafx.h"
 2 
 3 int __stdcall Add(int a, int b)
 4 {
 5     return (a + b);
 6 }
 7 
 8 int _stdcall Sub(int a, int b)
 9 {
10     return (a - b);
11 }
.cpp

     ③而後建立一個.def的文件

1 LIBRARY dllTest
2 EXPORTS
3 Add @ 1
4 Sub @ 2
.def

3. DLL的調用方式

  (1)動態調用:

    "LoadLibrary-GetProcAddress-FreeLibrary"系統API提供的三位一體"DLL加載-DLL函數地址獲取-DLL釋放"方式,這種調用方式稱爲DLL的動態調用。

 1 #include "stdafx.h"
 2 #include <iostream>
 3 using namespace std;
 4 #include <windows.h>
 5 
 6 typedef int(*lpAdd)(int, int);
 7 typedef int(*lpSub)(int, int);
 8 
 9 int main()
10 {
11     HMODULE hDll = NULL;
12     lpAdd pAdd = NULL;
13     lpSub pSub = NULL;
14 
15     hDll = LoadLibrary(TEXT("dllTest.dll"));
16     if (hDll != NULL)
17     {
18         pAdd = (lpAdd)GetProcAddress(hDll, "Add");
19         pSub = (lpSub)GetProcAddress(hDll, "Sub");
20 
21         if (pAdd != NULL)
22         {
23             cout << "3 + 5 = " << pAdd(3, 5) << endl;
24         }
25 
26         if (pSub != NULL)
27         {
28             cout << "2 + 4 = " << pSub(2, 4) << endl;
29         }
30     }
31 
32     getchar();
33     return 0;
34 }
View Code

   (2)靜態調用:

    靜態調用,也稱爲隱式調用,由編譯系統完成對DLL的加載和應用程序結束時DLL卸載的編碼(Windows系統負責對DLL調用次數的計數),調用方式簡單,可以知足一般的要求。一般採用的調用方式是把產生動態鏈接庫時產生的.LIB文件加入到應用程序的工程中,想使用DLL中的函數時,只須在源文件中聲明一下。

 1 #include "stdafx.h"
 2 #include <iostream>
 3 using namespace std;
 4 
 5 #pragma comment(lib, "..\\x64\\Release\\dllTest.lib")
 6 extern "C" __declspec(dllimport) int add(int x, int y);
 7 
 8 int main()
 9 {
10     cout << "4 + 7 = " << add(4, 7) << endl;
11 
12     getchar();
13     return 0;
14 }
View Code

 4. DllMain函數

    Windows在加載DLL的時候,須要一個入口函數,如同控制檯或 DOS 程序須要 main 函數、WIN32程序須要 WinMain 函數同樣。

    在前面的例子中,DLL並無提供 DllMain函數, 應用工程也能成功引用 DLL,這是由於Windows在找不到DllMain的時候, 系統會從其它運行庫中引入一個不作任何操做的缺省 DllMain函數版本,並不意味着 DLL能夠放棄 DllMain函數。

    根據編寫規範, Windows必須查找並執行 DLL裏的 DllMain函數做爲加載 DLL的依據,它使得 DLL得以保留在內存裏。這個函數並不屬於導出函數,而是 DLL 的內部函數。這意味着不能直接在應用工程中引用 DllMain函數, DllMain是自動被調用的 。

 1 BOOL APIENTRY DllMain( HMODULE hModule,
 2                        DWORD  ul_reason_for_call,
 3                        LPVOID lpReserved
 4                      )
 5 {
 6     switch (ul_reason_for_call)
 7     {
 8     case DLL_PROCESS_ATTACH:
 9     case DLL_THREAD_ATTACH:
10     case DLL_THREAD_DETACH:
11     case DLL_PROCESS_DETACH:
12         break;
13     }
14     return TRUE;
15 }
DllMain

  5. 關於調用約定

  C/C++缺省的調用方式是__cdecl方式,Windows API使用__stdcall調用方式,在DLL導出函數中,爲了跟Windows API保持一致,建議使用__stdcall調用方式。

        __cdecl方式與__stdcall對函數名最終生成符號的方式不一樣。若採用C編譯方式(在C++中需將函數聲明爲extern "C")。

  __stdcall調用約定在輸出函數名錢加下劃線,後面加「@」符號和參數的字節數,形如_functionname@number;

  而__cdecl調用約定僅在輸出函數名前加下劃線,形如_functionname。

6. DLL導出變量

     DLL定義的全局變量能夠被調用進程訪問,DLL也能夠訪問調用進程的全局數據。

    (1) 在DLL中導出變量有兩種方法:

   方法一:用模塊定義文件(.def)進行導出聲明  

 1 // dllmain.cpp : 定義 DLL 應用程序的入口點。
 2 #include "stdafx.h"
 3 
 4 int dllGlobalVar;
 5 
 6 BOOL APIENTRY DllMain( HMODULE hModule,
 7                        DWORD  ul_reason_for_call,
 8                        LPVOID lpReserved
 9                      )
10 {
11     switch (ul_reason_for_call)
12     {
13     case DLL_PROCESS_ATTACH:
14         dllGlobalVar = 123;
15         break;
16     case DLL_THREAD_ATTACH:
17     case DLL_THREAD_DETACH:
18     case DLL_PROCESS_DETACH:
19         break;
20     }
21     return TRUE;
22 }
23 
24 
25 
26 LIBRARY dllExportVariable_def
27 EXPORTS
28 dllGlobalVar DATA
View Code

  特別要注意的是用extern int dllGlobalVar聲明所導入的並非DLL中全局變量自己,而是其地址,應用程序必須經過強制指針轉換來使用DLL中的全局變量。這一點,從*(int*)dllGlobalVar能夠看出。所以在採用這種方式引用DLL全局變量時,千萬不要進行這樣的賦值操做:

                dllGlobalVar = 1;
  其結果是dllGlobalVar指針的內容發生變化,程序中之後再也引用不到DLL中的全局變量了。

  而經過_declspec(dllimport)方式導入的就是DLL中全局變量自己而再也不是其地址了,筆者建議在一切可能的狀況下都使用這種方式。

  方法二:用__declspec進行導出聲明

1 __declspec(dllexport) extern int dllGlobalVar = 88;
View Code

   (2) 調用DLL中導出的變量:

  一樣,應用程序調用DLL中的變量也有兩種方法。 

  第一種是隱式連接: 

 1 #include <iostream>
 2 using namespace std;
 3 
 4 #pragma comment(lib, "..\\x64\\Debug\\dllExportVariable_declspec.lib")
 5 extern _declspec(dllimport) int dllGlobalVar;
 6 
 7 int main()
 8 {
 9     cout << "dllGlobalVar = " << dllGlobalVar << endl;
10 
11     dllGlobalVar = 88;
12     cout << "dllGlobalVar = " << dllGlobalVar << endl;
13 
14     getchar();
15     return 0;
16 }
View Code

  第二種是顯式連接: 

 1 #include <iostream>
 2 using namespace std;
 3 
 4 #include <windows.h>
 5 
 6 int main()
 7 {
 8     int my_int;
 9     HINSTANCE hInstLibrary = LoadLibrary(TEXT("dllExportVariable_def.dll"));
10 
11     if (hInstLibrary != NULL)
12     {
13         my_int = *(int*)GetProcAddress(hInstLibrary, "dllGlobalVar");
14         cout << "my_int = " << my_int << endl;
15     }
16     FreeLibrary(hInstLibrary);
17 
18     getchar();
19     return 0;
20 }
View Code

  Note:通常不建議從DLL中導出全局變量,對於但願從DLL獲取資源以實現資源共享的情景,最好是經過導出一個Get函數得到,這樣操做起來更方便並且更安全。

7. DLL導出類

  1、導出類的簡單方式

    這種方式是比較簡單的,同時也是不建議採用的不合適方式。 

    只須要在導出類加上__declspec(dllexport),就能夠實現導出類。對象空間仍是在使用者的模塊裏,dll只提供類中的函數代碼。

    不足的地方是:使用者須要知道整個類的實現,包括基類、類中成員對象,也就是說全部跟導出類相關的東西,使用者都要知道。經過Dependency Walker能夠看到,這時候的dll導出的是跟類相關的函數:如構造函數、賦值操做符、析構函數、其它函數,這些都是使用者可能會用到的函數。

    這種導出類的方式,除了導出的東西太多、使用者對類的實現依賴太多以外,還有其它問題:必須保證使用同一種編譯器。導出類的本質是導出類裏的函數,由於語法上直接導出了類,沒有對函數的調用方式、重命名進行設置,致使了產生的dll並不通用。

 

    簡單方式導出類的DLL示例:

 1 #pragma once
 2 
 3 //相關的類都必須導出
 4 class _declspec(dllexport) CBase
 5 {
 6 public:
 7     void Test1();
 8 private:
 9     int m_var1;
10 };
11 
12 //相關的類都必須導出
13 class _declspec(dllexport) CData
14 {
15 public:
16     void Test2();
17 private:
18     int m_var2;
19 };
20 
21 //要導出的類
22 class _declspec(dllexport) CExportClass : public CBase
23 {
24 public:
25     CExportClass(int i = 0);
26 
27     void TestFun();
28     CData GetDataObj() { return m_DataObj; }
29 
30 private:
31     int m_i;
32     CData m_DataObj;
33 };
.h
 1 CExportClass::CExportClass(int i) : m_i(i)
 2 {
 3 }
 4 
 5 void CExportClass::TestFun()
 6 {
 7     cout << "This is TestFun() from CExportClass class!" << endl;
 8 }
 9 
10 void CBase::Test1()
11 {
12     cout << "This is Test1() from CBase class!" << endl;
13 }
14 
15 void CData::Test2()
16 {
17     cout << "This is Test2() from CData class!" << endl;
18 }
.cpp

   調用示例:

 1 #include "stdafx.h"
 2 #include <Windows.h>
 3 #include "..\dllTest_ExportClass(NotRecommend)\dllTest_ExportClass(NotRecommend).h"
 4 
 5 #pragma comment(lib, "..\\x64\\Debug\\dllTest_ExportClass(NotRecommend).lib")
 6 
 7 int main()
 8 {
 9     CExportClass obj(55);
10     obj.Test1();
11     obj.TestFun();
12 
13     CData DataObj = obj.GetDataObj();
14     DataObj.Test2();
15 
16     system("pause");
17     return 0;
18 }
View Code

   2、導出類的較好方式

    這種方式和COM相似,它的結構是這樣的:導出類是一個派生類,派生自一個抽象類(都是純虛函數)。使用者只需知道這個抽象類的結構。

    DLL最少須要提供一個用於獲取類對象指針的接口。使用者和DLL提供者共用一個抽象類的頭文件。使用者依賴於DLL的東西不多,只須要知道抽象類的接口,以及獲取對象指針的導出函數,對象內存空間的申請是在DLL模塊中作的,釋放也在DLL模塊中完成(須要在最後調用釋放對象的函數)。

    這種方式比較好,通用,產生的DLL沒有特定的環境限制。除了對DLL導出類有好處外,它採用接口和實現分離,也可使得工程結構更清晰,使用者只須要知道接口,不須要知道實現。

    

    測試示例代碼下載地址: https://files.cnblogs.com/files/YQ2014/dllTest_ExportClass%28NotRecommend%29.zip   

  

  參考資料:

  http://www.cnblogs.com/cswuyg/archive/2011/10/06/DLL2.html

  http://www.codeproject.com/KB/cpp/howto_export_cpp_classes.aspx

  

  導出類的DLL要當心DLL Hell問題。

       詳細的能夠參考:DLL導出類避免地獄問題的完美解決方案

相關文章
相關標籤/搜索