NO IMAGE

——動態連結庫(dll)是包含共享函式庫的二進位制檔案,可以被多個應用程式同時使用。建立應用程式的可執行檔案時,不必將DLL連線到應用程式中,而是在執行時動態裝載DLL,裝載時DLL被對映到呼叫程序的地址空間中。通常我們在呼叫DLL時所需的DLL檔案必須位於以下三個目錄之一: 
——(1)Windows的系統目錄:/windows/system; 
——(2)DOS中path所指出的任何目錄; 
——(3)程式所在的目錄; 

一.動態連結庫(DLL)結構 
——DLL中定義有兩種函式:匯出函式(export function)和內部函式 
(internal function),匯出函式可以被其他模組呼叫,內部函式只能在DLL內部使用。我們在用C 定製DLL檔案時,需要編寫的就是包含匯出函式表的模組定義檔案(.DEF)和實現匯出函式功能的C 檔案。下面以Sample.dll為例介紹DEF檔案和實現檔案的結構: 

——1.模組定義檔案(.DEF)是由一個或者多個用於描述DLL屬性的模組語 
句組成的文字檔案,每個.DEF檔案至少必須包含以下模組定義語句: 
第一個語句必須是LIBRARY語句,指出DLL的名字。 
EXPORTS語句列出被匯出函式的名字。 
可以使用DESCRIPTION語句描述DLL的用途(此句可選)。 
“;”對一行進行註釋(可選) 
——2.實現檔案(.cpp檔案為例) 
——實現入口表函式的.cpp檔案中,包含DLL入口點處理的API函式和匯出 
函式的程式碼。 

二.建立Sample.dll 
——1.首先建立Sample.dll的工程,啟動VC 5.0按以下步驟生成DLL工程: 
在選單中選擇File/New/Project 
在工程列表中選擇Win32Dynamic-LinkLibrary 
在ProjectName中輸入工程名:Sample 
單擊Location右邊按鈕,選擇c:/sample目錄 
單擊OK完成,至此已建立了Sample.dll的工程檔案 
—-2.建立Sample.def檔案 
在選單中選擇File/New/TextFile 
輸入以下完程式碼後儲存檔名”Sample.def” 
;Sample.def 
;指出DLL的名字Sample,連結器將這個名 
字放到DLL匯入庫中 
LIBRARY Sample 
;定義匯出函式ShowMe()為例 
EXPORTS 
ShowMe 
;def檔案結束 
—- 3. 創 建Sample.cpp 
.在選單中選擇File/New/C  Source File項 
.輸入以下程式碼後儲存檔名”Sample.cpp” 
//Sample.cpp 
#include < windows.h > 
int ShowMe(void); 
//DllEntryPoint為DLL入口點函式, 
負責初試化並終止DLL 
BOOL WINAPI DllEntryPoint(HINSTANCE 
hDLL,DWORD dwReason,LPVOID Reserved) 

switch(dwReason) 

case DLL_PROCESS_ATTACH: 

break; 

case DLL_PROCESS_DETACH: 

break; 


return TRUE; 

int ShowMe(void) 

//蜂鳴器響一下 
MessageBeep((WORD)-1); 
MessageBox(“你好!”); 
return 1; 

——4.編譯DLL檔案——從 Build 選單中選擇 BuildSample.DLL, 產生 
Sample.DLL檔案,以後就可以隨時呼叫了。 

三.在應用程式中呼叫DLL檔案 
——在應用程式中要首先裝入DLL後才能呼叫匯出表中的函式,例如用MFC 
建立基於對話方塊的工程Test,並在對話方塊上放置”Load”按鈕,你就必須新增裝載程式碼。—-1.首先在TestDlg.cpp的首部新增變數設定程式碼: 
//設定全域性變數gLibSample用於儲存DLL控制代碼 
HINSTANCE gLibSample=NULL; 
//第二個變數ShowMe是指向DLL 
庫中ShowMe()函式的指標 
typedef int(* SHOWME)(void); 
SHOWME ShowMe; 
2.利用ClassWizard為”Load”按鈕新增裝載DLL的程式碼 
Void CTestDlg::OnLoadButton() 

//要新增的程式碼如下 
if(gLibMyDLL!=NULL) 

