摘: VS2010 C++ 調用 DLL (C++編寫)


一、為什么需要dll

代碼復用是提高軟件開發效率的重要途徑。一般而言,只要某部分代碼具有通用性,就可將它構造成相對獨立的功能模塊並在之后的項目中重復使用。

比較常見的例子是各種應用程序框架,如ATL、MFC等,它們都以源代碼的形式發布。由於這種復用是“源碼級別”的,源代碼完全暴露給了程序員,因

而稱之為“白盒復用”。“白盒復用”的缺點比較多,總結起來有4點。

  1. 暴露了源代碼;
  2. 容易與程序員的“普通”代碼發生命名沖突;
  3. 多份拷貝,造成存儲浪費;
  4. 更新功能模塊比較困難。

實際上,以上4點概括起來就是“暴露的源代碼”造成“代碼嚴重耦合”。為了彌補這些不足,就提出了“二進制級別”的代碼復用。使用二進制級別的代碼復用

一定程度上隱藏了源代碼,對於緩解代碼耦合現象起到了一定的作用。這樣的復用被稱為“黑盒復用”。

在Windows操作系統中有兩種可執行文件,其后綴名分別為.exe和.dll。它們的區別在於,.exe文件可被獨立的裝載於內存中運行;.dll文件卻不能,它只能被

其它進程調用。然而無論什么格式,它們都是二進制文件。上面說到的“二進制級別”的代碼復用,可以使用.dll來實現。

與白盒復用相比,.dll很大程度上彌補了上述4大缺陷。.dll是二進制文件,因此隱藏了源代碼;如果采用“顯式調用”(后邊將會提到),一般不會發生命名沖

突;由於.dll是動態鏈接到應用程序中去的,它並不會在鏈接生成程序時被原原本本拷貝進去;.dll文件相對獨立的存在,因此更新功能模塊是可行的。

說明:實現“黑盒復用”的途徑不只dll一種,靜態鏈接庫甚至更高級的COM組件都是。本文只對dll進行討論。

二、創建dll

接下來用一個簡單的例子來說明創建dll的方法。本例采用VS2010,使用C++編程語言,具體操作步驟如下。

通過Start Page或者File菜單欄,新建一個Project,將會彈出新建項目對話框。選擇Win32 Project向導,項目名為CreateDLL,解決方案名為DLLTEST(注意

Create directories for solution是勾選上的),點擊OK,接着點擊Next,到Application Settings,選擇應用程序類型為dll,並勾選“Export Symbols”,點擊Finish。

完成這一步之后,VS界面上左邊的Solution Explorer中將會看到向導自動生成的文件列表,如圖1所示。

圖1 wizard自動生成的文件列表

在VS界面的編輯窗口中,展示了自動生成的CreateDLL.cpp的代碼。

  1. // CreateDLL.cpp : Defines the exported functions for the DLL application.  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5. #include "CreateDLL.h"  
  6.   
  7.   
  8. // This is an example of an exported variable  
  9. CREATEDLL_API int nCreateDLL = 0;  
  10.   
  11. // This is an example of an exported function.  
  12. CREATEDLL_API int fnCreateDLL(void)  
  13. {  
  14.     return 42;  
  15. }  
  16.   
  17. // This is the constructor of a class that has been exported.  
  18. // see CreateDLL.h for the class definition  
  19. CCreateDLL::CCreateDLL()  
  20. {  
  21.     return;  
  22. }  

這里有3種類型的example,分別為導出變量nCreateDLL、導出函數fnCreateDLL以及導出類CCreateDLL。為了簡化起見,本例只考慮導出函數。

修改CreateDLL.h文件為:

  1. #ifdef CREATEDLL_EXPORTS  
  2. #define CREATEDLL_API __declspec(dllexport)  
  3. #else  
  4. #define CREATEDLL_API __declspec(dllimport)  
  5. #endif  
  6.   
  7. CREATEDLL_API void printMax(int&,int&);  
  8. CREATEDLL_API void printMax(int&,int&,int&);  

