基於Visual C++的DLL編程實現

來源:互聯網
上載者:User
一、前言
  自從微軟推出16位的Windows作業系統起,此後每種版本的Windows作業系統都非常依賴於動態連結程式庫(DLL)中的函數和資料,實際上Windows作業系統中幾乎所有的內容都由DLL以一種或另外一種形式代表著,例如顯示的字型和表徵圖儲存在GDI DLL中、顯示Windows案頭和處理使用者的輸入所需要的代碼被儲存在一個User DLL中、Windows編程所需要的大量的API函數也被包含在Kernel DLL中。

  在Windows作業系統中使用DLL有很多優點,最主要的一點是多個應用程式、甚至是不同語言編寫的應用程式可以共用一個DLL檔案,真正實現了資源"共用",大大縮小了應用程式的執行代碼,更加有效利用了記憶體;使用DLL的另一個優點是DLL檔案作為一個單獨的程式模組,封裝性、獨立性好,在軟體需要升級的時候,開發人員只需要修改相應的DLL檔案就可以了,而且,當DLL中的函數改變後,只要不是參數的改變,程式碼並不需要重新編譯。這在編程時十分有用,大大提高了軟體開發和維護的效率。

  既然DLL那麼重要,所以搞清楚什麼是DLL、如何在Windows作業系統中開發使用DLL是程式開發人員不得不解決的一個問題。本文針對這些問題,通過一個簡單的例子,即在一個DLL中實現比較最大、最小整數這兩個簡單函數,全面地解析了在Visual C++編譯環境下編程實現DLL的過程,文章中所用到的程式碼在Windows98系統、Visual C++6.0編譯環境下通過。

  二、DLL的概念

  DLL是建立在客戶/伺服器通訊的概念上,包含若干函數、類或資源的庫檔案,函數和資料被儲存在一個DLL(伺服器)上並由一個或多個客戶匯出而使用,這些客戶可以是應用程式或者是其它的DLL。DLL庫不同於靜態庫,在靜態庫情況下,函數和資料被編譯進一個二進位檔案(通常副檔名為*.LIB),Visual C++的編譯器在處理常式代碼時將從靜態庫中恢複這些函數和資料並把他們和應用程式中的其他模組組合在一起產生可執行檔。這個過程稱為"靜態連結",此時因為應用程式所需的全部內容都是從庫中複製了出來,所以靜態庫本身並不需要與可執行檔一起發行。

  在動態庫的情況下,有兩個檔案,一個是引入庫(.LIB)檔案,一個是DLL檔案,引入庫檔案包含被DLL匯出的函數的名稱和位置,DLL包含實際的函數和資料,應用程式使用LIB檔案連結到所需要使用的DLL檔案,庫中的函數和資料並不複製到可執行檔中,因此在應用程式的可執行檔中,存放的不是被調用的函數代碼,而是DLL中所要調用的函數的記憶體位址,這樣當一個或多個應用程式運行是再把程式碼和被調用的函數代碼連結起來,從而節省了記憶體資源。從上面的說明可以看出,DLL和.LIB檔案必須隨應用程式一起發行,否則應用程式將會產生錯誤。

  微軟的Visual C++支援三種DLL,它們分別是Non-MFC Dll(非MFC動態庫)、Regular Dll(常規DLL)、Extension Dll(擴充DLL)。Non-MFC DLL指的是不用MFC的類庫結構,直接用C語言寫的DLL,其匯出的函數是標準的C介面,能被非MFC或MFC編寫的應用程式所調用。Regular DLL:和下述的Extension Dlls一樣,是用MFC類庫編寫的,它的一個明顯的特點是在源檔案裡有一個繼承CWinApp的類(注意:此類DLL雖然從CWinApp派生,但沒有訊息迴圈),被匯出的函數是C函數、C++類或者C++成員函數(注意不要把術語C++類與MFC的微軟基礎C++類相混淆),調用常規DLL的應用程式不必是MFC應用程式,只要是能調用類C函數的應用程式就可以,它們可以是在Visual C++、Dephi、Visual Basic、Borland C等編譯環境下利用DLL開發應用程式。

  常規DLL又可細分成靜態連結到MFC和動態連結到MFC上的,這兩種常規DLL的區別將在下面介紹。與常規DLL相比,使用擴充DLL用於匯出增強MFC基礎類的函數或子類,用這種類型的動態連結程式庫,可以用來輸出一個從MFC所繼承下來的類。

  擴充DLL是使用MFC的動態連結版本所建立的,並且它只被用MFC類庫所編寫的應用程式所調用。例如你已經建立了一個從MFC的CtoolBar類的衍生類別用於建立一個新的工具列,為了匯出這個類,你必須把它放到一個MFC擴充的DLL中。擴充DLL 和常規DLL不一樣,它沒有一個從CWinApp繼承而來的類的對象,所以,開發人員必須在DLL中的DllMain函數添加初始化代碼和結束代碼。

