規則dll

分爲兩類:
 (1)靜態鏈接到MFC的規則DLL
    靜態鏈接到MFC的規則DLL與MFC庫靜態鏈接,將MFC庫的代碼直接生產在.dll文件中。在調用這種DLL的接口時,MFC使用DLL的資源。因此,在靜態鏈接到MFC的規則DLL中不需要進行模塊狀態的切換。
    使用這種方法生成的規則DLL其程序較大,也可能包含重複的代碼。
 (2)動態鏈接到MFC的規則DLL
   動態鏈接到MFC的規則DLL,可以和使用它的可執行文件同時鏈接到MFC DLL和MFC擴展DLL。在使用了MFC共享庫的時候,默認情況嚇,MFC使用主應用程序的資源句柄來加載資源模塊。這樣,當DLL和應用程序中存在相同ID的資源時(即所謂的資源重複問題),系統可能不能獲得正確的資源。因此,對於共享MFC DLL的規則DLL,我們必須進行模塊切換以使得MFC能夠找到正確的資源模塊。
   我們可以在project->setting->General中設置MFC規則DLL是靜態鏈接到MFC DLL還是動態鏈接到MFC DLL。
   
  
如果在所調用的DLL函數中使用了資源,則需要採用靜態鏈接的方式,才能正確調用DLL函數;如果要採用動態鏈接,需要進行模塊切換,模塊切換的方法稍後即講。
   如果所調用的DLL函數沒有使用任何資源,則即使採用動態鏈接的方式,也不需要進行模塊切換,和使用Win32 DLL一樣。
 
   模塊切換的方法:
(1)在DLL接口函數中使用:
     AFX_MANAGE_STAGE(AfxGetStaticModuleStatic());
     即在接口函數入口處加上該語句進行模塊狀態切換。
(2)在DLL接口函數中使用:
     AfxGetResourceHandle();
     AfxSetResourceHandle(HINSTANCE xxx);
     如下列代碼:
void ShowDlg(void)
{
    HINSTANCE save_hInstance=AfxGetResourceHandle();
    AfxSetResourceHandle(theApp.m_hInstance);

    CDialog dlg(IDD_DLL_DIALOG);
    dlg.DoModal;

    AfxSetResourceHandle(save_hInstance);
}
(3)由調用DLL的應用程序自身切換
   資源模塊的切換除了可以由DLL接口函數完成以外,由應用程序自身也能完成。

   //獲取exe模塊句柄
   HINSTANCE exe_hInstance = GetModuleHandle(NULL);
   //或者HINSTANCE exe_hInstance = AfxGetResourceHandle();

   //獲取DLL模塊句柄
   HINSTANCE dll_hInstance = GetModuleHandle("SharedDll.dll");

   //切換狀態
   AfxSetResourceHandle(dll_hInstance);

   //調用DLL函數
   ShowDlg();

   //資源模塊恢復
   AfxSetResourceHandle(exe_hInstance);



引言:在編寫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一般很少用,不多說。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章