MessageBox(“The Sample.DLL has already been load.”); 
return; 

//裝載Sample.dll,未加路徑,將在三個預設路徑中尋找 
gLibSample=LoadLibrary(“SAMPLE.DLL”); 
//返回DLL中ShowMe()函式的地址 
ShowMe=(SHOWME) 
GetProcAddress(gLibSample,”ShowMe”); 
//程式碼新增完畢 

——3.只要DLL裝載成功,在應用程式中就可以直接呼叫ShowMe()函式,此時已完成了定製和呼叫DLL的全部過程。—-本程式在Windows95,VC 5.0中執行通過。 

生成一個Dll工程,然後將你的類和函式都新增進去,然後將要輸出的函式在該工程下的Translate.def檔案中將名字列出(注意,只要名字,不要括號、引數等);實際上和你的Exe工程沒有很大的區別。

如果你要用VB用,在Dll中定義你的函式的時候前面要加上“__stdcall”關鍵字,否則VB沒有辦法使用。

建議Dll中少用資源,只用函式比較好,如果要用資源,在資源載入時要做一個處理,不然會由於hModule的問題發生錯誤。

第十三章 動態連結庫

在Windows應用程式中使用動態連結庫有很多的好處。最主要的一點說是它可以使得多個應用程式共享一段程式碼,從而可以大幅度的降低應用程式的資源開銷,同時很縮小了應用程式的最終執行程式碼的大小。此外,通過使用動態連結庫,我們可以把一些常規的例程獨立出來,有效的避免了不必要的重複開發,並且,由於應用程式使用了動態連結的方式,還可以在不需重新改寫甚至編譯應用程式的基礎上更新應用程式的某些元件。

本章介紹Visual C  5.0中的動態連結庫的建立和使用。這些內容包括

為什麼要使用DLL 

不同的DLL型別之間的選擇 

在程式中建立和使用DLL 

使用DLL擴充套件MFC 

本章的重點在講述Win32 DLL和基於MFC的常規DLL,對於MFC擴充套件DLL僅給了一個很簡單的例子,更深入的資料請參閱Visual C 的聯機文件。

第一節 概述

動態連結庫(DLL,Dynamic-Link Library)也是一種可執行檔案,只不過它不能象普通的EXE檔案那樣可以直接執行,而是用來為其它可執行檔案(包括EXE檔案和其它DLL)提供共享函式庫。使用DLL的應用程式可以呼叫DLL中的匯出函式(imported function),不過在應用程式本身的執行程式碼中並不包含這些函式的執行程式碼,它們經過編譯和連結之後,獨立的儲存在DLL中。這是一種和過去常用的靜態連結不同的方式,使用靜態連結庫(static link library)的應用程式從函式庫得到所引用的函式的執行程式碼,然後把執行程式碼放進自身的執行檔案中,這樣,應用程式在執行時就可以不再需要靜態函式庫的支援。而使用DLL的應用程式只包括了用於從DLL中定位所引用的函式的資訊,而沒有函式具體實現,要等到程式執行時才從DLL中獲得函式的實現程式碼。顯然,使用了DLL的應用程式在執行時必須要有相應的DLL的支援。

DLL在Windows程式設計中得到了廣泛的應用。Windows應用程式的基礎:Windows API函式中的相當部分就是由一組DLL所提供的,這些DLL從安裝Windows起就存在於系統中了。儘管在前面的幾章中我們沒有明確的提到,但事實上我們早就在使用DLL進行程式設計了,只不過,所使用的DLL都是現成的,並且所有呼叫DLL的操作都由Visual C 的編譯和連結程式替我們完成了。

本章將詳細的介紹如何建立自己的DLL和如何使用自己建立的DLL進行程式設計。

為什麼要使用DLL呢?這當然是因為與傳統的靜態鏈庫相比,使用DLL有著更多的優勢:

使用DLL提供了一種共享資料和程式碼的方便途徑。並且,由於多個應用程式可以共享同一個DLL中的函式,因此,使用DLL可以顯著的節省磁碟空間。尤其對於Windows應用程式,有很多的操作都是“標準化”了的,如果使用傳統的靜態連結,每一個需要完成這些操作的應用程式都必須在自己的執行檔案中包括相同的執行程式碼,這不但使單個的應用程式變得更長,也浪費了磁碟空間。 

