VC 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()函數的所有參數都使用了缺省值。
總結
- 上一篇: 广搜--(搜索的第一道题)图像有用区域
- 下一篇: vc2008中使用boost库