三、動態連結程式庫的建立

  在Visual C++6.0開發環境下,開啟FileNewProject選項,可以選擇Win32 Dynamic-Link Library或MFC AppWizard[dll]來以不同的方式來建立Non-MFC Dll、Regular Dll、Extension Dll等不同種類的動態連結程式庫。

  1. Win32 Dynamic-Link Library方式建立Non-MFC DLL動態連結程式庫

  每一個DLL必須有一個進入點,這就象我們用C編寫的應用程式一樣,必須有一個WINMAIN函數一樣。在Non-MFC DLL中DllMain是一個預設的入口函數,你不需要編寫自己的DLL入口函數,用這個預設的入口函數就能使動態連結程式庫被調用時得到正確的初始化。如果應用程式的DLL需要分配額外的記憶體或資源時,或者說需要對每個進程或線程初始化和清除操作時,需要在相應的DLL工程的.CPP檔案中對DllMain()函數按照下面的格式書寫。
 

 

BOOL APIENTRY DllMain(HANDLE 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:
.......
}
return TRUE;
}

  參數中,hMoudle是動態庫被調用時所傳遞來的一個指向自己的控制代碼(實際上,它是指向_DGROUP段的一個選擇符);ul_reason_for_call是一個說明動態庫被調原因的標誌,當進程或線程裝入或卸載動態連結程式庫的時候,作業系統調用入口函數,並說明動態連結程式庫被調用的原因,它所有的可能值為:DLL_PROCESS_ATTACH: 進程被調用、DLL_THREAD_ATTACH: 線程被調用、DLL_PROCESS_DETACH: 進程被停止、DLL_THREAD_DETACH: 線程被停止;lpReserved為保留參數。到此為止,DLL的入口函數已經寫了,剩下部分的實現也不難,你可以在DLL工程中加入你所想要輸出的函數或變數了。

  我們已經知道DLL是包含若干個函數的庫檔案,應用程式使用DLL中的函數之前,應該先匯出這些函數,以便供給應用程式使用。要匯出這些函數有兩種方法,一是在定義函數時使用匯出關鍵字_declspec(dllexport),另外一種方法是在建立DLL檔案時使用模組定義檔案.Def。需要讀者注意的是在使用第一種方法的時候,不能使用DEF檔案。下面通過兩個例子來說明如何使用這兩種方法建立DLL檔案。

  1)使用匯出函數關鍵字_declspec(dllexport)建立MyDll.dll,該動態連結程式庫中有兩個函數,分別用來實現得到兩個數的最大和最小數。在MyDll.h和MyDLL.cpp檔案中分別輸入如下原代碼:
 

//MyDLL.h
extern "C" _declspec(dllexport) int Max(int a, int b);
extern "C" _declspec(dllexport) int Min(int a, int b);
//MyDll.cpp
#i nclude
#i nclude"MyDll.h"
int Max(int a, int b)
{
if(a>=b)return a;
else
return b;
}
int Min(int a, int b)
{
if(a>=b)return b;
else
return a;
}

  該動態連結程式庫編譯成功後,開啟MyDll工程中的debug目錄,可以看到MyDll.dll、MyDll.lib兩個檔案。LIB檔案中包含DLL檔案名稱和DLL檔案中的函數名等,該LIB檔案只是對應該DLL檔案的"映像檔案",與DLL檔案中,LIB檔案的長度要小的多,在進行隱式連結DLL時要用到它。讀者可能已經注意到在MyDll.h中有關鍵字"extern C",它可以使其他程式設計語言訪問你編寫的DLL中的函數。

  2)用.def檔案建立工程MyDll

  為了用.def檔案建立DLL,請先刪除上個例子建立的工程中的MyDll.h檔案,保留MyDll.cpp並在該檔案頭刪除#i nclude MyDll.h語句,同時往該工程中加入一個文字檔,命名為MyDll.def,再在該檔案中加入如下代碼:

LIBRARY MyDll
EXPORTS
Max
Min

  其中LIBRARY語句說明該def檔案是屬於相應DLL的,EXPORTS語句下列出要匯出的函數名稱。我們可以在.def檔案中的匯出函數後加@n,如Max@1,Min@2,表示要匯出的函數順序號,在進行顯式連時可以用到它。該DLL編譯成功後,開啟工程中的Debug目錄,同樣也會看到MyDll.dll和MyDll.lib檔案。

  2.MFC AppWizard[dll]方式產生常規/擴充DLL

  在MFC AppWizard[dll]下產生DLL檔案又有三種方式,在建立DLL是,要根據實際情況選擇建立DLL的方式。一種是常規DLL靜態連結到MFC,另一種是常規DLL動態連結到MFC。兩者的區別是:前者使用的是MFC的靜態連結庫,產生的DLL檔案長度大,一般不使用這種方式,後者使用MFC的動態連結程式庫,產生的DLL檔案長度小;動態連結到MFC的規則DLL所有輸出的函數應該以如下語句開始:
 

AFX_MANAGE_STATE(AfxGetStaticModuleState( )) //此語句用來正確地切換MFC模組狀態

  最後一種是MFC擴充DLL,這種DLL特點是用來建立MFC的衍生類別,Dll只被用MFC類庫所編寫的應用程式所調用。前面我們已經介紹過,Extension DLLs 和Regular DLLs不一樣,它沒有一個從CWinApp繼承而來的類的對象,編譯器預設了一個DLL入口函數DLLMain()作為對DLL的初始化,你可以在此函數中實現初始化,代碼如下:
 

BOOL WINAPI APIENTRY DLLMain(HINSTANCE hinstDll,DWORD reason ,LPVOID flmpload)
{
switch(reason)
{
……………//初始化代碼;
}
return true;
}

  參數hinstDll存放DLL的控制代碼,參數reason指明調用函數的原因,lpReserved是一個被系統所保留的參數。對於隱式連結是一個非零值,對於顯式連結值是零。

  在MFC下建立DLL檔案,會自動產生def檔案架構,其它與建立傳統的Non-MFC DLL沒有什麼區別,只要在相應的標頭檔寫入關鍵字_declspec(dllexport)函數類型和函數名等,或在產生的def檔案中EXPORTS下輸入函數名就可以了。需要注意的是在向其它開發人員分發MFC擴充DLL 時,不要忘記提供描述DLL中類的標頭檔以及相應的.LIB檔案和DLL本身,此後開發人員就能充分利用你開發的擴充DLL了。

四、動態連結程式庫DLL的連結

  應用程式使用DLL可以採用兩種方式:一種是隱式連結,另一種是顯式連結。在使用DLL之前首先要知道DLL中函數的結構資訊。Visual C++6.0在VCin目錄下提供了一個名為Dumpbin.exe的小程式,用它可以查看DLL檔案中的函數結構。另外,Windows系統將遵循下面的搜尋順序來定位DLL: 1.包含EXE檔案的目錄,2.進程的當前工作目錄, 3.Windows系統目錄, 4.Windows目錄,5.列在Path環境變數中的一系列目錄。

  1.隱式連結

  隱式連結就是在程式開始執行時就將DLL檔案載入到應用程式當中。實現隱式連結很容易,只要將匯入函數關鍵字_declspec(dllimport)函數名等寫到應用程式相應的標頭檔中就可以了。下面的例子通過隱式連結調用MyDll.dll庫中的Min函數。首先產生一個項目為TestDll,在DllTest.h、DllTest.cpp檔案中分別輸入如下代碼:
 

 