由於相同的原因,多個應用程式還可以同時共享DLL在記憶體中的同一份拷貝,這樣就有效的節省了應用程式所佔用的記憶體資源,減少了頻繁的記憶體交換,從而提高了應用程式的執行效率。 

由於DLL是獨立於可執行檔案的,因此,如果需要向DLL中增加新的函式或是增強現有函式的功能,只要原有函式的引數和返回值等屬性不變,那麼,所有使用該DLL的原有應用程式都可以在升級後的DLL的支援下執行,而不需要重新編譯。這就極大的方便了應用程式的升級和售後支援。 

DLL除了包括函式的執行程式碼以外,還可以只包括如圖示、點陣圖、字串和對話方塊之類的資源,因此可以把應用程式所使用的資源獨立出來做成DLL。對於一些常用的資源,把它們做到DLL中後,就可為多個應用程式所共享。 

便於建立多語言的應用程式。我們可以把多語言應用程式中所使用的與語言相關的函式做到DLL中,只要不同語言的應用程式所呼叫的函式都具有相同的介面,這樣就可以通過簡單地更換DLL來實現多語言支援。 

然而,使用DLL也有其不足之處。最典型的就是應用程式在執行時必須要有相應的DLL的支援。另外,使用DLL也增大了程式執行的開銷,好在這種額外的開銷對於大多數應用程式的影響並不明顯,我們也只是在某些對執行速度要求苛刻的特殊場合,才不得不考慮這一點。

Visual C  5.0支援多種DLL,包括:

非MFC DLL 

靜態連結到MFC的常規DLL 

動態連結到MFC的常規DLL 

MFC擴充套件DLL 

其中非MFC DLL(non-MFC DLL)內部不使用MFC,呼叫非MFC DLL提供的匯出函式的可執行程式可以使用MFC,也可以不使用MFC。一般來說,非MFC DLL的匯出函式都使用標準的C介面(standard C interface)。

其餘三種DLL的內部都使用了MFC。顧名思義,靜態連結到MFC的常規DLL(regular DLL statically linking to MFC)和動態連結到MFC的常規DLL(regular DLL dynamically linking to MFC)的區別在於一個使用的是MFC的靜態連結庫,而另一個使用的是MFC的DLL。這和一般的MFC應用程式的情況是很類似的。

MFC擴充套件DLL一般用來提供派生於MFC的可重用的類,以擴充套件已有的MFC類庫的功能。MFC擴充套件DLL使用MFC的動態連結版本。只有使用MFC動態連結的可執行程式(無論是EXE還是DLL)才能訪問MFC擴充套件DLL。MFC擴充套件DLL的另一個有用的功能是它可以在應用程式和它所載入的MFC擴充套件DLL之間傳遞MFC和MFC派生物件的指標。在其它情況下,這樣做是可能導致問題的。

注意: 

只有Visual C  5.0的專業版和企業版才支援到MFC的靜態連結。 

靜態連結到MFC的常規DLL過去的USRDLL有著相同的特性,同樣的,MFC擴充套件DLL和過去的AFXDLL有著相同的特性。在Visual C  5.0中已不再使用USRDLL和AFXDLL這兩個術語。 

如何選擇所應該使用的DLL的型別呢?我們可以從以下幾個方面來考慮:

相比使用了MFC的DLL而言,非MFC DLL顯得更為短小精悍。因此,如果DLL不需要使用MFC,那麼使用非MFC DLL是一個很好的選擇,它將顯著地節省磁碟和記憶體空間。同時,無論應用程式是否使用了MFC,都可以呼叫非MFC DLL中所匯出的函式。 

如果需要建立使用了MFC的DLL,並希望MFC和非MFC應用程式都能使用所建立的DLL,那麼可以選擇的範圍包括靜態連結到MFC的常規DLL和動態連結到MFC的常規DLL。動態連結到MFC的常規DLL比較短小,因此可以節省磁碟和記憶體,但是,在分發動態連結到MFC的常規DLL時,必須同時分發MFC的支援DLL,如MFCx0.DLL和MSVCRT.DLL等。而使用靜態連結到MFC的常規DLL則不存在這種問題。 

