本文共 11299 字,大约阅读时间需要 37 分钟。
作者: 宋宝华 e-mail: 21cnbao @ 21cn.com 第 4 节我们对非 MFC DLL 进行了介绍,这一节将详细地讲述 MFC 规则 DLL 的创建与使用技巧。 另外,自从本文开始连载后,收到了一些读者的 e-mail 。有的读者提出了一些问题,笔者将在本文的最后一次连载中选取其中的典型问题进行解答。由于时间的关系,对于读者朋友的来信,笔者暂时不能一一回复,还望海涵!由于笔者的水平有限,文中难免有错误和纰漏,也热诚欢迎读者朋友不吝指正! “是 MFC 的”意味着可以在这种 DLL 的内部使用 MFC ; “是规则的”意味着它不同于 MFC 扩展 DLL ,在 MFC 规则 DLL 的内部虽然可以使用 MFC ,但是其与应用程序的接口不能是 MFC 。而 MFC 扩展 DLL 与应用程序的接口可以是 MFC ,可以从 MFC 扩展 DLL 中导出一个 MFC 类的派生类。 Regular DLL 能够被所有支持 DLL 技术的语言所编写的应用程序调用,当然也包括使用 MFC 的应用程序。在这种动态连接库中,包含一个从 CWinApp 继承下来的类, DllMain 函数则由 MFC 自动提供。 静态链接到 MFC 的规则 DLL 与 MFC 库(包括 MFC 扩展 DLL )静态链接,将 MFC 库的代码直接生成在 .dll 文件中。在调用这种 DLL 的接口时, MFC 使用 DLL 的资源。因此,在静态链接到 MFC 的规则 DLL 中不需要进行模块状态的切换。 使用这种方法生成的规则 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 中进行设置。 我们来一步步讲述使用 MFC 向导创建 MFC 规则 DLL 的过程,首先新建一个 project ,如图 9 ,选择 project 的类型为 MFC AppWizard(dll) 。点击 OK 进入如图 10 所示的对话框。 图 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() AfxMessageBox(IDP_SOCKETS_INIT_FAILED); 4 区选择是否由 MFC 向导自动在源代码中添加注释,一般我们选择“ Yes,please ”。 这个 DLL 的例子(属于静态链接到 MFC 的规则 DLL )中提供了一个如图 11 所示的对话框。 在 DLL 中添加对话框的方式与在 MFC 应用程序中是一样的。 在图 11 所示 DLL 中的对话框的 Hello 按钮上点击时将 MessageBox 一个“ Hello,pconline 的网友”对话框,下面是相关的文件及源代码,其中删除了 MFC 向导自动生成的绝大多数注释( ): // 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_ #endif // _MSC_VER > 1000 #error include 'stdafx.h' before including this file for PCH #include "resource.h" // main symbols class CRegularDllApp : public CWinApp // RegularDll.cpp : Defines the initialization routines for the DLL. static char THIS_FILE[] = __FILE__; BEGIN_MESSAGE_MAP(CRegularDllApp, CWinApp) // CRegularDllApp construction CRegularDllApp::CRegularDllApp() // The one and only CRegularDllApp object 在这一组文件中定义了一个继承自 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 成员函数中。 #if !defined(AFX_DLLDIALOG_H__CEA4C6AF_245D_48A6_B11A_A5521EAD7C4E__INCLUDED_) #define AFX_DLLDIALOG_H__CEA4C6AF_245D_48A6_B11A_A5521EAD7C4E__INCLUDED_ #endif // _MSC_VER > 1000 // DllDialog.h : header file class CDllDialog : public CDialog CDllDialog(CWnd* pParent = NULL); // standard constructor enum { IDD = IDD_DLL_DIALOG }; virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support afx_msg void OnHelloButton(); // DllDialog.cpp : implementation file static char THIS_FILE[] = __FILE__; CDllDialog::CDllDialog(CWnd* pParent /*=NULL*/) : CDialog(CDllDialog::IDD, pParent) void CDllDialog::DoDataExchange(CDataExchange* pDX) CDialog::DoDataExchange(pDX); BEGIN_MESSAGE_MAP(CDllDialog, CDialog) ON_BN_CLICKED(IDC_HELLO_BUTTON, OnHelloButton) // CDllDialog message handlers void CDllDialog::OnHelloButton() MessageBox("Hello,pconline 的网友 ","pconline"); 这一部分的编程与一般的应用程序根本没有什么不同,我们照样可以利用 MFC 类向导来自动为对话框上的控件添加事件。 MFC 类向导照样会生成类似 ON_BN_CLICKED(IDC_HELLO_BUTTON, OnHelloButton) 的消息映射宏。 // Microsoft Developer Studio generated include file. #define IDD_DLL_DIALOG 1000 #define IDC_HELLO_BUTTON 1000 在 MFC 规则 DLL 中使用资源也与在 MFC 应用程序中使用资源没有什么不同,我们照样可以用 Visual C++ 的资源编辑工具进行资源的添加、删除和属性的更改。 extern "C" __declspec(dllexport) void ShowDlg(void) 这个接口并不使用 MFC ,但是在其中却可以调用 MFC 扩展类 CdllDialog 的函数,这体现了“规则”的概类。 与非 MFC DLL 完全相同,我们可以使用 __declspec(dllexport) 声明或在 .def 中引出的方式导出 MFC 规则 DLL 中的接口。 笔者编写了如图 12 的对话框 MFC 程序( )来调用 5.3 节的 MFC 规则 DLL ,在这个程序的对话框上点击“调用 DLL ”按钮时弹出 5.3 节 MFC 规则 DLL 中的对话框。 下面是“调用 DLL ”按钮单击事件的消息处理函数: void CRegularDllCallDlg::OnCalldllButton() typedef void (*lpFun)(void); hDll = LoadLibrary("RegularDll.dll"); lpFun pShowDlg = (lpFun)GetProcAddress(hDll,"ShowDlg"); MessageBox("DLL 中函数寻找失败 "); 上述例子中给出的是显示调用的方式,可以看出,其调用方式与第 4 节中非 MFC DLL 的调用方式没有什么不同。 我们照样可以在 EXE 程序中隐式调用 MFC 规则 DLL ,只需要将 DLL 工程生成的 .lib 文件和 .dll 文件拷入当前工程所在的目录,并在 RegularDllCallDlg.cpp 文件(图 12 所示对话框类的实现文件)的顶部添加: #pragma comment(lib,"RegularDll.lib") 并将 void CRegularDllCallDlg::OnCalldllButton() 改为: void CRegularDllCallDlg::OnCalldllButton() 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 的对话框。 另外,在与这个 DLL 相同的工作区中生成一个基于对话框的 MFC 程序,其对话框与图 12 完全一样。但是在此工程中我们另外添加了一个如图 14 的对话框。 图 13 和图 14 中的对话框除了 caption 不同(以示区别)以外,其它的都相同。 尤其值得特别注意,在 DLL 和 EXE 中我们对图 13 和图 14 的对话框使用了相同的资源 ID=2000 ,在 DLL 和 EXE 工程的 resource.h 中分别有如下的宏: #define IDD_DLL_DIALOG 2000 #define IDD_EXE_DIALOG 2000 与 5.3 节静态链接 MFC DLL 的规则 DLL 相同,我们还是在规则 DLL 中定义接口函数 ShowDlg ,原型如下: CDialog dlg(IDD_DLL_DIALOG); // 打开 ID 为 2000 的对话框 而为应用工程主对话框的“调用 DLL ”的单击事件添加如下消息处理函数: void CSharedDllCallDlg::OnCalldllButton() 我们以为单击“调用 DLL ”会弹出如图 13 所示 DLL 中的对话框,可是可怕的事情发生了,我们看到是图 14 所示 EXE 中的对话框! 产生这个问题的根源在于应用程序与 MFC 规则 DLL 共享 MFC DLL (或 MFC 扩展 DLL )的程序总是默认使用 EXE 的资源,我们必须进行资源模块句柄的切换,其实现方法有三: AFX_MANAGE_STATE(AfxGetStaticModuleState()); 我们将 DLL 中的接口函数 ShowDlg 改为: // 方法 1: 在函数开始处变更,在函数结束时恢复 // 将 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 作为接口函数的第一 // 条语句进行模块状态切换 AFX_MANAGE_STATE(AfxGetStaticModuleState()); CDialog dlg(IDD_DLL_DIALOG);// 打开 ID 为 2000 的对话框 这次我们再点击 EXE 程序中的“调用 DLL ”按钮,弹出的是 DLL 中的如图 13 的对话框!嘿嘿,弹出了正确的对话框资源。 AfxGetStaticModuleState 是一个函数,其原型为: AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState( ); 该函数的功能是在栈上(这意味着其作用域是局部的)创建一个 AFX_MODULE_STATE 类(模块全局数据也就是模块状态)的实例,对其进行设置,并将其指针 pModuleState 返回。 // AFX_MODULE_STATE (global data for a module) class AFX_MODULE_STATE : public CNoTrackObject AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion); AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion,BOOL bSystem); AFX_MODULE_STATE(BOOL bDLL); 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 的析构函数恢复。 AfxSetResourceHandle(HINSTANCE xxx); AfxGetResourceHandle 用于获取当前资源模块句柄,而 AfxSetResourceHandle 则用于设置程序目前要使用的资源模块句柄。 我们将 DLL 中的接口函数 ShowDlg 改为: HINSTANCE save_hInstance = AfxGetResourceHandle(); AfxSetResourceHandle(theApp.m_hInstance); CDialog dlg(IDD_DLL_DIALOG);// 打开 ID 为 2000 的对话框 AfxSetResourceHandle(save_hInstance); 通过 AfxGetResourceHandle 和 AfxSetResourceHandle 的合理变更,我们能够灵活地设置程序的资源模块句柄,而方法一则只能在 DLL 接口函数退出的时候才会恢复模块句柄。方法二则不同,如果将 ShowDlg 改为: extern CSharedDllApp theApp; // 需要声明 theApp 外部全局变量 HINSTANCE save_hInstance = AfxGetResourceHandle(); AfxSetResourceHandle(theApp.m_hInstance); CDialog dlg(IDD_DLL_DIALOG);// 打开 ID 为 2000 的对话框 AfxSetResourceHandle(save_hInstance); // 使用方法 2 后在此处再进行操作针对的将是应用程序的资源 CDialog dlg1(IDD_DLL_DIALOG); // 打开 ID 为 2000 的对话框 在应用程序主对话框的“调用 DLL ”按钮上点击,将看到两个对话框,相继为 DLL 中的对话框(图 13 )和 EXE 中的对话框(图 14 )。 资源模块的切换除了可以由 DLL 接口函数完成以外,由应用程序自身也能完成( )。 CDialog dlg(IDD_DLL_DIALOG); // 打开 ID 为 2000 的对话框 而将应用程序的 OnCalldllButton 函数改为: void CSharedDllCallDlg::OnCalldllButton() HINSTANCE exe_hInstance = GetModuleHandle(NULL); // 或者 HINSTANCE exe_hInstance = AfxGetResourceHandle(); HINSTANCE dll_hInstance = GetModuleHandle("SharedDll.dll"); AfxSetResourceHandle(dll_hInstance); // 切换状态 ShowDlg(); // 此时显示的是 DLL 的对话框 AfxSetResourceHandle(exe_hInstance); // 恢复状态 ShowDlg(); // 此时显示的是 EXE 的对话框 方法三中的 Win32 函数 GetModuleHandle 可以根据 DLL 的文件名获取 DLL 的模块句柄。如果需要得到 EXE 模块的句柄,则应调用带有 Null 参数的 GetModuleHandle 。 方法三与方法二的不同在于方法三是在应用程序中利用 AfxGetResourceHandle 和 AfxSetResourceHandle 进行资源模块句柄切换的。同样地,在应用程序主对话框的“调用 DLL ”按钮上点击,也将看到两个对话框,相继为 DLL 中的对话框(图 13 )和 EXE 中的对话框(图 14 )。 在下一节我们将对 MFC 扩展 DLL 进行详细分析和实例讲解,欢迎您继续关注本系列连载。 本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/120817,如需转载请自行联系原作者