修改CreateDLL.cpp文件為: 

  1. CREATEDLL_API void printMax(int& a,int& b)  
  2. {  
  3.     std::cout<<"Among ("<<a<<","<<b<<"), the Max Number is "<<(a>b?a:b)<<"\n";  
  4. }  
  5. CREATEDLL_API void printMax(int& a,int& b,int& c)  
  6. {  
  7.     std::cout<<"Among ("<<a<<","<<b<<","<<c<<"), the Max Number is "<<(((a>b?a:b)>c)?(a>b?a:b):c)<<"\n";  
  8. }  

不難發現,printMax函數的作用就是打印出兩個整數或三個整數中的最大值。需要說明的是,這里故意使用同名函數是為了引出導出函數的修飾名稱,

具體將在第四節中闡述。

接下來,選擇菜單Build->Build CreateDLL,Output窗口提示CreateDLL.dll文件生成成功,如圖2所示。

 圖2 CreateDLL.dll成功生成

三、使用dll

本例采用“顯式調用”的方式使用CreateDLL.dll。顯式調用方式相比於”隱式調用“有好有壞。顯式調用只需要一個.dll文件就可以了,靈活性更好,

更新模塊方便;相對的,程序員需要做的事情更多,使用方法更為復雜。

右鍵單擊Solution Explorer中的Solution 'DLLTEST',在彈出的菜單中選擇Add->New Project,選擇Win32 Console Application,輸入項目名為UseDLL,

點擊OK,接着點擊Next,在Application Settings界面勾選EmptyProject並點擊Finish。右鍵單擊項目UseDLL,給它添加源文件UseDLL.cpp。這樣操

作之后,Solution Explorer的信息如圖3所示。

圖3 向Solution'DLLTEST'添加項目UseDLL

編寫UseDLL.cpp的代碼為:

  1. /*--UseDLL.cpp 
  2.  *Author: ume(李優米) 
  3.  *Use CreateDLL.dll explicitly 
  4.  */  
  5. #include<Windows.h>  
  6. #include<iostream>  
  7. typedef void(*FUNA)(int&,int&);  
  8. typedef void(*FUNB)(int&,int&,int&);  
  9. int main()  
  10. {  
  11.     const char* dllName = "CreateDLL.dll";  
  12.     const char* funName1 = "printMax";  
  13.     const char* funName2 = "printMax";  
  14.     int x(100), y(100), z(100);  
  15.     HMODULE hDLL = LoadLibrary(dllName);  
  16.     if(hDLL != NULL)  
  17.     {  
  18.         FUNA fp1 = FUNA(GetProcAddress(hDLL,funName1));  
  19.         if(fp1 != NULL)  
  20.         {  
  21.             std::cout<<"Input 2 Numbers:";  
  22.             std::cin>>x>>y;  
  23.             fp1(x,y);  
  24.         }  
  25.         else  
  26.         {  
  27.             std::cout<<"Cannot Find Function "<<funName1<<std::endl;  
  28.         }  
  29.         FUNB fp2 = FUNB(GetProcAddress(hDLL,funName2));  
  30.         if(fp2 != NULL)  
  31.         {  
  32.             std::cout<<"Input 3 Numbers:";  
  33.             std::cin>>x>>y>>z;  
  34.             fp2(x,y,z);  
  35.         }  
  36.         else  
  37.         {  
  38.             std::cout<<"Cannot Find Function "<<funName2<<std::endl;  
  39.         }  
  40.         FreeLibrary(hDLL);  
  41.     }  
  42.     else  
  43.     {  
  44.         std::cout<<"Cannot Find "<<dllName<<std::endl;  
  45.     }  
  46.     return 1;  
  47. }  

代碼比較長,但是並不難理解,這里僅說明代碼中的一些要點。

  • 包含頭文件Windows.h,原因在於程序中用到了LoadLibrary、FreeLibrary、GetProcAddress等Win32 API函數。
  • FUNA和FUNB是函數指針類型的聲明。
  • 當程序不再使用dll時,應該調用FreeLibrary及時釋放它占用的內存空間。
  • 如果在const char* dllName和funName底部出現紅色波浪線提示,說明采用的字符集不匹配,需要修改項目UseDLL的屬性CharaterSet為Not Set。
  • 為方便項目的調試,建議修改解決方案的Startup Project屬性為Single startup project並以UseDLL為首選。