如果希望在DLL中實現從MFC派生的可重用的類,或者是希望在應用程式和DLL之間傳遞MFC的派生物件時,必須選擇MFC擴充套件DLL。 

第二節 建立和使用動態連結庫

本節以非MFC DLL為例來講解DLL的結構和匯出方法,並介紹建立和使用DLL的方法和步驟。

13.2.1 DLL的結構和匯出方式

DLL檔案和EXE檔案都屬於可執行檔案,不同的是DLL檔案包括了一個匯出表,匯出表中給出了可以從DLL中匯出的所有函式的名字。外部可執行程式只能訪問包括在DLL的匯出表中的函式,DLL中的其它函式是私有的,不能為外部可執行程式所訪問。

可以使用Visual C 提供的DUMPBIN實用程式(可以在DevStudio/VC/bin目錄下找到這個工具)來檢視一個DLL檔案的結構。舉一個例子,如果需要檢視DLL檔案msgbox.dll(我們將在本小節的後續內容中建立該DLL)的匯出表,可以在命令提示符下鍵入下面的命令:

>dumpbin /exports msgbox.dll

執行結果如下:

Microsoft (R) COFF Binary File Dumper Version 5.00.7022

Copyright (C) Microsoft Corp 1992-1997. All rights reserved.

Dump of file msgbox.dll

File Type: DLL

Section contains the following Exports for MSGBOX.dll

0 characteristics

351643C3 time date stamp Mon Mar 23 19:13:07 1998

0.00 version

1 ordinal base

1 number of functions

1 number of names

ordinal hint name

1 0 MsgBox (00001000)

Summary

7000 .data

1000 .idata

2000 .rdata

2000 .reloc

17000 .text

由上面的結果得知,msgbox.dll中僅包括了一個匯出函式MsgBox()。

注意: 

僅僅知道匯出函式的名稱並不足以從DLL中匯出該函式。若在應用程式中使用顯式連結(link explicitly),至少還應該知道匯出函式的返回值的型別以及所傳遞給匯出函式的引數的個數、順序和型別;若使用隱含連結(link implicitly),必須有包括匯出函式(或類)的定義的標頭檔案(.H檔案)和引入庫(import library,.LIB檔案),這些檔案是由DLL的建立者所提供的。關於顯式連結和隱含連結,將在本章的“13.2.2 連結應用程式到DLL”小節中講述。

從DLL中匯出函式有兩種方法:

在建立DLL時使用模組定義(module DEFinition,.DEF)檔案。 

在定義函式時使用關鍵字__declspec(dllexport)。 

下面我們通過一個簡單的例子來分別說明兩種方法的使用。在這個例子中,我們將建立一個只包括一個函式MsgBox()的DLL,函式MsgBox()用來顯示一個訊息框,它和Win32 API函式MessageBox()的功能是一樣,只不過在函式MsgBox()中,不需要指定訊息的父視窗,而且可以預設其它所有的引數。

(1) 使用模組定義檔案

模組定義檔案是一個文字檔案,它包括了一系列的模組語句,這些語句用來描述DLL的各種屬性,典型的,模組語句定義了DLL中所匯出的函式的名稱和順序值。

在講解模組定義檔案之前,我們先建立一個Win32 Dynamic-Link Library工程。

1. 在Microsoft Developer Studio中選擇File選單下的New命令,在Projects選項卡中選擇Win32 Dynamic-Link Library,併為工程取一個名字,如msgbox。單擊OK後,Visual C 建立一個Win32 DLL的空白工程,必須手動的將所需要的檔案新增到工程中。

2. 單擊Project選單下的Add To Project子選單下的New命令,在Files選項卡中選擇Text File,在File文字框中輸入DEF檔名,如msgbox.def。

3. 雙擊Workspace視窗的FileView選項卡中的msgbox.def節點,在msgbox.def檔案中輸入下面的內容:

LIBRARY MSGBOX

DESCRIPTION “一個DLL的簡單例子”

EXPORTS

MsgBox @1

在DEF檔案中的第一條語句必須是LIBRARY語句,該語句表明該DEF檔案屬於一個DLL,在LIBRARY之後是DLL的名稱,這個名稱在連結時將放到DLL的引入庫中。

