Windows程式設計__孫鑫C++Lesson19《動態連結程式庫》

來源:互聯網
上載者:User

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的類時就要建立擴充庫。
本節小結:
掌握動態連結程式庫的兩種調用方式,動態連結函數的匯出包括類的匯出,尤其要注意動態連結程式庫中函數的呼叫慣例,這中錯誤應該盡量避免。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.