1. 程式人生 > >VC++動態連結庫(DLL)程式設計深入淺出(三)

VC++動態連結庫(DLL)程式設計深入淺出(三)

 第4節我們對非MFC DLL進行了介紹,這一節將詳細地講述MFC規則DLL的建立與使用技巧。

 另外,自從本文開始連載後,收到了一些讀者的e-mail。有的讀者提出了一些問題,筆者將在本文的最後一次連載中選取其中的典型問題進行解答。由於時間的關係,對於讀者朋友的來信,筆者暫時不能一一回復,還望海涵!由於筆者的水平有限,文中難免有錯誤和紕漏,也熱誠歡迎讀者朋友不吝指正!

  5. MFC規則DLL

  5.1 概述

  MFC規則DLL的概念體現在兩方面:

  (1) 它是MFC的

  “是MFC的”意味著可以在這種DLL的內部使用MFC;

  (2) 它是規則的

 “是規則的”意味著它不同於MFC擴充套件DLL,在MFC規則DLL的內部雖然可以使用MFC,但是其與應用程式的介面不能是MFC。而MFC擴充套件DLL與應用程式的介面可以是MFC,可以從MFC擴充套件DLL中匯出一個MFC類的派生類。

 Regular DLL能夠被所有支援DLL技術的語言所編寫的應用程式呼叫,當然也包括使用MFC的應用程式。在這種動態連線庫中,包含一個從CWinApp繼承下來的類,DllMain函式則由MFC自動提供。

  Regular DLL分為兩類:

  (1)靜態連結到MFC 的規則DLL

 靜態連結到MFC的規則DLL與MFC庫(包括MFC擴充套件 DLL)靜態連結,將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能夠找到正確的資源模板。

 我們可以在Visual C++中設定MFC規則DLL是靜態連結到MFC DLL還是動態連結到MFC DLL。如圖8,依次選擇Visual C++的project -> Settings -> General選單或選項,在Microsoft Foundation Classes中進行設定。

  圖8 設定動態/靜態連結MFC DLL

  5.2 MFC規則DLL的建立

  我們來一步步講述使用MFC嚮導建立MFC規則DLL的過程,首先新建一個project,如圖9,選擇project的型別為MFC AppWizard(dll)。點選OK進入如圖10所示的對話方塊。

  圖9 MFC DLL工程的建立

  圖10所示對話方塊中的1區選擇MFC DLL的類別。

 2區選擇是否支援automation(自動化)技術, automation 允許使用者在一個應用程式中操縱另外一個應用程式或元件。例如,我們可以在應用程式中利用 Microsoft Word 或Microsoft Excel的工具,而這種使用對使用者而言是透明的。自動化技術可以大大簡化和加快應用程式的開發。

 3區選擇是否支援Windows Sockets,當選擇此專案時,應用程式能在 TCP/IP 網路上進行通訊。 CWinApp派生類的InitInstance成員函式會初始化通訊端的支援,同時工程中的StdAfx.h檔案會自動include <AfxSock.h>標頭檔案。

  新增socket通訊支援後的InitInstance成員函式如下:


  4區選擇是否由MFC嚮導自動在原始碼中添加註釋,一般我們選擇“Yes,please”。

  圖10 MFC DLL的建立選項

 5.3 一個簡單的MFC規則DLL

  這個DLL的例子(屬於靜態連結到MFC 的規則DLL)中提供了一個如圖11所示的對話方塊。

  圖11 MFC規則DLL例子

 在DLL中新增對話方塊的方式與在MFC應用程式中是一樣的。

  在圖11所示DLL中的對話方塊的Hello按鈕上點選時將MessageBox一個“Hello,pconline的網友”對話方塊,下面是相關的檔案及原始碼,其中刪除了MFC嚮導自動生成的絕大多數註釋(下載本工程附件 ):

  第一組檔案:CWinApp繼承類的宣告與實現


  分析:

 在這一組檔案中定義了一個繼承自CWinApp的類CRegularDllApp,並同時定義了其的一個例項theApp。乍一看,您會以為它是一個MFC應用程式,因為MFC應用程式也包含這樣的在工程名後新增“App”組成類名的類(並繼承自CWinApp類),也定義了這個類的一個全域性例項theApp。

 我們知道,在MFC應用程式中CWinApp取代了SDK程式中WinMain的地位,SDK程式WinMain所完成的工作由CWinApp的三個函式完成:


 但是MFC規則DLL並不是MFC應用程式,它所繼承自CWinApp的類不包含訊息迴圈。這是因為,MFC規則DLL不包含CWinApp::Run機制,主訊息泵仍然由應用程式擁有。如果DLL 生成無模式對話方塊或有自己的主框架視窗,則應用程式的主訊息泵必須呼叫從DLL 匯出的函式來呼叫PreTranslateMessage成員函式。

  另外,MFC規則DLL與MFC 應用程式中一樣,需要將所有 DLL中元素的初始化放到InitInstance 成員函式中。

  第二組檔案 自定義對話方塊類宣告及實現(點選檢視附件 )

  分析:

 這一部分的程式設計與一般的應用程式根本沒有什麼不同,我們照樣可以利用MFC類嚮導來自動為對話方塊上的控制元件新增事件。MFC類嚮導照樣會生成類似ON_BN_CLICKED(IDC_HELLO_BUTTON, OnHelloButton)的訊息對映巨集。

  第三組檔案 DLL中的資原始檔


  分析:

 在MFC規則DLL中使用資源也與在MFC應用程式中使用資源沒有什麼不同,我們照樣可以用Visual C++的資源編輯工具進行資源的新增、刪除和屬性的更改。

  第四組檔案 MFC規則DLL介面函式


  分析:

  這個介面並不使用MFC,但是在其中卻可以呼叫MFC擴充套件類CdllDialog的函式,這體現了“規則”的概類。

  與非MFC DLL完全相同,我們可以使用__declspec(dllexport)宣告或在.def中引出的方式匯出MFC規則DLL中的介面。

  5.4 MFC規則DLL的呼叫

  筆者編寫了如圖12的對話方塊MFC程式(下載本工程附件 )來呼叫5.3節的MFC規則DLL,在這個程式的對話方塊上點選“呼叫DLL”按鈕時彈出5.3節MFC規則DLL中的對話方塊。

  圖12 MFC規則DLL的呼叫例子

 下面是“呼叫DLL”按鈕單擊事件的訊息處理函式:


  上述例子中給出的是顯示呼叫的方式,可以看出,其呼叫方式與第4節中非MFC DLL的呼叫方式沒有什麼不同。

 我們照樣可以在EXE程式中隱式呼叫MFC規則DLL,只需要將DLL工程生成的.lib檔案和.dll檔案拷入當前工程所在的目錄,並在RegularDllCallDlg.cpp檔案(圖12所示對話方塊類的實現檔案)的頂部新增:


  5.5 共享MFC 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的模組控制代碼。

  這次我們建立一個動態連結到MFC DLL的規則DLL(下載本工程附件 ),在其中包含如圖13的對話方塊。

  圖13 DLL中的對話方塊

  另外,在與這個DLL相同的工作區中生成一個基於對話方塊的MFC程式,其對話方塊與圖12完全一樣。但是在此工程中我們另外添加了一個如圖14的對話方塊。

  圖14 EXE中的對話方塊

  圖13和圖14中的對話方塊除了caption不同(以示區別)以外,其它的都相同。

 尤其值得特別注意,在DLL和EXE中我們對圖13和圖14的對話方塊使用了相同的資源ID=2000,在DLL和EXE工程的resource.h中分別有如下的巨集:


  我們以為單擊“呼叫DLL”會彈出如圖13所示DLL中的對話方塊,可是可怕的事情發生了,我們看到是圖14所示EXE中的對話方塊!

  驚訝?

 產生這個問題的根源在於應用程式與MFC規則DLL共享MFC DLL(或MFC擴充套件DLL)的程式總是預設使用EXE的資源,我們必須進行資源模組控制代碼的切換,其實現方法有三:

  方法一 在DLL介面函式中使用:

  AFX_MANAGE_STATE(AfxGetStaticModuleState());

  我們將DLL中的介面函式ShowDlg改為:


 這次我們再點選EXE程式中的“呼叫DLL”按鈕,彈出的是DLL中的如圖13的對話方塊!嘿嘿,彈出了正確的對話方塊資源。 AfxGetStaticModuleState是一個函式,其原型為:

  AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState( );

 該函式的功能是在棧上(這意味著其作用域是區域性的)建立一個AFX_MODULE_STATE類(模組全域性資料也就是模組狀態)的例項,對其進行設定,並將其指標pModuleState返回。

  AFX_MODULE_STATE類的原型如下:


 AFX_MODULE_STATE類利用其建構函式和解構函式進行儲存模組狀態現場及恢復現場的工作,類似彙編中call指令對pc指標和sp暫存器的儲存與恢復、中斷服務程式的中斷現場壓棧與恢復以及作業系統執行緒排程的任務控制塊儲存與恢復。

  許多看似不著邊際的知識點居然有驚人的相似!

  AFX_MANAGE_STATE是一個巨集,其原型為:

  AFX_MANAGE_STATE( AFX_MODULE_STATE* pModuleState )

  該巨集用於將pModuleState設定為當前的有效模組狀態。當離開該巨集的作用域時(也就離開了pModuleState所指向棧上物件的作用域),先前的模組狀態將由AFX_MODULE_STATE的解構函式恢復。

 方法二 在DLL介面函式中使用:

AfxGetResourceHandle();
AfxSetResourceHandle(HINSTANCE xxx);

 AfxGetResourceHandle用於獲取當前資源模組控制代碼,而AfxSetResourceHandle則用於設定程式目前要使用的資源模組控制代碼。

  我們將DLL中的介面函式ShowDlg改為:


  通過AfxGetResourceHandle和AfxSetResourceHandle的合理變更,我們能夠靈活地設定程式的資源模組控制代碼,而方法一則只能在DLL介面函式退出的時候才會恢復模組控制代碼。方法二則不同,如果將ShowDlg改為:


  在應用程式主對話方塊的“呼叫DLL”按鈕上點選,將看到兩個對話方塊,相繼為DLL中的對話方塊(圖13)和EXE中的對話方塊(圖14)。

 方法三 由應用程式自身切換

 資源模組的切換除了可以由DLL介面函式完成以外,由應用程式自身也能完成(下載本工程附件 )。

  現在我們把DLL中的介面函式改為最簡單的:


  方法三中的Win32函式GetModuleHandle可以根據DLL的檔名獲取DLL的模組控制代碼。如果需要得到EXE模組的控制代碼,則應呼叫帶有Null引數的GetModuleHandle。

   方法三與方法二的不同在於方法三是在應用程式中利用AfxGetResourceHandle和AfxSetResourceHandle進行資源模組 控制代碼切換的。同樣地,在應用程式主對話方塊的“呼叫DLL”按鈕上點選,也將看到兩個對話方塊,相繼為DLL中的對話方塊(圖13)和EXE中的對話方塊(圖 14)。