EXPORTS語句下列出了DLL的所有匯出函式以及它們的順序值。函式的順序值不是必須的,在指定匯出函式的順序值時,我們在函式名後跟上一個@符號和一個數字,該數字即匯出函式的順序值。如果在DEF中指定了順序值,它必須不小於1,且不大於DLL中所有匯出函式的數目。

DESCRIPTION語句是可選的,它簡單的說明了DLL的用途。

4. 下一步是向工程中新增一個標頭檔案,它定義了DLL中的函式的返回值的型別和引數的個數、順序和型別。

單擊選單項Project|Add To Project|New…,在Files選項卡下選擇C/C  Header File,在File文字框中指定標頭檔案名,如msgbox.h(可以省略字尾名.h)。

在標頭檔案中輸入如下的內容:

#include 

extern “C” int MsgBox(

// 訊息框的文字

LPCTSTR lpText=”雖然這個例子有一些幼稚,但它工作得非常的好!”,

// 訊息框的標題

LPCTSTR lpCaption=”一個簡單的例子”,

// 訊息框的樣式

UINT uType=MB_OK);

請注意函式定義前的關鍵字extern “C”,這是由於我們使用了C 語言來開發DLL,為了使C語言模組能夠訪問該匯出函式,我們應該使用C連結來代替C 連結。否則,C 編譯器將使用C 的型別安全命名和呼叫協議,這在使用C呼叫該函式時就會遇上問題。在本例中並不需要考慮到這個問題,因為我們在開發DLL和應用程式時都是使用C ,但我們仍然強烈建議使用extern “C”,以保證在使用C編寫的程式呼叫該DLL的匯出函式不會遇上麻煩,在本章後面的內容中我們還會討論到這個問題。

5. 下面要做的事是向工程中新增一個C 原始檔,在該檔案中實現函式MsgBox()。

仿照上面的過程,單擊選單項Project|Add To Project|New…,在Files選項卡下選擇C  Source File,在File文字框中指定原始檔名,如msgbox.cpp。

在msgbox.cpp檔案中新增如下的程式碼:

#include “test1.h”

int MsgBox(LPCTSTR lpText,

LPCTSTR lpCaption,

UINT uType)

{

return MessageBox(NULL,lpText,lpCaption,uType);

}

編譯該工程,在Debug目錄下生成檔案msgbox.lib和msgbox.dll。

在“13.2.2 連結應用程式到DLL”小節中將講述如何使用在本節中所建立的DLL:msgbox.dll。

(2) 使用關鍵字__declspec(dllexport)

從DLL中匯出檔案的另一種方法是在定義函式時使用__declspec(dllexport)關鍵字。這種方法不需要使用DEF檔案。

仍使用前面的例子,在工程中刪除msgbox.def檔案,將msgbox.h檔案修改如下:

#include 

extern “C” __declspec(dllexport) int MsgBox(

// 訊息框的文字

LPCTSTR lpText=”雖然這個例子有一些幼稚,但它工作得非常的好!”,

// 訊息框的標題

LPCTSTR lpCaption=”一個簡單的例子”,

// 訊息框的樣式

UINT uType=MB_OK);

msgbox.cpp檔案並不需要做任何修改,重新編譯該工程,在Debug目錄下仍生成兩個檔案msgbox.lib和msgbox.dll。

在下一小節“13.2.2 連結應用程式到DLL”中講述瞭如何在應用程式中使用所建立的DLL:msgbox.dll的匯出函式MsgBox()。

使用__declspec(dllexport)從DLL中匯出類的語法如下:

class __declspec(dllexport) CDemoClass

{

}

注意: 

如果在使用__declspec(dllexport)的同時指定了呼叫協議關鍵字,則必須將__declspec(dllexport)關鍵字放在呼叫協議關鍵字的左邊。如: 

int __declspec(dllexport) __cdacl MyFunc(); 

在32位版本的Visual C 中,__declspec(dllexport)和__declspec(dllimport)代替了16版本中使用的__export關鍵字。因此,在將16位的DLL原始碼移植到Win32平臺時,需要把每一處__export替換為__declsped(dllexport)。 

如何從這兩種匯出函式的方法中作出選擇,可以從下面的幾個方面考慮:

