在C++中调用DLL中的函数
1.dll的優點
代碼復用是提高軟件開發效率的重要途徑。一般而言,只要某部分代碼具有通用性,就可將它構造成相對獨立的功能模塊并在之后的項目中重復使用。比較常見的例子是各種應用程序框架,ATL、MFC等,它們都以源代碼的形式發布。由于這種復用是“源碼級別”的,源代碼完全暴露給了程序員,因而稱之為“白盒復用”。“白盒復用”的缺點比較多,總結起來有4點。?暴露了源代碼;多份拷貝,造成存儲浪費;?
容易與程序員的“普通”代碼發生命名沖突;?
更新功能模塊比較困難,不利于問題的模塊化實現;?
實際上,以上4點概括起來就是“暴露的源代碼”造成“代碼嚴重耦合”。為了彌補這些不足,就提出了“二進制級別”的代碼復用。使用二進制級別的代碼復用一定程度上隱藏了源代碼,對于緩解代碼耦合現象起到了一定的作用。這樣的復用被稱為“黑盒復用”。?
說明:實現“黑盒復用”的途徑不只dll一種,靜態鏈接庫甚至更高級的COM組件都是。
2.dll的創建
參考程序原文:http://msdn.microsoft.com/zh-cn/library/ms235636.aspx?新建“Win32項目”,選擇應用程序類型為"DLL”,其他默認。添加頭文件testdll.h
?//testdll.h
#ifdef?TESTDLL_EXPORTS??#define?TESTDLL_API?__declspec(dllexport)???
#else??
#define?TESTDLL_API?__declspec(dllimport)???
#endif??
namespace?MathFuncs??
{??
????//?This?class?is?exported?from?the?testdll.dll??
????class?MyMathFuncs??
????{??
????public:???
????????//?Returns?a?+?b??
????????static?TESTDLL_API?double?Add(double?a,?double?b);
????????//?Returns?a?-?b??
????????static?TESTDLL_API?double?Subtract(double?a,?double?b);
????????//?Returns?a?*?b??
????????static?TESTDLL_API?double?Multiply(double?a,?double?b);
????????//?Returns?a?/?b??
????????//?Throws?const?std::invalid_argument&?if?b?is?0??
????????static?TESTDLL_API?double?Divide(double?a,?double?b);
????};??
}
?當定義了符號TESTDLL_EXPORTS,TESTDLL_API被設置為?__declspec(dllexport)?修飾符,?This modifier enables the function to be exported by the DLL so that it can be used by other applications。若未定義則TESTDLL_API被設置為__declspec(dllimport),This modifier enables the compiler to optimize the importing of the function from the DLL for use in other applications。當DLL項目生成時,TESTDLL_EXPORTS默認是定義的,所以默認設置的是__declspec(dllexport)?修飾符。
添加cpp文件?//?testdll.cpp?:?定義?DLL?應用程序的導出函數。
#include?"stdafx.h"#include?"testdll.h"??
#include?<stdexcept>??
using?namespace?std;??
namespace?MathFuncs??
{??
????double?MyMathFuncs::Add(double?a,?double?b)??
????{??
????????return?a?+?b;??
????}??
????double?MyMathFuncs::Subtract(double?a,?double?b)??
????{??
????????return?a?-?b;??
????}??
????double?MyMathFuncs::Multiply(double?a,?double?b)??
????{??
????????return?a?*?b;??
????}??
????double?MyMathFuncs::Divide(double?a,?double?b)??
????{??
????????if?(b?==?0)??
????????{??
????????????throw?invalid_argument("b?cannot?be?zero!");??
????????}??
????????return?a?/?b;??
????}??
}
編譯就會生成對應的dll文件,同時也會生成對應的lib文件。?
注意:a.DLL中導出函數的聲明有兩種方式:在函數聲明中加上__declspec(dllexport);采用模塊定義(.def)文件聲明。詳見:http://www.cnblogs.com/enterBeijingThreetimes/archive/2010/08/04/1792099.html?
b.對于C文件創建dll時或者想使用C編譯器創建dll時,建議使用?extern “C”?標志,參見extern "C"的簡單解析(在C++中調用DLL中的函數(1))
?3.dll的調用
?應用程序使用DLL可以采用兩種方式:一種是隱式鏈接(調用),另一種是顯式鏈接。在使用DLL之前首先要知道DLL中函數的結構信息。VS在VC\bin目錄下提供了一個名為Dumpbin.exe的小程序,用它可以查看DLL文件中的函數結構。兩種的對比詳見:http://blog.sina.com.cn/s/blog_53004b4901009h3b.html?
隱式鏈接采用靜態加載的方式,比較簡單,需要.h、.lib、.dll三件套。。
新建“控制臺應用程序”或“空項目”配置如下:(非常關鍵)
項目->屬性->配置屬性->VC++?目錄->?在“包含目錄”里添加頭文件testdll.h所在的目錄
項目->屬性->配置屬性->VC++?目錄->?在“庫目錄”里添加頭文件testdll.lib所在的目錄
項目->屬性->配置屬性->鏈接器->輸入->?在“附加依賴項”里添加“testdll.lib”(若有多個?lib?則以空格隔開)
添加cpp文件
?//mydll.cpp
#include?<iostream>??#include?"testdll.h"??
using?namespace?std;??
int?main()??
{??
????double?a?=?7.4;??
????int?b?=?99;??
????cout?<<?"a?+?b?=?"?<<??
????????MathFuncs::MyMathFuncs::Add(a,?b)?<<?endl;??
????cout?<<?"a?-?b?=?"?<<??
????????MathFuncs::MyMathFuncs::Subtract(a,?b)?<<?endl;??
????cout?<<?"a?*?b?=?"?<<??
????????MathFuncs::MyMathFuncs::Multiply(a,?b)?<<?endl;??
????cout?<<?"a?/?b?=?"?<<??
????????MathFuncs::MyMathFuncs::Divide(a,?b)?<<?endl;??
????try??
????{??
????????cout?<<?"a?/?0?=?"?<<??
????????????MathFuncs::MyMathFuncs::Divide(a,?0)?<<?endl;???
????}??
????catch?(const?invalid_argument?&e)???
????{??
????????cout?<<?"Caught?exception:?"?<<?e.what()?<<?endl;???
????}??
????return?0;??
}
現在可以編譯通過了,但是程序運行就報錯,還需要將testdll.dll復制到當前項目生成的可執行文件所在的目錄。?
顯式鏈接是應用程序在執行過程中隨時可以加載DLL文件,也可以隨時卸載DLL文件,這是隱式鏈接所無法作到的,所以顯式鏈接具有更好的靈活性,對于解釋性語言更為合適。?
新建項目,不需要特殊配置,添加cpp文件
?#include<Windows.h>?//加載的頭文件
#include<iostream>using?namespace?std;
int?main()??
{??
????typedef?double?(*pAdd)(double?a,?double?b);
????typedef?double?(*pSubtract)(double?a,?double?b);
?
????HMODULE?hDLL?=?LoadLibrary("testdll.dll");?//加載dll文件?
????if(hDLL?!=?NULL)??
????{??
????????pAdd?fp1?=?pAdd(GetProcAddress(hDLL,?MAKEINTRESOURCE(1)));?//得到dll中的第一個函數
????????if(fp1?!=?NULL)??
????????{???
????????????cout<<fp1(2.5,?5.5)<<endl;?
????????}??
????????else??
????????{??
????????????cout<<"Cannot?Find?Function?"<<"add"<<endl;??
????????}??
????????pSubtract?fp2?=?pSubtract(GetProcAddress(hDLL,?"?Subtract@MyMathFuncs@MathFuncs@@SANNN@Z"));?//得到dll中標示為"?..."的函數,C++編譯器考慮了函數的參數
????????if(fp2?!=?NULL)??
????????{??
????????????cout<<fp2(5.5,?2.5)<<endl;??
????????}??
????????else??
????????{??
????????????cout<<"Cannot?Find?Function?"<<"Subtract"<<endl;??
????????}??
????????FreeLibrary(hDLL);??
????}??
????else??
????{??
????????std::cout<<"Cannot?Find?"<<"testdll"<<std::endl;??
????}??
????return?1;??
}
?顯式調用的問題:在DLL文件中,dll工程中函數名稱在編譯生成DLL的過程中發生了變化(C++編譯器),在DLL文件中稱變化后的字符為“name標示”。GetProcAddress中第二個參數可以由DLL文件中函數的順序獲得,或者直接使用DLL文件中的”name標示”,這個標示可以通過Dumpbin.exe小程序查看。如果C++編譯器下,想讓函數名更規范(和原來工程中一樣),具體方法詳見:http://blog.csdn.net/btwsmile/article/details/6676802。?
當然,為了讓函數名更規范,最常用的方式是:創建dll過程中使用C編譯器來編譯函數,這樣DLL文件中的函數名和原dll工程中的函數名就一致了。
4.更一般的顯式調用
為了解決上部分最后的問題,可以使用?extern “C”?為dll工程中的函數建立C連接,簡單的示例工程如下。?在DLL創建的工程中,添加cpp文件
?#include?"stdafx.h"
#ifdef?__cplusplus?????????//?if?used?by?C++?code
extern?"C"?{??????????????????//?we?need?to?export?the?C?interface
#endif
__declspec(dllexport)?int?addfun(int?a,?int?b)
{
????????return?a+b;
}
#ifdef?__cplusplus
}
#endif
?
編譯即可生成DLL文件。在dll調用工程中,添加cpp文件
?
/*
?*作者:侯凱
?*說明:顯式調用dll
?*日期:2013-6-5
*/
#include?<windows.h>
#include?<iostream>
using?namespace?std;
void?main()
{
????typedef?int(*FUNA)(int,int);
????HMODULE?hMod?=?LoadLibrary("cdll.dll");//dll路徑
????if?(hMod)
????{
????????FUNA?addfun?=?(FUNA)GetProcAddress(hMod,?TEXT("addfun"));//直接使用原工程函數名?
????????if?(addfun?!=?NULL)
????????{
????????????cout<<addfun(5,?4)<<endl;
????????}
????????else
????????{
????????????cout<<"ERROR?on?GetProcAddress"<<endl;
????????}
????????FreeLibrary(hMod);
????}
????else
????????cout<<"ERROR?on?LoadLibrary"<<endl;
}
?運行,這樣便可以調用dll的函數了。再進一步,上述dll文件如果通過隱式調用,利用.dll、.lib文件,調用函數應為
?//隱式鏈接
#include?<iostream>#pragma?comment(lib,"cdll.lib")
using?namespace?std;
extern?"C"?_declspec(dllimport)?int?addfun(int?a,int?b);
//載入addfun函數,這里起到了.h文件的作用
//dll中使用C編譯器?故這里需要extern?"C"?如果dll中無extern?"C"
//此處為:_declspec(dllimport)?int?addfun(int?a,int?b);
void?main()
{
????cout<<addfun(5,4)<<endl;
} 創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎
總結
以上是生活随笔為你收集整理的在C++中调用DLL中的函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 长寿的十个秘诀 至少选择一个坚持实施
- 下一篇: C++ string 介绍