VC++動態連接庫(DLL)編程深刻淺出(三)

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

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

  5. MFC規則DLL網絡

  5.1 概述框架

  MFC規則DLL的概念體如今兩方面:socket

  (1) 它是MFC的函數

  「是MFC的」意味着能夠在這種DLL的內部使用MFC;工具

  (2) 它是規則的this

  「是規則的」意味着它不一樣於MFC擴展DLL,在MFC規則DLL的內部雖然可使用MFC,可是其與應用程序的接口不能是MFC。而MFC擴展DLL與應用程序的接口能夠是MFC,能夠從MFC擴展DLL中導出一個MFC類的派生類。spa

  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 symbols
class 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_FILE
static char THIS_FILE[] = __FILE__;
#endif
BEGIN_MESSAGE_MAP(CRegularDllApp, CWinApp)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CRegularDllApp construction
CRegularDllApp::CRegularDllApp()
{
}
/////////////////////////////////////////////////////////////////////////////
// The one and only CRegularDllApp object
CRegularDllApp 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 _AFXDLL
AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion);
AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion,BOOL bSystem);
#else
AFX_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); //恢復狀態
//資源模塊恢復後再調用ShowDlg
ShowDlg(); //此時顯示的是EXE的對話框
}

 

  方法三中的Win32函數GetModuleHandle能夠根據DLL的文件名獲取DLL的模塊句柄。若是須要獲得EXE模塊的句柄,則應調用帶有Null參數的GetModuleHandle。

  方法三與方法二的不一樣在於方法三是在應用程序中利用AfxGetResourceHandle和AfxSetResourceHandle進行資源模塊句柄切換的。一樣地,在應用程序主對話框的「調用DLL」按鈕上點擊,也將看到兩個對話框,相繼爲DLL中的對話框(圖13)和EXE中的對話框(圖14)。

  在下一節咱們將對MFC擴展DLL進行詳細分析和實例講解,歡迎您繼續關注本系列連載。

 

引自:pconline

相關文章
相關標籤/搜索