然而,這個程序還有錯誤。編譯並運行,結果如圖4所示。

                圖4 UseDLL的運行結果

這並不是期望中的結果。實際上,正如第二節提到的那樣,造成這種錯誤的原因正是導出函數的修飾名稱。雖然在CreateDLL.cpp中兩個printMax函數

有相同的名稱,但在dll二進制文件中,經過編譯器的“加工”,它們實際上各自有不同的名稱了。這也是函數重載機制得以實現的一個技術支持。

使用VS2010附帶工具dumpbin,查看CreateDLL.dll的導出函數名,結果如圖5所示。

 圖5 查看CreateDLL.dll的導出函數名

觀察圖5可以發現,CreateDLL.dll導出函數名為?printMax@@YAXAAH00@Z和?printMax@@YAXAAH0@Z。它們分別對應着三個整數的printMax

和兩個整數的printMax。因此,Use.DLL中funName應當相應修改為:

  1. const char* funName1 = "?printMax@@YAXAAH0@Z";  
  2. const char* funName2 = “?printMax@@YAXAAH00@Z”;  

修改之后,再次編譯運行,結果正確,如圖6所示。

 圖6 UseDLL正常運行

四、dll導出函數名稱規范化

創建、使用dll並不復雜,走過前三節,相信讀者肯定有這樣的體會。然而,一個問題仍然值得思考:導出函數的修飾名稱太“奇怪”,

為dll的使用帶來了不便,能不能讓導出函數的修飾名稱規范一些?答案是肯定的,而且方法至少有兩種:一是運用extern "C"修飾printMax;

二是運用模塊定義文件.def。后者的效果更好,所以本節將使用.def來規范化導出函數的修飾名稱。

CreateDLL.dll導出的兩個函數功能很簡單,根據功能描述,理想的函數名稱是pMaxA2和pMaxA3。在CreateDLL項目中添加CreateDLL.def文件:

  1. LIBRARY CreateDLL  
  2. EXPORTS  
  3. pMaxA2 = ?printMax@@YAXAAH0@Z  
  4. pMaxA3 = ?printMax@@YAXAAH00@Z  

重新build項目CreateDLL,使用dumpbin再次查看CreateDLL.dll的導出函數名稱,結果如圖7所示。

圖7 規范化的函數名,奇怪的修飾名稱還存在

出現了期望的結果,但仍有小缺憾:奇怪的修飾名稱仍然存在。能否去掉這些不太規范的修飾名稱呢?當然是可以的。只需要將CreateDLL.h

中#define CREATEDLL_API __declspec(dllexport) 修改為#define CREATEDLL_API即可。修改之后重新編譯生成CreateDLL.dll,使用dumpbin

查看導出函數名稱,結果如圖8所示。

圖8 規范化的函數名,去除了奇怪的修飾名稱    

回到UseDLL.cpp,修改funName:

  1. const char* funName1 = "pMaxA2";  
  2. const char* funName2 = "pMaxA3";  

重新編譯運行UseDLL,結果正確,與圖6類似。
五、dll的不足

動態鏈接庫雖然一定程度上實現了“黑盒復用”,但仍存在着諸多不足,筆者能夠想到的有下面幾點。

  1. dll節省了編譯期的時間,但相應延長了運行期的時間,因為在使用dll的導出函數時,不但要加載dll,而且程序將會在模塊間跳轉,
  2. 降低了cache的命中率。
  3. 若采用隱式調用,仍然需要.h、.lib、.dll文件(“三件套”),並不能有效支持模塊的更新。
  4. 顯式調用雖然很好地支持模塊的更新,但卻不能導出類和變量。
  5. dll不支持Template。

二進制級別的代碼復用相比源碼級別的復用已經有了很大的進步,但在二進制級別的代碼復用中,dll顯得太古老。想真正完美實現跨平台、

跨語言的黑盒復用,采用COM才是正確的選擇。

 

http://blog.csdn.net/wondergdf/article/details/7870491?reload


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM