C++动态链接库的制作
輸入函式__declspec(dllimport)?與輸出函式__declspec(dllexport)?有什么區別呢?我知道他們不同,但差別在哪呢?我用的全是__declspec(dllexport)?,?__declspec(dllimport)一般在什么時用呢?說說一般在什么時分別用到它們?
導出函式__declspec(dllexport)在dll中用
導入函式__declspec(dllimport)在要調用dll的程序中用?
這是指靜態連接
動態鏈接就不需要__declspec(dllimport)
很多書都有介紹
_declspec(dllexport) 與__declspec(dllimport) 的使用說明
__declspec(XXXXXX)是windows擴展C++的編譯宏頭
_declspec(dllexport)?
聲明一個導出函數,是說這個函數要從本DLL導出。我要給別人用。一般用于dll中?。
省掉在DEF文件中手工定義導出哪些函數的一個方法。當然,如果你的DLL里全是C++的類的話,你無法在DEF里指定導出的函數,只能用__declspec(dllexport)導出類。
__declspec(dllimport)
聲明一個導入函數,是說這個函數是從別的DLL導入。我要用。一般用于使用某個dll的exe中?。
不使用 __declspec(dllimport) 也能正確編譯代碼,但使用 __declspec(dllimport) 使編譯器可以生成更好的代碼。編譯器之所以能夠生成更好的代碼,是因為它可以確定函數是否存在于 DLL 中,這使得編譯器可以生成跳過間接尋址級別的代碼,而這些代碼通常會出現在跨 DLL 邊界的函數調用中。但是,必須使用 __declspec(dllimport) 才能導入 DLL 中使用的變量。
??? 相信寫WIN32程序的人,做過DLL,都會很清楚__declspec(dllexport)的作用,它就是為了省掉在DEF文件中手工定義導出哪些函數的一個方法。當然,如果你的DLL里全是C++的類的話,你無法在DEF里指定導出的函數,只能用__declspec(dllexport)導出類。但是,MSDN文檔里面,對于__declspec(dllimport)的說明讓人感覺有點奇怪,先來看看MSDN里面是怎么說的:
??? 不使用?__declspec(dllimport) 也能正確編譯代碼,但使用?__declspec(dllimport) 使編譯器可以生成更好的代碼。編譯器之所以能夠生成更好的代碼,是因為它可以確定函數是否存在于 DLL 中,這使得編譯器可以生成跳過間接尋址級別的代碼,而這些代碼通常會出現在跨 DLL 邊界的函數調用中。但是,必須使用?__declspec(dllimport) 才能導入 DLL 中使用的變量。
extern??? "C"???
指示編譯器用C語言方法給函數命名。
在制作DLL導出函數時由于C++存在函數重載,因此__declspec(dllexport)??? function(int,int)??? 在DLL會被decorate,例如被decorate成為??? function_int_int,而且不同的編譯器decorate的方法不同,造成了在用GetProcAddress取得function地址時的不便,使用extern??? "C"時,上述的decorate不會發生,因為C沒有函數重載,但如此一來被extern"C"修飾的函數,就不具備重載能力,可以說extern??? 和?? extern??? "C"不是一回事。
C++編譯器在生成DLL時,會對導出的函數進行名字改編,并且不同的編譯器使用的改變規則不一樣,因此改編后的名字會不一樣。這樣,如果利用不同的編譯器分別生成DLL和訪問該DLL的客戶端代碼程序的話,后者在訪問該DLL的導出函數時會出現問題。為了實現通用性,需要加上限定符:extern “C”。
但是利用限定符extern “C”可以解決C++和C之間相互調用時函數命名的問題,但是這種方法有一個缺陷,就是不能用于導出一個類的成員函數,只能用于導出全局函數。
? LoadLibrary導入的函數名,對于非改編的函數,可以寫函數名;對于改編的函數,就必須吧@和號碼都寫上,一樣可以加載成功,可以試試看。
解決警告? inconsistent dll linkage
????inconsistent dll linkage警告是寫dll時常遇到的一個問題,解決此警告的方法如下:
??? 一般PREDLL_API工程依賴于是否定義了MYDLL_EXPORTS來決定宏展開為__declspec(dllexport)還是__declspec(dllimport)。展開為__declspec(dllexport)是DLL編譯時的需要,通知編譯器該函數是需要導出供外部調用的。展開為__declspec(dllimport)是給調用者用的,通知編譯器,該函數是個外部導入函數。
對于工程設置里面的預定義宏,是最早被編譯器看到的。所以當編譯器編譯DLL工程中的MYDLL.cpp時,因為看到前面有工程設置有定義MYDLL_EXPORTS,所以就把PREDLL_API展開為__declspec(dllexport)了。
這樣做的目的是為了讓DLL和調用者共用同一個h文件,在DLL項目中,定義MYDLL_EXPORTS,PREDLL_API就是導出;在調用該DLL的項目中,不定義MYDLL_EXPORTS,PREDLL_API就是導入。
使用dll的兩種方式
方法一: load-time dynamic linking?(隱式調用)
在要調用dll的應用程序鏈接時,將dll的輸入庫文件(import library,.lib文件)包含進去。具體的做法是在源文件開頭加一句#include ,然后就可以在源文件中調用dlldemo.dll中的輸出文件了。
方法二: run-time dynamic linking?(顯示調用)
不必在鏈接時包含輸入庫文件,而是在源程序中使用LoadLibrary或LoadLibraryEx動態的載入dll。
主要步驟為(以demodll.dll為例):?
1) typedef函數原型和定義函數指針。
typedef void (CALLBACK* DllFooType)(void) ;
DllFooType pfnDllFoo = NULL ;
2) 使用LoadLibrary載入dll,并保存dll實例句柄
HINSTANCE dllHandle = NULL ;
...?
dllHandle = LoadLibrary(TEXT("dlldemo.dll"));
3) 使用GetProcAddress得到dll中函數的指針
pfnDllFoo = (DllFooType)GetProcAddress(dllHandle,TEXT("DllFoo")) ;
注意從GetProcAddress返回的指針必須轉型為特定類型的函數指針。
4)檢驗函數指針,如果不為空則可調用該函數?
if(pfnDllFoo!=NULL)
DllFoo() ;
5)使用FreeLibrary卸載dll
FreeLibrary(dllHandle) ;
??? 使用run-time dynamic linking 比較麻煩,但有它的好處(下面討論)。MSDN中有一篇文章DLLs the Dynamic Way討論使用c的宏創建一個基類pDll完成以上復雜的操作,使用時只需定義一個類繼承自類pDll并對類和函數使用宏。?
以上兩種方法都要求應用程序能找到dll文件,Windows按以下順序尋找dll文件:
???如果系統不能找到dll文件,將結束調用dll的進程并彈出一個“啟動程序時出錯”對話框,告訴你“找不到所需的dll文件-XXX.dll”
一、DLL的創建?
創建項目: Win32->Win32項目,名稱:MyDLL
選擇DLL (D) ->完成.
1、新建頭文件testdll.h
testdll.h代碼如下:
#ifndef TestDll_H_
#define TestDll_H_
#ifdef MYLIBDLL
#define MYLIBDLL extern "C" _declspec(dllimport)?
#else
#define MYLIBDLL extern "C" _declspec(dllexport)?
#endif
MYLIBDLL int Add(int plus1, int plus2);
//You can also write like this:
//extern "C" {
//_declspec(dllexport) int Add(int plus1, int plus2);
//};
#endif
2、新建源文件testdll.cpp
testdll.cpp代碼如下:
#include "stdafx.h"
#include "testdll.h"
#include <iostream>
using namespace std;
int Add(int plus1, int plus2)
{
int add_result = plus1 + plus2;
return add_result;
}
3、新建模塊定義文件mydll.def
mydll.def代碼如下:
LIBRARY "MyDLL"
EXPORTS
Add @1
4、vs2010自動創建dllmain.cpp文件,它定義了DLL 應用程序的入口點。
dllmain.cpp代碼如下:
// dllmain.cpp : 定義 DLL 應用程序的入口點。
#include "stdafx.h"
BOOL APIENTRY DllMain( HMODULE hModule,
? ?? ?? ?? ?? ?? ?? ???DWORD??ul_reason_for_call,
? ?? ?? ?? ?? ?? ?? ???LPVOID lpReserved
? ?? ?)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
??break;
}
return TRUE;
}
最后,編譯生成MyDLL.dll文件和MyDLL.lib文件。
1>------ 已啟動生成: 項目: MyDLL, 配置: Debug Win32 ------
1> ?dllmain.cpp
========== 生成: 成功 1 個,失敗 0 個,最新 0 個,跳過 0 個 ==========
?
1>------ 已啟動生成: 項目: MyDLL, 配置: Debug Win32 ------
1> ?stdafx.cpp
1> ?testdll.cpp
1> ?MyDLL.cpp
1> ?正在生成代碼...
1> ? ? 正在創建庫 D:\Visual C++\工程\Libaray\MyDLL\Debug\MyDLL.lib 和對象 D:\Visual C++\工程\Libaray\MyDLL\Debug
標準C/C++的DLL編寫
DLL也就是動態鏈接庫,使用DLL編程的好處大家應當都知道了吧,可是怎么樣來作呢,今天我就來說說。
首先,你要確定你要導出那些個函數,然后你就在你要導出的函數名前加上下面一句話:
????// 輸出函數的前綴
????#define? DLL_EXPORT???extern?"C"?__declspec(?dllexport?)
????DLL_EXPORT?VOID??ExportFun()
??? {
??????? ...
??? }
? 是不是很簡單啊。如果你要導出整個類或者全局變量,你需要這樣做:
// 輸出類的前綴
#define? DLL_CLASS_EXPORT???__declspec(?dllexport?)
// 輸出全局變量的前綴
#define? DLL_GLOBAL_EXPORT???extern __declspec(?dllexport?)
?完成了這些以后,我們就要在主程序中調用這些個函數了,用下面的方法:
????HINSTANCE?hInst = NULL;
??? hInst = LoadLibrary("*.dll");????????//?你的DLL文件名
??? if?(!hInst)
??? {
??????? MessageBox(hWnd,"無法加載 *.Dll ","Error",MB_OK);
??? }
??? 還記得上面我聲明的那個ExportFun()函數嗎?我不能直接得到那個函數,但是可以把那個函數的地址取出來。其實函數地址使用起來和函數是一樣的。只不過,為了使用方便,需要定義一個函數指針的類型。如果要指向上面的那個ExportFun(),則它的函數指針的類型定義如下:
????typedef?void (CALLBACK* LPEXPORTFUN)(void)
??? 之后需要做的是聲明一個指針,然后得到DLL中ExportFun()的地址。GetProcAddress函數的第一個參數是之前得到的DLL的實例句柄,后面一個是DLL中那個函數的函數名。
???????
????LPEXPORTFUN?pFun = NULL;
????LPEXPORTFUN?pFun = (LPEXPORTFUN)GetProcAddress(hInst, "ExportFun");
好了,到這里已經就要大功告成了,還差最后一步,調用那個函數:
pFun();
大功告成!!
總結
以上是生活随笔為你收集整理的C++动态链接库的制作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mplayer安装记录 源码分析
- 下一篇: windows下使用pthread库