//Dlltest.h
#pragma comment(lib,"MyDll.lib")
extern "C"_declspec(dllimport) int Max(int a,int b);
extern "C"_declspec(dllimport) int Min(int a,int b);
//TestDll.cpp
#i nclude
#i nclude"Dlltest.h"
void main()
{int a;
a=min(8,10)
printf("比較的結果為%d ",a);
}
 

  在建立DllTest.exe檔案之前,要先將MyDll.dll和MyDll.lib拷貝到當前工程所在的目錄下面,也可以拷貝到windows的System目錄下。如果DLL使用的是def檔案,要刪除TestDll.h檔案中關鍵字extern "C"。TestDll.h檔案中的關鍵字Progam commit是要Visual C+的編譯器在link時,連結到MyDll.lib檔案,當然,開發人員也可以不使用#pragma comment(lib,"MyDll.lib")語句,而直接在工程的Setting->Link頁的Object/Moduls欄填入MyDll.lib既可。

  2.顯式連結

  顯式連結是應用程式在執行過程中隨時可以載入DLL檔案,也可以隨時卸載DLL檔案,這是隱式連結所無法作到的,所以顯式連結具有更好的靈活性,對於解釋性語言更為合適。不過實現顯式連結要麻煩一些。在應用程式中用LoadLibrary或MFC提供的AfxLoadLibrary顯式的將自己所做的動態連結程式庫調進來,動態連結程式庫的檔案名稱即是上述兩個函數的參數,此後再用GetProcAddress()擷取想要引入的函數。自此,你就可以象使用如同在應用程式自訂的函數一樣來調用此引入函數了。在應用程式退出之前,應該用FreeLibrary或MFC提供的AfxFreeLibrary釋放動態連結程式庫。下面是通過顯式連結調用DLL中的Max函數的例子。
 

#i nclude
#i nclude
void main(void)
{
typedef int(*pMax)(int a,int b);
typedef int(*pMin)(int a,int b);
HINSTANCE hDLL;
PMax Max
HDLL=LoadLibrary("MyDll.dll");//載入動態連結程式庫MyDll.dll檔案;
Max=(pMax)GetProcAddress(hDLL,"Max");
A=Max(5,8);
Printf("比較的結果為%d ",a);
FreeLibrary(hDLL);//卸載MyDll.dll檔案;
}

  在上例中使用類型定義關鍵字typedef,定義指向和DLL中相同的函數原型指標,然後通過LoadLibray()將DLL載入到當前的應用程式中並返回當前DLL檔案的控制代碼,然後通過GetProcAddress()函數擷取匯入到應用程式中的函數指標,函數調用完畢後,使用FreeLibrary()卸載DLL檔案。在編譯器之前,首先要將DLL檔案拷貝到工程所在的目錄或Windows系統目錄下。

  使用顯式連結應用程式編譯時間不需要使用相應的Lib檔案。另外,使用GetProcAddress()函數時,可以利用MAKEINTRESOURCE()函數直接使用DLL中函數出現的順序號,如將GetProcAddress(hDLL,"Min")改為GetProcAddress(hDLL, MAKEINTRESOURCE(2))(函數Min()在DLL中的順序號是2),這樣調用DLL中的函數速度很快,但是要記住函數的使用序號,否則會發生錯誤。

  本文通過通俗易懂的方式,全面介紹了動態連結程式庫的概念、動態連結程式庫的建立和動態連結程式庫的連結,並給出個簡單明了的例子,相信讀者看了本文後,能夠建立自己的動態連結程式庫並應用到後續的軟體開發當中去了,當然,讀者要熟練操作DLL,還需要在大量的實踐中不斷摸索,希望本文能起到拋磚引玉的作用。
      以上為轉載,原文地址:http://hegeng2000.bokee.com/3807138.html
      本人是在VS2005環境下實現的,都可以實現。這裡對原文有幾處錯誤需要改正:
      1.在顯示連結時,首先要包含windows.h標頭檔,因為這是windows程式開發必要的。所用到的HINSTANCE,LoadLibrary,FreeLibrary等都是系統關鍵字;
      2.同樣在顯示連結中,調用LoadLibrary函數中,傳入的參數必須是LPCWSTR即Unicode字串指標,所以必須轉換例如LoadLibrary(_T("dll檔案名稱"))。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.