DLL
的優點
簡單的說,dll
有以下幾個優點
:
1
)節省內存
.同一個軟件模塊
,若是源碼重用
,則會在不同可執行程序
中編譯
,同時運行這些exe
時,會在內存
中重復加載
這些模塊的二進制碼
.
如果使用dll
,則只在內存
中加載一次
,所有使用該dll
的進程會共享此塊內存
(當然,每個進程
會復制
一份的dll
中的全局變量
).
2
)不需編譯
的軟件系統升級
,若一個軟件系統
使用了dll
,則改變該dll
(函數名
不變)時,系統升級
只需要切換此dll
即可,不需要重新編譯整個系統
.
3
)多種語言可使用Dll
庫,如用c編寫的dll
可在vb
中調用.DLL
還做得很不夠,因此在dll
的基礎上發明了COM
技術,更好的解決
了一系列問題
.
最簡單的dll
最簡單的dll
并不比c的helloworld
難,只要一個DllMain
函數即可,包含objbase.h
頭文件(支持COM
技術的一個頭文件
).
若該頭文件
名字難記,則用windows.h
也可以.源碼如下:dll_nolib.cpp
#include <objbase.h>
#include <iostream.h>
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{HANDLE g_hModule;switch(dwReason){case DLL_PROCESS_ATTACH:cout<<"Dll is attached!"<<endl;g_hModule = (HINSTANCE)hModule;break;case DLL_PROCESS_DETACH:cout<<"Dll is detached!"<<endl;g_hModule=NULL;break;}return true;
}
其中DllMain
是每個dll
的入口函數,如同c的主
函數一樣.DllMain
帶三個參數,hModule
表示本dll
的實例句柄
,dwReason
表示dll
當前所處的狀態,如DLL_PROCESS_ATTACH
表示dll
剛剛被加載
到一個進程
中,DLL_PROCESS_DETACH
表示剛剛從一個進程
中卸載dll
.
當然還有表示加載到線程
中和從線程中卸載
的狀態,這里省略.最后參數
是一個保存參數
.
如上,在一個進程
中加載dll
時,dll
打印"Dllisattached!"
語句;當從進程
中卸載dll
時,打印"Dllisdetached!"
語句.
編譯dll
需要以下兩條命令
:
cl /c dll_nolib.cpp
這條命令
會按obj
文件編譯cpp
,若不使用/c參數
,則cl
還會繼續鏈接obj
為exe
,但是這里是一個dll
,沒有主
函數,因此會報錯
.不要緊,繼續使用鏈接命令
.
Link /dll dll_nolib.obj
這條命令
會生成dll_nolib.dll
.
加載DLL
(顯式調用)
一般有兩個方式
使用dll
,顯式調用和隱式調用
.這里首先介紹顯式調用
.編寫一個客戶程序
:dll_nolib_client.cpp
#include <windows.h>
#include <iostream.h>
int main(void)
{//加載的`dll`HINSTANCE hinst=::LoadLibrary("dll_nolib.dll");if (NULL != hinst){cout<<"dll loaded!"<<endl;}return 0;
}
注意,調用dll
使用LoadLibrary
函數,它的參數
就是dll
的路徑和名字
,返回值
是dll
的句柄
.使用如下命令
編譯鏈接客戶:
Cl dll_nolib_client.cpp
并執行dll_nolib_client.exe
,得到如下結果:
Dllisattached!
dllloaded!
Dllisdetached!
以上結果表明客戶已加載dll
.但是這樣僅可在內存加載dll
,不能找到dll
中的函數
.
使用dumpbin
命令查看DLL
中的函數
Dumpbin
命令可查看一個dll
中的輸出函數符號名
,輸入如下命令
:
Dumpbin -exports dll_nolib.dll
查看
發現dll_nolib.dll
并沒有輸出函數
.
如何在dll
中定義輸出函數
總體來說
有兩個方法
,一個
是添加
一個def
定義文件,在此文件
中定義dll
中要輸出的函數
;第二個
是在源碼
中,待輸出的函數
前加上__declspec(dllexport)
關鍵字.
Def
文件
首先寫一個帶輸出函數
的dll
,源碼如下:dll_def.cpp
#include <objbase.h>
#include <iostream.h>
void FuncInDll (void)
{cout<<"FuncInDll is called!"<<endl;
}
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{HANDLE g_hModule;switch(dwReason){case DLL_PROCESS_ATTACH:g_hModule = (HINSTANCE)hModule;break;case DLL_PROCESS_DETACH:g_hModule=NULL;break;}return TRUE;
}
該dll
的def
文件如下:dll_def.def
;
//`dll_def`模塊定義文件
;
LIBRARY dll_def.dll
DESCRIPTION '(c)2007-2009 Wang Xuebin'
EXPORTSFuncInDll @1 PRIVATE
def
的語法很簡單
,首先是庫
關鍵字,指定dll
的名字;然后一個可選
的描述
關鍵字.
最后是導出
關鍵字,后面寫上dll
中所有要輸出的函數名或變量名
,然后接上@及依次編號的數字
(從1到N
),最后接上修飾符
.
用如下命令
編譯鏈接帶def
文件的dll
:
Cl /c dll_def.cpp
Link /dll dll_def.obj /def:dll_def.def
再調用dumpbin
查看生成的dll_def.dll
:
Dumpbin -exports dll_def.dll
得到結果
.
觀察這一行
.
1000001000FuncInDll
會發現該dll
輸出了FuncInDll
函數.
顯式調用DLL
中的函數
寫一個dll_def.dll
的客戶程序:dll_def_client.cpp
#include <windows.h>
#include <iostream.h>
int main(void)
{//定義一個函數指針typedef void (* DLLWITHLIB )(void);//定義一個函數指針變量DLLWITHLIB pfFuncInDll = NULL;//加載`dll`HINSTANCE hinst=::LoadLibrary("dll_def.dll");if (NULL != hinst){cout<<"dll loaded!"<<endl;}//找到`dll`的`FuncInDll`函數pfFuncInDll = (DLLWITHLIB)GetProcAddress(hinst, "FuncInDll");//調用`dll`里的函數if (NULL != pfFuncInDll){(*pfFuncInDll)();}return 0;
}
有兩個地方
值得注意,第一是定義和使用函數指針
;第二是使用GetProcAddress
,來查找dll
中的函數地址
.
第一個參數
是DLL
的句柄
,即LoadLibrary
返回的句柄
,第二個參數
是dll
中的函數名
,即dumpbin
中輸出的函數名
.
注意,這里的函數名
指的是編譯后的函數名
,不一定等于dll
源碼中的函數名
.
編譯鏈接
該客戶程序
,執行得到:
dllloaded!
FuncInDlliscalled!
即客戶成功調用
了dll
中的FuncInDll
函數.
__declspec(dllexport)
為每個dll
寫def
顯得很麻煩,當前def
使用已比較少了,更多的是在源碼
中,使用__declspec(dllexport)
定義dll
的輸出函數
.
Dll
寫法同上
,去掉def
文件,并在每個要輸出的函數
前面加上__declspec(dllexport)
聲明,如:
__declspec(dllexport) void FuncInDll (void)
這里提供一個dll
的dll_withlib.cpp
源程序,然后編譯鏈接
.鏈接
時不需要指定/DEF
:參數,直接加/DLL
參數即可,
Cl /c dll_withlib.cpp
Link /dll dll_withlib.obj
然后使用dumpbin
命令查看,得到:
1 0 00001000 FuncInDll@@YAXXZ
可知編譯
后的函數名
為FuncInDll@@YAXXZ
.
可用extern"C"
指令來命令c++
編譯器按c編譯器
的方式來命名該函數
.如下:
extern "C" __declspec(dllexport) void FuncInDll (void)
dumpbin
命令結果:
1000001000 FuncInDll
這樣,顯式調用
時只需查找函數名
為FuncInDll
的函數
即可成功.
隱式調用DLL
顯式調用
顯得非常復雜,每次都要LoadLibrary
,并且每個函數
都必須使用GetProcAddress
來得到函數指針
,對大量使用dll
函數的客戶
是個困擾.
而隱式調用
可像使用c函數庫
一樣使用dll
中的函數,非常方便快捷
.
下面是一個隱式調用
的示例:dll
包含兩個文件dll_withlibAndH.cpp
和dll_withlibAndH.h
.
代碼如下:dll_withlibAndH.h
extern "C" __declspec(dllexport) void FuncInDll (void);
//dll_withlibAndH.cpp
#include <objbase.h>
#include <iostream.h>
#include "dll_withLibAndH.h"//看到沒有,這就是增加的頭文件
extern "C" __declspec(dllexport) void FuncInDll (void)
{cout<<"FuncInDll is called!"<<endl;
}
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{HANDLE g_hModule;switch(dwReason){case DLL_PROCESS_ATTACH:g_hModule = (HINSTANCE)hModule;break;case DLL_PROCESS_DETACH:g_hModule=NULL;break;}return TRUE;
}
編譯鏈接命令
:
Cl /c dll_withlibAndH.cpp
Link /dll dll_withlibAndH.obj
在隱式調用
時,需要在客戶
中引入頭文件
,并在鏈接
時指明dll
對應的lib
文件(dll
只要有函數輸出
,則鏈接
時會產生一個與dll
同名的lib
文件)位置和名
.
然后如同調用api
函數庫中的函數
一樣調用dll
中的函數,不需要顯式
的LoadLibrary
和GetProcAddress
.使用最方便
.
客戶代碼
如下:dll_withlibAndH_client.cpp
#include "dll_withLibAndH.h"
//注意路徑,加載`dll`的或`項目`|設置|`鏈接`設置里
#pragma comment(lib,"dll_withLibAndH.lib")
int main(void)
{FuncInDll();//只要這樣就可調用`dll`里的函數了return 0;
}
__declspec(dllexport)
和__declspec(dllimport
)配對使用
事實上不使用extern"C"
是可行
的,這時會按c++
的符號串編譯函數
,如(FuncInDll@@YAXH@Z,FuncInDll@@YAXXZ
),當客戶
也是c++
時,也能正確
的隱式調用
.
這時要考慮一種情況
:若DLL1.CPP
是源,DLL2.CPP
使用了DLL1
中的函數
,但同時DLL2
也是一個DLL
,也要輸出
一些函數供Client.CPP
使用.
則在DLL2
中如何聲明
所有的,既包含了從DLL1
中引入的函數
,還包括自己要輸出的函數
的函數.此時就需要
同時使用__declspec(dllexport)
和__declspec(dllimport)
了.
前者用來裝飾
本dll
中的輸出函數
,后者用來裝飾
從其它dll
中引入的函數
.
所有的源碼
包括DLL1.H,DLL1.CPP,DLL2.H,DLL2.CPP,Client.cpp
.
值得關注的是DLL1
和DLL2
中都使用的一個編碼方法
,見DLL2.H
#ifdef DLL_DLL2_EXPORTS
#define DLL_DLL2_API __declspec(dllexport)
#else
#define DLL_DLL2_API __declspec(dllimport)
#endif
DLL_DLL2_API void FuncInDll2(void);
DLL_DLL2_API void FuncInDll2(int);
在頭文件
中這樣定義DLL_DLL2_EXPORTS
和DLL_DLL2_API
宏,可確保DLL
端的函數用__declspec(dllexport)
裝飾,而客戶的函數
用__declspec(dllimport)
裝飾.
當然,記得在編譯dll
時加上參數/D "DLL_DLL2_EXPORTS"
,或干脆就在dll
的cpp
文件第一行
加上#define DLL_DLL2_EXPORTS
.
DLL
中的全局變量和對象
解決了重載函數
的問題,則dll
中的全局變量和對象
都不是問題了,只是有一點語法
注意.如源碼所示:dll_object.h
#ifdef DLL_OBJECT_EXPORTS
#define DLL_OBJECT_API __declspec(dllexport)
#else
#define DLL_OBJECT_API __declspec(dllimport)
#endif
DLL_OBJECT_API void FuncInDll(void);
extern DLL_OBJECT_API int g_nDll;
class DLL_OBJECT_API CDll_Object {
public:CDll_Object(void);show(void);//`待辦`:在此處添加你的方法.
};
Cpp
文件dll_object.cpp
如下:
#define DLL_OBJECT_EXPORTS
#include <objbase.h>
#include <iostream.h>
#include "dll_object.h"
DLL_OBJECT_API void FuncInDll(void)
{cout<<"FuncInDll is called!"<<endl;
}
DLL_OBJECT_API int g_nDll = 9;
CDll_Object::CDll_Object()
{cout<<"ctor of CDll_Object"<<endl;
}
CDll_Object::show()
{cout<<"function show in class CDll_Object"<<endl;
}
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{HANDLE g_hModule;switch(dwReason){case DLL_PROCESS_ATTACH:g_hModule = (HINSTANCE)hModule;break;case DLL_PROCESS_DETACH:g_hModule=NULL;break;}return TRUE;
}
編譯鏈接
完后Dumpbin
一下,可見輸出了5個符號
:
1 0 00001040 0CDll_Object@@QAE@XZ2 1 00001000 4CDll_Object@@QAEAAV0@ABV0@@Z3 2 00001020 FuncInDll@@YAXXZ4 3 00008040 g_nDll@@3HA5 4 00001069 show@CDll_Object@@QAEHXZ
它們分別代表CDll_Object
類,類的構造器
,FuncInDll
函數,g_nDll
全局變量和類的顯示
成員函數.下面是客戶代碼
:dll_object_client.cpp
#include "dll_object.h"
#include <iostream.h>
//注意路徑,加載`dll`的或`項目`|設置|`鏈接`設置里
#pragma comment(lib,"dll_object.lib")
int main(void)
{cout<<"call dll"<<endl;cout<<"call function in dll"<<endl;FuncInDll();//只要這樣就可調用`dll`里的函數了cout<<"global var in dll g_nDll ="<<g_nDll<<endl;cout<<"call member function of class CDll_Object in dll"<<endl;CDll_Object obj;obj.show();return 0;
}
運行該客戶可見:
calldll
callfunctionindll
FuncInDlliscalled!
globalvarindllg_nDll=9
callmemberfunctionofclassCDll_Objectindll
ctorofCDll_Object
functionshowinclassCDll_Object
可知,客戶
成功的訪問了dll
中的全局變量
,并創建了dll
中定義的C++
對象,還調用
了該對象的成員函數
.