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

來源:互聯網
上載者:User

標籤:朋友   clu   分享圖片   button   export   時間   選擇   pre   article   

前面我們對非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成員函數如下:

BOOL CRegularDllSocketApp::InitInstance(){if (!AfxSocketInit()){AfxMessageBox(IDP_SOCKETS_INIT_FAILED);return FALSE;}return TRUE;}

 

 圖10 MFC DLL的建立選項

  5.3 一個簡單的MFC規則DLL

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

 圖11 MFC規則DLL例子

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

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

  第一組檔案:CWinApp繼承類的聲明與實現

// RegularDll.h : main header file for the REGULARDLL DLL#if !defined(AFX_REGULARDLL_H__3E9CB22B_588B_4388_B778_B3416ADB79B3__INCLUDED_)#define AFX_REGULARDLL_H__3E9CB22B_588B_4388_B778_B3416ADB79B3__INCLUDED_#if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000#ifndef __AFXWIN_H__#error include ‘stdafx.h‘ before including this file for PCH#endif#include "resource.h" // main symbolsclass CRegularDllApp : public CWinApp{public:CRegularDllApp();DECLARE_MESSAGE_MAP()};#endif// RegularDll.cpp : Defines the initialization routines for the DLL.#include "stdafx.h"#include "RegularDll.h"#ifdef _DEBUG#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE[] = __FILE__;#endifBEGIN_MESSAGE_MAP(CRegularDllApp, CWinApp)END_MESSAGE_MAP()/////////////////////////////////////////////////////////////////////////////// CRegularDllApp constructionCRegularDllApp::CRegularDllApp(){}/////////////////////////////////////////////////////////////////////////////// The one and only CRegularDllApp objectCRegularDllApp theApp;

 

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

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

virtual BOOL InitApplication( );
virtual BOOL InitInstance( );
virtual BOOL Run( ); //傳說中MFC程式的“活水源頭”

  但是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中的資源檔

//{{NO_DEPENDENCIES}}// Microsoft Developer Studio generated include file.// Used by RegularDll.rc//#define IDD_DLL_DIALOG 1000#define IDC_HELLO_BUTTON 1000

 

分析:

 

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

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

#include "StdAfx.h"#include "DllDialog.h"extern "C" __declspec(dllexport) void ShowDlg(void){CDllDialog dllDialog;dllDialog.DoModal();}

 

  分析:

  這個介面並不使用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”按鈕單擊事件的訊息處理函數:

void CRegularDllCallDlg::OnCalldllButton(){typedef void (*lpFun)(void);HINSTANCE hDll; //DLL控制代碼hDll = LoadLibrary("RegularDll.dll");if (NULL==hDll){MessageBox("DLL載入失敗");}lpFun addFun; //函數指標lpFun pShowDlg = (lpFun)GetProcAddress(hDll,"ShowDlg");if (NULL==pShowDlg){MessageBox("DLL中函數尋找失敗");}pShowDlg();}

 

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

#pragma comment(lib,"RegularDll.lib")void ShowDlg(void);

  並將void CRegularDllCallDlg::OnCalldllButton() 改為:

void CRegularDllCallDlg::OnCalldllButton(){ShowDlg();}

  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中的對話方塊

 

 

圖14 EXE中的對話方塊

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

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

//DLL中對話方塊的ID#define IDD_DLL_DIALOG 2000//EXE中對話方塊的ID#define IDD_EXE_DIALOG 2000

 

  與5.3節靜態連結MFC DLL的規則DLL相同,我們還是在規則DLL中定義介面函數ShowDlg,原型如下:

#include "StdAfx.h"#include "SharedDll.h"void ShowDlg(void){CDialog dlg(IDD_DLL_DIALOG); //開啟ID為2000的對話方塊dlg.DoModal();}

 

  而為應用工程主對話方塊的“調用DLL”的單擊事件添加如下訊息處理函數:

void CSharedDllCallDlg::OnCalldllButton(){ShowDlg();}

 

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

  驚訝?

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

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

  AFX_MANAGE_STATE(AfxGetStaticModuleState());

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

void ShowDlg(void){//方法1:在函數開始處變更,在函數結束時恢複//將AFX_MANAGE_STATE(AfxGetStaticModuleState());作為介面函數的第一//條語句進行模組狀態切換AFX_MANAGE_STATE(AfxGetStaticModuleState());CDialog dlg(IDD_DLL_DIALOG);//開啟ID為2000的對話方塊dlg.DoModal();}

 

  這次我們再點擊EXE程式中的“調用DLL”按鈕,彈出的是DLL中的13的對話方塊!嘿嘿,彈出了正確的對話方塊資源。

 AfxGetStaticModuleState是一個函數,其原型為:

  AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState( );

  該函數的功能是在棧上(這意味著其範圍是局部的)建立一個AFX_MODULE_STATE類(模組全域資料也就是模組狀態)的執行個體,對其進行設定,並將其指標pModuleState返回。

  AFX_MODULE_STATE類的原型如下:

// AFX_MODULE_STATE (global data for a module)class AFX_MODULE_STATE : public CNoTrackObject{public:#ifdef _AFXDLLAFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion);AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion,BOOL bSystem);#elseAFX_MODULE_STATE(BOOL bDLL);#endif~AFX_MODULE_STATE();CWinApp* m_pCurrentWinApp;HINSTANCE m_hCurrentInstanceHandle;HINSTANCE m_hCurrentResourceHandle;LPCTSTR m_lpszCurrentAppName;… //省略後面的部分}

 

  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改為:

void ShowDlg(void){//方法2的狀態變更HINSTANCE save_hInstance = AfxGetResourceHandle();AfxSetResourceHandle(theApp.m_hInstance);CDialog dlg(IDD_DLL_DIALOG);//開啟ID為2000的對話方塊dlg.DoModal();//方法2的狀態還原AfxSetResourceHandle(save_hInstance);}

 

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

extern CSharedDllApp theApp; //需要聲明theApp外部全域變數void ShowDlg(void){//方法2的狀態變更HINSTANCE save_hInstance = AfxGetResourceHandle();AfxSetResourceHandle(theApp.m_hInstance);CDialog dlg(IDD_DLL_DIALOG);//開啟ID為2000的對話方塊dlg.DoModal();//方法2的狀態還原AfxSetResourceHandle(save_hInstance);//使用方法2後在此處再進行操作針對的將是應用程式的資源CDialog dlg1(IDD_DLL_DIALOG); //開啟ID為2000的對話方塊dlg1.DoModal();}

 

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

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

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

void ShowDlg(void){CDialog dlg(IDD_DLL_DIALOG); //開啟ID為2000的對話方塊dlg.DoModal();}

 

  而將應用程式的OnCalldllButton函數改為:

void CSharedDllCallDlg::OnCalldllButton(){//方法3:由應用程式本身進行狀態切換//擷取EXE模組控制代碼HINSTANCE exe_hInstance = GetModuleHandle(NULL);//或者HINSTANCE exe_hInstance = AfxGetResourceHandle();//擷取DLL模組控制代碼HINSTANCE dll_hInstance = GetModuleHandle("SharedDll.dll");AfxSetResourceHandle(dll_hInstance); //切換狀態ShowDlg(); //此時顯示的是DLL的對話方塊AfxSetResourceHandle(exe_hInstance); //恢複狀態//資源模組恢複後再調用ShowDlgShowDlg(); //此時顯示的是EXE的對話方塊}

 

  方法三中的Win32函數GetModuleHandle可以根據DLL的檔案名稱擷取DLL的模組控制代碼。如果需要得到EXE模組的控制代碼,則應調用帶有Null參數的GetModuleHandle。

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

  在下一節我們將對MFC擴充DLL進行詳細分析和執行個體講解,歡迎您繼續關注本系列連載。

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.