天天看點

靜态.共享的規則MFC DLL/MFC擴充DLL詳解

引言:在編寫MFC程式的時候,通常需要編寫dll庫以供其他程式調用。關于MFC dll的相關知識很多很雜,這裡特酷吧結合自己學習中遇到的問題專門整理了一些MFC dll的基礎知識。本部分共上下兩篇文章,本文為上篇,MFC DLL應用程式類型分為以下三種:

(1)使用共享MFC DLL的規則DLL

(2)帶靜态連結MFC的規則DLL

(3)MFC擴充DLL

下面重點解釋一下這些DLL的含義差別:

一,規則DLL

首先談談所謂的"規則DLL":"規則DLL"是由"Regular DLL"翻譯而來的。它實際上展現出來兩方面的本質:

(1)該DLL是基于MFC的;

(2)該DLL是"規則"的,它不同于"MFC擴充DLL",在規則DLL中内部雖然是可以使用MFC,但是規則DLL的接口應該不能是基于MFC的。而MFC擴充DLL與應用程式接口可以是MFC,可以從MFC擴充dll中導出一個MFC的派生類。

一般情況下我們都會使用規則的dll,因為"規則DLL"能夠提供給所有支援dll技術的語言的調用接口。在規則DLL中,有一個CWinApp繼承下來的類,dll入口函數則是由MFC自動提供,被MFC封裝。此類DLL程式從CWinApp派生,但是沒有消息循環:

下面再詳細說明"規則DLL"的兩個分類:

(1)使用共享MFC DLL的規則DLL;

"共享MFC DLL的規則DLL"是在編寫基于MFC的DLL程式時,編譯後該DLL中不包含MFC的庫,比如MFC42.dll,而是由dll運作的時候動态連結到MFC的庫。這種方式比"帶靜态連結MFC的規則DLL"編譯的稍微大些。是以,當釋出"共享MFC DLL的規則DLL"dll時,如果對方的機器上沒有安裝MFC的庫,那麼該dll是運作不了的,除非你将MFC的庫也一塊給他,"共享MFC DLL的規則DLL"和"帶靜态連結MFC的規則DLL"最大的差別就是在使用MFC的方法上。

正是由于"共享MFC DLL的規則DLL"的這些特點,導緻在系統加載該類dll時,會涉及到多個dll的加載,那麼如果當DLL和應用程式中存在相同ID的資源時,系統不能正确分辨程式員的意圖,是以,使用"共享MFC DLL的規則DLL"我們需要通過子產品切換來找到正确的資源子產品,并進行對應的操作。

"共享MFC DLL的規則DLL"的子產品切換:

再說明這個問題之前,我們先來了解下DLL的内部運作機制:

應用程式程序本身及其調用的每個DLL子產品都具有一個全局唯一的HINSTANCE句柄,它們代表了DLL或EXE子產品在程序虛拟空間中的起始位址。程序本身的子產品句柄一般為0x400000,而DLL子產品的預設句柄為0x10000000。如果程式同時加載了多個DLL,則每個DLL子產品都會有不同的HINSTANCE。應用程式在加載DLL時對其進行了重定位。

共享MFC DLL(或MFC擴充DLL)的規則DLL涉及到HINSTANCE句柄問題,HINSTANCE句柄對于加載資源特别重要。EXE和DLL都有其自己的資源,而且這些資源的ID可能重複,應用程式需要通過資源子產品的切換來找到正确的資源。如果應用程式需要來自于DLL的資源,就應将資源子產品句柄指定為DLL的子產品句柄;如果需要EXE檔案中包含的資源,就應将資源子產品句柄指定為EXE的子產品句柄。

為了完成子產品切換,在所有從DLL輸出的函數中都應該使用以下語句開頭:

AFX_MANAGE_STATE(AfxGetStaticModuleState( ))

此句話用來正确切換MFC的子產品狀态。說明:

其功能是在棧上(這意味着其作用域是局部的)建立一個AFX_MODULE_STATE類的執行個體,并将其指針pModuleState傳回。

AFX_MODULE_STATE類利用其構造函數和析構函數進行存儲子產品狀态現場及恢複現場的工作。

該宏用于将pModuleState設定為目前的有效子產品狀态。當離開該宏的作用域時(也就離開了pModuleState所指棧上對象的作用域),先前的子產品狀态将由類AFX_MODULE_STATE的析構函數恢複。

(2)帶靜态連結MFC的規則DLL;

這個不多講,是将MFC dll編譯到自身内部的DLL類型,對比"使用共享MFC DLL的規則DLL"不難了解;

(3)規則DLL中的調用約定和名稱修飾:

調用約定是程式向函數傳遞參數,以及接收傳回值的标準約定,它是為了實作函數調用而建立的一種标準的協定,這種協定規定了該語言的函數中的參數傳遞方法,參數是否可變以及由誰來處理堆棧等問題,不同的語言定義了不同的調用約定。

在C++中,為了允許操作符重載和函數重載,C++編譯器往往按照某種規則改寫每一個入口點的符号名,以便允許同一個名字(具有不同的參數類型或者是不同的作用域)有多個用法,而不會打破現有的基于C的連結器.這項技術通常被稱為名稱改編(Name Mangling)或者名稱修飾(Name Decoration).許多C++編譯器廠商選擇了自己的名稱修飾方案.