如果需要使用匯出順序值(export ordinal value),那麼應該使用DEF檔案來匯出函式。只在使用DEF檔案匯出函式才能指定匯出函式的順序值。使用順序值的一個好處是當向DLL中新增新的函式時,只要新的匯出函式的順序值大於原有的匯出函式,就沒有必要重新連結使用隱含連結的應用程式。相反,如果使用__declspec(dllexport)來匯出函式,如果向DLL中新增了新的函式,使用隱含連結的應用程式有可以需要重新編譯和連結。 

使用DEF檔案來匯出函式,可以建立具有NONAME屬性的DLL。具有NONAME屬性的DLL在匯出表中僅包含了匯出函式的順序值,這種型別的DLL在包括有大量的匯出函式時,其檔案長度要小於通常的DLL。 

使用DEF檔案從C 檔案匯出函式,應該在定義函式時使用extern “C”或者在DEF檔案中指定匯出函式的decorated name。否則,由於編譯器所產生的decorated name是基於特定編譯器的,連結到該DLL的應用程式也必須使用建立DLL的同一版本的Visual C 來編譯和連結。 

由於使用__declspec(dllexport)關鍵字匯出函式不需要編寫DEF檔案,因此,如果編寫的DLL只供自己使用,使用__declspec(dllexport)較為簡單。 

 

注意: 

MFC本身使用了DEF檔案從MFCx0.DLL中匯出函式和類。 

13.2.2 連結應用程式到DLL

同樣,連結應用程式到DLL也有兩種方法:

隱含連結 

顯式連結 

隱含連結有時又稱為靜態載入。如果應用程式使用了隱含連結,作業系統在載入應用程式的同時載入應用程式所使用的DLL。顯式連結有時又稱為動態載入。使用動態載入的應用程式必須在程式碼中明確的載入所使用的DLL,並使用指標來呼叫DLL中的匯出函式,在使用完畢之後,應用程式必須解除安裝所使用的DLL。

同一個DLL可以被應用程式隱含連結,也可以被顯式連結,這取決於應用程式的目的和實現。

下面我們在分別講述兩種不同的連結方式之後再作對比。

(1) 使用隱含連結

在使用隱含連結除了需要相應的DLL檔案外,還必須具備如下的條件:

一個包括匯出的函式或C 類的標頭檔案 

一個輸入庫檔案(.LIB檔案) 

通常情況下,我們需要從DLL的提供者那裡得到上面的檔案。輸入庫檔案是在DLL檔案被連結時由連結程式生成的。

在“13.2.1 DLL的結構和匯出方式”中所建立的DLL:msgbox.dll所對應的標頭檔案msgbox.h如下:

#include 

extern “C” __declspec(dllimport) int MsgBox(

// 訊息框的文字

LPCTSTR lpText=”雖然這個例子有一些幼稚,但它工作得非常的好!”,

// 訊息框的標題

LPCTSTR lpCaption=”一個簡單的例子”,

// 訊息框的樣式

UINT uType=MB_OK);

需要注意的是,這個msgbox.h檔案和建立DLL時所使用msgbox.h是不同的,唯一的差別在於,建立DLL時的msgbox.h中使用的是__declspec(dllexport)關鍵字,而供應用程式所使用的msgbox.h中使用的是__declspec(dllimport)關鍵字。無論建立DLL時使用的是DEF檔案還是__declspec(dllexport)關鍵字,均可使用__declspec(dllimport)關鍵字從DLL中引入函式。引入函式時也可以省略__declspec(dllimport)關鍵字,但是使用它可以使編譯器生成效率更高的程式碼。

注意: 

如果需要引入的是DLL中的公用資料和物件,則必須使用__declspec(dllimport)關鍵字。 

現在使用Microsoft Developer Studio建立一個Win32 Application工程,命名為tester。向工程中新增一個C 原始檔,如tester.cpp。在tester.cpp檔案中輸入下面的程式碼:

#include “msgbox.h” // 應將msgbox.h檔案拷貝到工程tester的目錄下。

int WINAPI WinMain(HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow)

{

return MsgBox();

}

在上面的程式碼中,MsgBox()函式的所有引數都使用了預設值。