Windows程式設計__孫鑫C++Lesson19《動態連結程式庫》
本節要點:
1.動態連結程式庫與靜態連結庫
2.查看動態連結程式庫中匯出函數和程式中匯入函數
3.動態連結程式庫載入的的隱式串連
4.動態連結程式庫函數的外部提供
5.動態連結程式庫匯出函數的名字改編
6.整個類的匯出及類的部分函數的匯出
7.動態連結程式庫的明確式載入和卸載
8.利用MFC AppWizard(dll)建立Dll工程時選項的說明
//***********************************************************************************************
1.動態連結程式庫與靜態連結庫
關於動態連結程式庫和靜態連結庫介紹如所示:
2.查看動態連結程式庫中匯出函數(前提是函數已經匯出了)
利用VC 提供的程式dumpbin.exe來查看或者利用其工具Depends來查看。
利用dumpbin.exe:
運行cmd.exe,首先建立環境資訊,在cmd.exe中運行 "C:\Program Files\Microsoft Visual Studio\VC98\Bin\VCVARS32.BAT"
然後使用dumpbin的命令來查看.
查看匯出情況,命令如,dumpbin -exports Dll1.dll
查看匯入情況,命令如,dumpbin -imports DllTest.exe >1.txt(這裡表示將結果顯示到1.txt檔案中)
查看Dll的匯出或者exe的匯入資訊過程如所示:
3.動態連結程式庫載入的的隱式串連
隱式串連時注意包含動態連結程式庫的Lib檔案,Lib檔案包含了函數的說明。
隱式串連動態連結程式庫需要聲明函數原型,方法有三種,如下:
第一種:extern int add(int a,int b);
extern int sustract(int a,int b);
第二種: _declspec (dllimport)int add(int a,int b);
_declspec (dllimport)int sustract(int a,int b);//外部函數 串連時要引入庫檔案 這種方式效率更高 動態連結程式庫時都應該使用這種方式
第三種:包含標頭檔法 使編程更加工程化
串連時錯誤:
Linking...
DllTestDlg.obj : error LNK2001: unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z)
DllTestDlg.obj : error LNK2001: unresolved external symbol "int __cdecl sustract(int,int)" (?sustract@@YAHHH@Z)
解決將Dll1.lib檔案拷貝到DllTest目錄下.Dll1.lib包含了Dll1.dll包含了動態連結程式庫中包含的函數名和變數名。
串連lib檔案並且將Dll1.dll拷貝到目前的目錄下(否則出現執行階段錯誤)。
4.動態連結程式庫函數的外部提供
怎麼讓其他開發人員瞭解我們動態連結程式庫的匯出函數,可以通過提供標頭檔形式給別的開發人員。
這裡有一個技巧,使用宏定義可以讓標頭檔中的函式宣告既為本程式服務,又為其他開發人員服務。使用宏定義匯出函數形式如下:
//Dll1.h
#ifdef DLL1_API
#else
#define DLL1_API _declspec (dllimport)
#endif
DLL1_API int add(int a,int b);
DLL1_API int sustract(int a,int b);
//Dll1.cpp
#define DLL1_API _declspec (dllexport)
#include "Dll1.h"
int add(int a,int b)
{
return a+b;
}
int sustract(int a,int b)
{
return a-b;
}
這裡應該體會到宏定義的展開過程。在本程式中因為定義了DLL1_API 所以包含標頭檔時預設不做什麼,
那麼DLL1_API 就被展開為_declspec (dllexport)表明這是匯出函數;
當其他程式包含標頭檔"Dll1.h" 時,只要不定義DLL1_API ,則宏展開為_declspec (dllimport),表示匯入函數。利用宏定義減輕了函式宣告的負擔。
5.動態連結程式庫匯出函數的名字改編
不同的C++編譯器可能採用不同規則進行名字改編,也就是你在編寫Dll檔案中的函數名稱對外部來說發生了改變,名字改編可能引發程式串連時錯誤。
例如動態連結程式庫編寫如下:
//Dll3.cpp
_declspec (dllexport) int add(int a ,int b)//匯出函數時編譯器會做名字改編 add改編為 ?add@@YAHHH@Z
{
return a+b;
}
//測試調用代碼如下:
//明確式載入和卸載動態連結程式庫
void CDllTestDlg::OnBtnAdd()
{
// TODO: Add your control notification handler code here
//動態載入連結庫
HINSTANCE hinst;
hinst=LoadLibrary("Dll3.dll");//路徑名正確填寫
//擷取動態連結程式庫中函數地址
typedef int (*AddProc)(int a,int b);//函數指標類型 在需要時定義指標變數接受函數地址
//AddProc pfnAdd=(AddProc)GetProcAddress(hinst,"add");//Dll3發生了名字改編 調用無效
//使用改編的名字?add@@YAHHH@Z成功調用
//AddProc pfnAdd=(AddProc)GetProcAddress(hinst,"?add@@YAHHH@Z");
AddProc pfnAdd=(AddProc)GetProcAddress(hinst,MAKEINTRESOURCE(1));//使用函數編號成功
//注意函數指標類型轉換
if(!pfnAdd)
{
MessageBox("擷取函數地址失敗!");
return;
}
CString msg;
msg.Format("5+3=%d",pfnAdd(5,3));
MessageBox(msg);
FreeLibrary(hinst);//卸載動態連結程式庫
}
匯出函數名字時使用extern "C"可以解決上述問題。使用extern "C"解決了C++和c語言之間問題,但是Extern C 不能用來到處一個類的成員函數 只能到處全域函數。
//Dll1.h
#ifdef DLL1_API
#else
#define DLL1_API extern "C" _declspec (dllimport)
#endif
DLL1_API int add(int a,int b);
DLL1_API int sustract(int a,int b);
//Dll1.dll
#define DLL1_API extern "C" _declspec (dllexport)
#include "Dll1.h"
int add(int a,int b)
{
return a+b;
}
int sustract(int a,int b)
{
return a-b;
}
表示未使用extern "C"時發生的名字改編:
表示使用extern "C"時未發生名字改編:
:
但是使用Extern C仍然存在問題,當函式宣告為_stdcall,形式時仍然會發生名字改編改編後如所示:
為瞭解決這個問題可以使用,模組定義檔案def檔案。呼叫慣例改變為標準調用時不會發生名字改編。
例如程式中使用模組定義檔案如下:
//***********************************************************************************************
//Dll2.def
LIBRARY Dll2
EXPORTS
add //函數名
sustract
//***********************************************************************************************
//Dll2.cpp
int _stdcall add(int x,int y)
{
return x+y;
}
int _stdcall sustract(int x,int y)
{
return x-y;
}
使用模組定義檔案即使使用了_stdcall,動態連結程式庫的匯出函數名仍然不會改變。
但是訪問函數時需要指明呼叫慣例,否則會出錯。以下程式碼範例了聲明為標準調用時必須在調用時也使用標準調用。
//*************************************************************************************************
//明確式載入和卸載動態連結程式庫
void CDllTestDlg::OnBtnAdd()
{
// TODO: Add your control notification handler code here
//動態載入連結庫
HINSTANCE hinst;
hinst=LoadLibrary("Dll2.dll");//路徑名正確填寫
//擷取動態連結程式庫中函數地址
//typedef int (*AddProc)(int a,int b);//函數指標類型在動態連結程式庫中函式宣告為標準調用時,採用這種形式錯誤
typedef int (_stdcall *AddProc)(int a,int b);// 標準調用的函數指標類型
AddProc pfnAdd=(AddProc)GetProcAddress(hinst,"add");//定義了一個函數指標變數 利用這個指標調用函數
//注意函數指標類型轉換
if(!pfnAdd)
{
MessageBox("擷取函數地址失敗!");
return;
}
CString msg;
msg.Format("5+3=%d",pfnAdd(5,3));
MessageBox(msg);
FreeLibrary(hinst);//卸載動態連結程式庫
}
//***********************************************************************************************
如果不定義為標準調用的函數指標類型,則發生錯誤,錯誤的提示如所示:
總之注意一點:定義為標準呼叫慣例使用時也要標準調用函數。
關於呼叫慣例有好幾種,這點留待以後學習,暫時未作討論。
6.整個類的匯出及類的部分函數的匯出
(1)匯出整個類
//***********************************************************************************************
//匯出整個類
#ifdef DLL1_API
#else
#define DLL1_API _declspec (dllimport)
#endif
class DLL1_API Point
{
public:
void Output(int x,int y);
void Test();
};
void Point::Output(int x,int y)
{
HWND hWnd=GetForegroundWindow();//獲得調用者進程的當前正在使用的控制代碼
HDC hDC=GetDC(hWnd);//擷取DC控制代碼
char buf[20];
memset(buf,0,sizeof(buf));
sprintf(buf,"x=%d,y=%d",x,y);
TextOut(hDC,0,0,buf,strlen(buf));//輸出資訊
ReleaseDC(hWnd,hDC);
}
//Test函數僅為匯出查看 不完成實際功能
void Point::Test()
{
}
匯出整個類函數如所示:
//***********************************************************************************************
(2)匯出類的部分函數
//***********************************************************************************************
#ifdef DLL1_API
#else
#define DLL1_API _declspec (dllimport)
#endif
//匯出部分函數
class Point
{
public:
void DLL1_API Output(int x,int y);//匯出類中的一個成員函數
void Test();//不會被匯出
};
匯出部分函數如所示:
//***********************************************************************************************
匯出類的使用代碼如下:
void CDllTestDlg::OnBtnPt()
{
// TODO: Add your control notification handler code here
Point pt;
pt.Output(3,5);
}
運行效果如所示:
//***********************************************************************************************
7.動態連結程式庫的明確式載入和卸載
(1)
載入動態連結程式庫使函數LoadLibrary,其函數原型為:
FARPROC GetProcAddress(
HMODULE hModule, // handle to DLL module
LPCSTR lpProcName // function name
);
卸載動態連結程式庫使用函數FreeLibrary。
FreeLibrary 減少動態連結程式庫的引用計數 減為零時模組將被調用進程的地址控制項卸載。
隱式載入的動態連結程式庫,整個程式中都可以使用,但是動態連結程式庫通過隱式串連方式載入到記憶體,會加大進程啟動時間,而且肯能很多動態連結程式庫中的函數沒有引用;
而動態載入動態連結程式庫可以避免這一點。動態載入的不會在dumpbin中出現。
(2)動態載入的連結庫中函數擷取使用GetProcAddress函數。注意函數函數指標轉換.否則引發如下錯誤:
error C2440: 'initializing' : cannot convert from 'int (__stdcall *)(void)' to 'int (__cdecl *)(int,int)'
This conversion requires a reinterpret_cast, a C-style cast or function-style cast
明確式載入和卸載動態連結程式庫範例程式碼如下:
//***********************************************************************************************
//明確式載入和卸載動態連結程式庫
void CDllTestDlg::OnBtnAdd()
{
// TODO: Add your control notification handler code here
//動態載入連結庫
HINSTANCE hinst;
hinst=LoadLibrary("Dll2.dll");//路徑名正確填寫
//擷取動態連結程式庫中函數地址
typedef int (*AddProc)(int a,int b);//函數指標類型 在需要時定義指標變數接受函數地址
AddProc pfnAdd=(AddProc)GetProcAddress(hinst,"add");//定義了一個函數指標變數 利用這個指標調用函數
//注意函數指標類型轉換
if(!pfnAdd)
{
MessageBox("擷取函數地址失敗!");
return;
}
CString msg;
msg.Format("5+3=%d",pfnAdd(5,3));
MessageBox(msg);
FreeLibrary(hinst);//卸載動態連結程式庫
}
//***********************************************************************************************
8.利用MFC AppWizard(dll)建立Dll工程時選項的說明
常規連結庫 MFC 靜態連結庫 只需要發布你的動態連結程式庫檔案其中包含了MFC連結庫
常規連結庫 MFC 共用的連結庫 發布動態連結程式庫時要確保使用者機器上安裝有MFC的連結庫 否則你的連結庫就就不能被正確載入
擴充的連結庫 MFC 共用的連結庫 與常規的區別在於匯出MFC的類時就要建立擴充庫。
本節小結:
掌握動態連結程式庫的兩種調用方式,動態連結函數的匯出包括類的匯出,尤其要注意動態連結程式庫中函數的呼叫慣例,這中錯誤應該盡量避免。