是以,為了使其它語言編寫的子產品(如Visual Basic應用程式、Pascal或Fortran的應用程式等)可以調用C/C++編寫的DLL的函數,必須使用正确的調用約定來導出函數,并且不要讓編譯器對要導出的函數進行任何名稱修飾.

調用約定用來處理決定函數參數傳送時入棧和出棧的順序(由調用者還是被調用者把參數彈出棧),以及編譯器用來識别函數名稱的名稱修飾約定等問題.在Microsoft VC++ 6.0中定義了下面幾種調用約定:

1,__cdecl

__cdecl是C/C++和MFC程式預設使用的調用約定,也可以在函數聲明時加上__cdecl關鍵字來手工指定.采用__cdecl約定時,函數參數按照從右到左的順序入棧,并且由調用函數者把參數彈出棧以清理堆棧.是以,實作可變參數的函數隻能使用該調用約定.由于每一個使用__cdecl約定的函數都要包含清理堆棧的代碼,是以産生的可執行檔案大小會比較大.__cdecl可以寫成_cdecl.

2、__stdcall

__stdcall調用約定用于調用Win32 API函數.采用__stdcal約定時,函數參數按照從右到左的順序入棧,被調用的函數在傳回前清理傳送參數的棧,函數參數個數固定.由于函數體本身知道傳進來的參數個數,是以被調用的函數可以在傳回前用一條ret n指令直接清理傳遞參數的堆棧.__stdcall可以寫成_stdcall.

3、__fastcall

__fastcall約定用于對性能要求非常高的場合.__fastcall約定将函數的從左邊開始的兩個大小不大于4個位元組(DWORD)的參數分别放在ECX和EDX寄存器,其餘的參數仍舊自右向左壓棧傳送,被調用的函數在傳回前清理傳送參數的堆棧.__fastcall可以寫成_fastcall.

最後說明:關鍵字__cdecl、__stdcall和__fastcall可以直接加在要輸出的函數前,也可以在編譯環境的Setting...->C/C++->Code Generation項選擇.它們對應的指令行參數分别為/Gd、/Gz和/Gr.預設狀态為/Gd,即__cdecl.當加在輸出函數前的關鍵字與編譯環境中的選擇不同時,直接加在輸出函數前的關鍵字有效.

(4)規則DLL的其他幾點說明:

1,DLL程式入口點是DllMain

DllMain負責初始化(Initialization)和結束(Termination)工作,每當一個新的程序或者該程序的新的線程通路DLL時,或者通路DLL的每一個程序或者線程不再使用DLL或者結束時,都會調用DllMain。但是,使用TerminateProcess或TerminateThread結束程序或者線程,不會調用DllMain。

DllMain的函數原型符合DllEntryPoint的要求,有如下結構:

OOL WINAPI DllMain (HANDLE hInst,

ULONG 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;

其中:

參數1是子產品句柄;

參數2是指調用DllMain的類别,四種取值:新的程序要通路DLL;新的線程要通路DLL;一個程序不再使用DLL(Detach from DLL);一個線程不再使用DLL(Detach from DLL)。參數3保留。

如果程式員不指定DllMain,則編譯器使用它自己的DllMain,該函數僅僅傳回TRUE

規則DLL應用程式使用了MFC的DllMain,它将調用DLL程式的應用程式對象(從CWinApp派生)的InitInstance函數和ExitInstance函數。

擴充DLL必須實作自己的DllMain。

當然必須注意MFC dll已經隐藏了DllMain。它的初始化稱許實在一個基于CWinApp類的InitInstance()函數。

DLL定義的全局變量可以被調用程序通路;DLL可以通路調用程序的全局資料。使用同一DLL的每一個程序都有自己的DLL全局變量執行個體。如果多個線程并發通路同一變量,則需要使用同步機制;對一個DLL的變量,如果希望每個使用DLL的線程都有自己的值,則應該使用線程局部存儲(TLS,Thread Local Strorage)。

DLL輸出函數的方法:

(1)在子產品定義檔案的EXPORT部分指定要輸入的函數或者變量。

(2)使用MFC提供的修飾符号_declspec(dllexport);

要輸出整個的類,對類使用_declspec(_dllexpot);要輸出類的成員函數,則對該函數使用_declspec(_dllexport)。如:

class AFX_EXT_CLASS CTextDoc : public CDocument

{}

extern "C" AFX_EXT_API void WINAPI InitMYDLL();

(3)對連結程式LINK指定/EXPORT指令行參數,輸出有關函數。

特酷吧推薦使用第二種。

二,MFC擴充DLL

MFC擴充DLL與MFC規則DLL的相同點在于在兩種DLL的内部都可以使用MFC類庫,其不同點在于MFC擴充DLL與應用程式的接口可以是MFC的。MFC擴充DLL的含義在于它是MFC的擴充,其主要功能是實作從現有MFC庫類中派生出可重用的類。MFC擴充DLL使用MFC 動态連結庫版本,是以隻有用共享MFC版本生成的MFC可執行檔案(應用程式或規則DLL)才能使用MFC擴充DLL。此類dll一般很少用,不多說。

繼續閱讀