VC++动态链接库(DLL)编程(四)――MFC扩展 DLL

简介:
VC++ 动态链接库 (DLL) 编程(四)
―― MFC 扩展  DLL
作者:宋宝华  e-mail:21cnbao@21cn.com
 
前文我们对非 MFC DLL MFC 规则 DLL 进行了介绍,现在开始详细分析 DLL 的最后一种类型―― MFC 扩展 DLL
 
6.1 概论
MFC 扩展 DLL MFC 规则 DLL 的相同点在于在两种 DLL 的内部都可以使用 MFC 类库,其不同点在于 MFC 扩展 DLL 与应用程序的接口可以是 MFC 的。 MFC 扩展 DLL 的含义在于它是 MFC 的扩展,其主要功能是实现从现有 MFC 库类中派生出可重用的类。 MFC 扩展 DLL 使用 MFC  动态链接库版本,因此只有用共享 MFC  版本生成的 MFC  可执行文件(应用程序或规则 DLL )才能使用 MFC 扩展 DLL
从前文可知, MFC 规则 DLL MFC 向导自动添加了一个 CWinApp 的对象,而 MFC 扩展 DLL 则不包含该对象,它只是被自动添加了 DllMain  函数。对于 MFC 扩展 DLL ,开发人员必须在 DLL DllMain 函数中添加初始化和结束代码。
从下表我们可以看出三种 DLL DllMain 入口函数的不同处理方式:
 
DLL 类型
入口函数
 MFC DLL
编程者提供 DllMain 函数
MFC 规则  DLL
CWinApp 对象的 InitInstance   ExitInstance
MFC 扩展  DLL
MFC DLL 向导生成 DllMain  函数
 
对于 MFC 扩展 DLL ,系统会自动在工程中添加如下表所示的宏,这些宏为 DLL 和应用程序的编写提供了方便。像 AFX_EXT_CLASS AFX_EXT_API AFX_EXT_DATA 这样的宏,在 DLL 和应用程序中将具有不同的定义,这取决于 _AFXEXT 宏是否被定义。这使得在 DLL 和应用程序中,使用统一的一个宏就可以表示出输出和输入的不同意思。在 DLL 中,表示输出(因为 _AFXEXT 被定义,通常是在编译器的标识参数中指定 /D_AFXEXT );在应用程序中,则表示输入( _AFXEXT 没有定义)。
 
定义
AFX_CLASS_IMPORT
__declspec(dllexport)
AFX_API_IMPORT
__declspec(dllexport)
AFX_DATA_IMPORT
__declspec(dllexport)
AFX_CLASS_EXPORT
__declspec(dllexport)
AFX_API_EXPORT
__declspec(dllexport)
AFX_DATA_EXPORT
__declspec(dllexport)
AFX_EXT_CLASS
#ifdef _AFXEXT
AFX_CLASS_EXPORT
#else
AFX_CLASS_IMPORT
AFX_EXT_API
#ifdef _AFXEXT
AFX_API_EXPORT
#else
AFX_API_IMPORT
AFX_EXT_DATA
#ifdef _AFXEXT
AFX_DATA_EXPORT
#else
AFX_DATA_IMPORT
 
6.2 MFC 扩展 DLL 导出 MFC 派生类
 
在这个例子中,我们将产生一个名为“ ExtDll ”的 MFC 扩展 DLL 工程,在这个 DLL 中导出一个对话框类,这个对话框类派生自 MFC CDialog
使用 MFC 向导生成 MFC 扩展 DLL 时,系统会自动添加如下代码:
static AFX_EXTENSION_MODULE ExtDllDLL = { NULL, NULL };
 
extern "C" int APIENTRY
DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved )
{
      // Remove this if you use lpReserved
      UNREFERENCED_PARAMETER( lpReserved );
   // 说明: lpReserved 是一个被系统所保留的参数,对于隐式链接是一个非零值,对于显式链接值是零
 
      if (dwReason == DLL_PROCESS_ATTACH)
      {
             TRACE0( "EXTDLL.DLL Initializing!\n" );
            
             // Extension DLL one-time initialization
             if ( !AfxInitExtensionModule( ExtDllDLL, hInstance ))
                    return 0;
 
             // Insert this DLL into the resource chain
             new CDynLinkLibrary( ExtDllDLL );
      }
      else if (dwReason == DLL_PROCESS_DETACH)
      {
             TRACE0( "EXTDLL.DLL Terminating!\n" );
             // Terminate the library before destructors are called
             AfxTermExtensionModule( ExtDllDLL );
      }
      return 1;   // ok
}
这一段代码含义晦涩,我们需要对其进行解读:
1 )上述代码完成 MFC 扩展 DLL 的初始化和终止处理;
2 )初始化期间所创建的  CDynLinkLibrary  对象使 MFC 扩展  DLL  可以将  DLL 中的 CRuntimeClass  对象或资源导出到应用程序;
3 AfxInitExtensionModule 函数捕获模块的 CRuntimeClass  结构和在创建  CDynLinkLibrary  对象时使用的对象工厂( COleObjectFactory  对象);
4 AfxTermExtensionModule 函数使  MFC  得以在每个进程与扩展  DLL  分离时(进程退出或使用 AfxFreeLibrary 卸载 DLL 时)清除扩展  DLL
5 )第一条语句 static AFX_EXTENSION_MODULE ExtDllDLL = { NULL, NULL }; 定义了一个 AFX_EXTENSION_MODULE 类的静态全局对象, AFX_EXTENSION_MODULE 的定义如下:
struct AFX_EXTENSION_MODULE
{
      BOOL bInitialized;
      HMODULE hModule;
      HMODULE hResource;
      CRuntimeClass* pFirstSharedClass;
      COleObjectFactory* pFirstSharedFactory;
};
AFX_EXTENSION_MODULE 的定义我们可以更好的理解( 2 )、( 3 )、( 4 )点。
在资源编辑器中添加一个如图 15 所示的对话框,并使用 MFC 类向导为其添加一个对应的类 CExtDialog ,系统自动添加了 ExtDialog.h ExtDialog.cpp 两个头文件。
15 MFC扩展DLL中的对话框
修改 ExtDialog.h CExtDialog 类的声明为:
class AFX_EXT_CLASS CExtDialog : public CDialog
{
public:
      CExtDialog( CWnd* pParent = NULL );  
 
      enum { IDD = IDD_DLL_DIALOG };
 
      protected:
      virtual void DoDataExchange( CDataExchange* pDX );  
 
      DECLARE_MESSAGE_MAP()
};
这其中最主要的改变是我们在 class AFX_EXT_CLASS CExtDialog 语句中添加了“ AFX_EXT_CLASS ”宏,则使得 DLL 中的 CExtDialog 类被导出。
 
6.3 MFC 扩展 DLL 的加载
6.3.1  隐式加载
我们在 6.2 工程所在的工作区中添加一个 LoadExtDllDlg 工程,用于演示 MFC 扩展 DLL 的加载。在 LoadExtDllDlg 工程中添加一个如图 16 所示的对话框,这个对话框上包括一个“调用 DLL ”按钮。
16 MFC扩展DLL调用工程中的对话框
    在与图 16 对应对话框类实现文件的头部添加:
// LoadExtDllDlg.cpp : implementation file
//
#include "..\ExtDialog.h"
#pragma comment( lib, "ExtDll.lib" )
而“调用 DLL ”按钮的单击事件的消息处理函数为:
void CLoadExtDllDlg::OnDllcallButton()
{
       CExtDialog  extDialog;
       extDialog.DoModal();
}
当我们单击“调用 DLL ”的时候,弹出了如图 15 的对话框。
为提供给用户隐式加载( MFC 扩展 DLL 一般使用隐式加载,具体原因见下节), MFC 扩展 DLL 需要提供三个文件:
1 )描述 DLL 中扩展类的头文件;
2 )与动态链接库对应的 .LIB 文件;
3 )动态链接库 .DLL 文件本身。
有了这三个文件,应用程序的开发者才可充分利用 MFC 扩展 DLL
6.3.2  显示加载
显示加载 MFC 扩展 DLL 应使用 MFC 全局函数 AfxLoadLibrary 而不是 WIN32 API 中的 LoadLibrary AfxLoadLibrary  最终也调用了  LoadLibrary 这个 API ,但是在调用之前进行了线程同步的处理。
AfxLoadLibrary  的函数原型与  LoadLibrary 完全相同,为:
HINSTANCE AFXAPI AfxLoadLibrary( LPCTSTR lpszModuleName );
与之相对应的是, MFC  应用程序应使用 AfxFreeLibrary  而非 FreeLibrary  卸载 MFC 扩展 DLL AfxFreeLibrary 的函数原型也与  FreeLibrary 完全相同,为:
BOOL AFXAPI AfxFreeLibrary( HINSTANCE hInstLib );
如果我们把上例中的“调用 DLL ”按钮单击事件的消息处理函数改为:
void CLoadExtDllDlg::OnDllcallButton()
{
    HINSTANCE hDll = AfxLoadLibrary( "ExtDll.dll" );
       if(NULL == hDll)
       {
              AfxMessageBox( "MFC 扩展 DLL 动态加载失败 " );
              return;
       }
      
       CExtDialog  extDialog;
       extDialog.DoModal();
   
       AfxFreeLibrary(hDll);
}
则工程会出现 link 错误:
LoadExtDllDlg.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) public: virtual __thiscall CExtDialog::~CExtDialog(void)" (__imp_??1CExtDialog@@UAE@XZ)
LoadExtDllDlg.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) public: __thiscall CExtDialog::CExtDialog(class CWnd *)" (__imp_??0CExtDialog@@QAE@PAVCWnd@@@Z)
提示 CExtDialog 的构造函数和析构函数均无法找到!是的,对于派生 MFC 类的 MFC 扩展 DLL ,当我们要在应用程序中使用 DLL 中定义的派生类时,我们不宜使用动态加载 DLL 的方法。
 
6.4 MFC 扩展 DLL 加载 MFC 扩展 DLL
    我们可以在 MFC 扩展 DLL 中再次使用 MFC 扩展 DLL ,但是,由于在两个 DLL 中对于 AFX_EXT_CLASS AFX_EXT_API AFX_EXT_DATA 宏的定义都是输出,这会导致调用的时候出现问题。
我们将会在调用 MFC 扩展 DLL DLL 中看到 link 错误:
error LNK2001: unresolved external symbol ….......
因此,在调用 MFC 扩展 DLL MFC 扩展 DLL 中,在包含被调用 DLL 的头文件之前,需要临时重新定义 AFX_EXT_CLASS 的值。下面的例子显示了如何实现:
// 临时改变宏的含义“输出”为“输入”
#undef AFX_EXT_CLASS
#undef AFX_EXT_API
#undef AFX_EXT_DATA
#define AFX_EXT_CLASS AFX_CLASS_IMPORT
#define AFX_EXT_API AFX_API_IMPORT
#define AFX_EXT_DATA AFX_DATA_IMPORT
 
// 包含被调用 MFC 扩展 DLL 的头文件
#include "CalledDLL.h"
 
// 恢复宏的含义为输出
#undef AFX_EXT_CLASS
#undef AFX_EXT_API
#undef AFX_EXT_DATA
#define AFX_EXT_CLASS AFX_CLASS_EXPORT
#define AFX_EXT_API AFX_API_EXPORT
#define AFX_EXT_DATA AFX_DATA_EXPORT
 
6.5 MFC 扩展 DLL 导出函数和变量
MFC 扩展 DLL 导出函数和变量的方法也十分简单,下面我们给出一个简单的例子。
我们在 MFC 向导生成的 MFC 扩展 DLL 工程中添加 gobal.h global.cpp 两个文件:
//global.h:MFC 扩展 DLL 导出变量和函数的声明
extern "C"
{     int AFX_EXT_DATA total; // 导出变量
       int AFX_EXT_API  add( int x, int y ); // 导出函数
}
 
//global.cpp:MFC 扩展 DLL 导出变量和函数定义
#include "StdAfx.h"
#include "global.h"
 
extern "C" int total;
int add(int x,int y)
{
       total = x + y;
       return total;
}
编写一个简单的控制台程序来调用这个 MFC 扩展 DLL
#include <iostream.h>
#include <afxver_.h>  
       //AFX_EXT_DATA AFX_EXT_API 宏的定义在 afxver_.h 头文件中
 
#pragma comment ( lib, "ExtDll.lib" )
#include "..\global.h"
 
int main(int argc, char* argv[])
{
 
       cout << add(2,3) << endl;
       cout << total;
 
       return 0;
}
运行程序,在控制台上看到:
5
5
另外,在 Visual C++ 下建立 MFC 扩展 DLL 时, MFC DLL 向导会自动生成 .def 文件。因此,对于函数和变量,我们除了可以利用 AFX_EXT_DATA AFX_EXT_API 宏导出以外,在 .def 文件中定义导出也是一个很好的办法。与之相比,在 .def 文件中导出类却较麻烦。通常需要从工程生成的 .map 文件中获得类的所有成员函数被 C++ 编译器更改过的标识符,并且在 .def 文件中导出这些“奇怪”的标识符。因此, MFC 扩展 DLL 通常以 AFX_EXT_CLASS 宏直接声明导出类。
 
6.6 MFC 扩展 DLL 的应用
上述各小节所举 MFC 扩展 DLL 的例子均只是为了说明某方面的问题,没有真实地体现“ MFC 扩展”   的内涵,譬如 6.2 派生自 CDialog 的类也不具备比 CDialog 更强的功能。 MFC 扩展 DLL 的真实内涵体现在它提供的类虽然派生自 MFC 类,但是提供了比 MFC 类更强大的功能、更丰富的接口。下面我们来看一个具体的例子
我们知道 static 控件所对应的 CStatic 类不具备设置背景和文本颜色的接口,这使得我们不能在对话框或其它用户界面上自由灵活地修改 static 控件的颜色风格,因此我们需要一个提供了 SetBackColor SetTextColor 接口的 CStatic 派生类 CMultiColorStatic
这个类的声明如下:
class AFX_EXT_CLASS CMultiColorStatic : public CStatic
{
      // Construction
public:
      CMultiColorStatic();
      virtual ~CMultiColorStatic();
      // Attributes
protected:    
      CString m_strCaption;
      COLORREF m_BackColor;
      COLORREF m_TextColor;
      // Operations
public:
    void SetTextColor( COLORREF TextColor );   
    void SetBackColor( COLORREF BackColor );   
       void SetCaption( CString strCaption );
      // Generated message map functions
protected:
    afx_msg void OnPaint();
      DECLARE_MESSAGE_MAP()
};
在这个类的实现文件中,我们需要为它提供 WM_PAINT 消息的处理函数(这是因为颜色的设置依赖于 WM_PAINT 消息):
BEGIN_MESSAGE_MAP(CMultiColorStatic, CStatic)
//{{AFX_MSG_MAP(CMultiColorStatic)
      ON_WM_PAINT()  // 为这个类定义 WM_PAINT 消息处理函数
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
下面是这个类中的重要成员函数:
// CMultiColorStatic 类添加“设置文本颜色”接口
void CMultiColorStatic::SetTextColor( COLORREF TextColor )
{
      m_TextColor = TextColor;       // 设置文字颜色
}
// CMultiColorStatic 类添加“设置背景颜色”接口
void CMultiColorStatic::SetBackColor( COLORREF BackColor )
{
    m_BackColor = BackColor;      // 设置背景颜色
}
// CMultiColorStatic 类添加“设置标题”接口
void CMultiColorStatic::SetCaption( CString strCaption )
{
      m_strCaption = strCaption;
}
// 重画 Static ,颜色和标题的设置都依赖于这个函数
void CMultiColorStatic::OnPaint()
{
    CPaintDC dc(this); // device context for painting
    CRect rect;
    GetClientRect( &rect );
    dc.SetBkColor( m_BackColor );
    dc.SetBkMode( TRANSPARENT );
    CFont *pFont = GetParent()->GetFont();// 得到父窗体的字体
    CFont *pOldFont;
    pOldFont = dc.SelectObject( pFont );// 选用父窗体的字体
    dc.SetTextColor( m_TextColor );// 设置文本颜色
    dc.DrawText( m_strCaption, &rect, DT_CENTER );// 文本在 Static 中央
    dc.SelectObject( pOldFont );
}
为了验证 CMultiColorStatic 类,我们制作一个基于对话框的应用程序,它包含一个如图 17 所示的对话框。该对话框上包括一个 static 控件和三个按钮,这三个按钮可分别把 static 控件设置为“红色”、“蓝色”和“绿色”。
17 扩展的CStatic类调用演示
下面看看应如何编写与这个对话框对应的类。
包含这种 Static 的对话框类的声明如下:
#include "..\MultiColorStatic.h"
#pragma comment ( lib, "ColorStatic.lib" )
 
// CCallDllDlg dialog
class CCallDllDlg : public CDialog
{
public:
      CCallDllDlg(CWnd* pParent = NULL);  // standard constructor
 
      enum { IDD = IDD_CALLDLL_DIALOG };
      CMultiColorStatic       m_colorstatic;  // 包含一个 CMultiColorStatic 的实例
 
      protected:
      virtual void DoDataExchange(CDataExchange* pDX);//DDX/DDV support
 
      HICON m_hIcon;
 
      // Generated message map functions
      //{{AFX_MSG(CCallDllDlg)
      virtual BOOL OnInitDialog();
      afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
      afx_msg void OnPaint();
      afx_msg HCURSOR OnQueryDragIcon();
      afx_msg void OnRedButton();
      afx_msg void OnBlueButton();
      afx_msg void OnGreenButton();
      //}}AFX_MSG
      DECLARE_MESSAGE_MAP()
};
下面是这个类中与使用 CMultiColorStatic 相关的主要成员函数:
void CCallDllDlg::DoDataExchange(CDataExchange* pDX)
{
      CDialog::DoDataExchange(pDX);
      //{{AFX_DATA_MAP(CCallDllDlg)
      DDX_Control(pDX, IDC_COLOR_STATIC, m_colorstatic);
       // 使 m_colorstatic IDC_COLOR_STATIC 控件关联
      //}}AFX_DATA_MAP
}
 
BOOL CCallDllDlg::OnInitDialog()
{
      
      // TODO: Add extra initialization here
 
      //  初始 static 控件的显示
      m_colorstatic.SetCaption(" 最开始为黑色 ");  
      m_colorstatic.SetTextColor(RGB(0,0,0));
 
      return TRUE;  // return TRUE  unless you set the focus to a control
}
 
// 设置 static 控件文本颜色为红色
void CCallDllDlg::OnRedButton()
{
      m_colorstatic.SetCaption( " 改变为红色 " );
      m_colorstatic.SetTextColor( RGB( 255, 0, 0 ) );
      Invalidate( TRUE );     // 导致发出 WM_PAINT 消息
}
 
// 设置 static 控件文本颜色为蓝色
void CCallDllDlg::OnBlueButton()
{
      m_colorstatic.SetCaption( " 改变为蓝色 " );
      m_colorstatic.SetTextColor( RGB( 0, 0, 255 ) );
      Invalidate( TRUE );     // 导致发出 WM_PAINT 消息
}
 
// 设置 static 控件文本颜色为绿色
void CCallDllDlg::OnGreenButton()
{
      m_colorstatic.SetCaption( " 改变为绿色 " );
      m_colorstatic.SetTextColor( RGB(0,255,0) );
      Invalidate( TRUE );     // 导致发出 WM_PAINT 消息
}
 
至此,我们已经讲解完成了所有类型的动态链接库,即非 MFC DLL MFC 规则 DLL MFC 扩展 DLL 。下一节将给出 DLL 的三个工程实例,与读者朋友们共同体会 DLL 的应用范围和使用方法。

 本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/120771 ,如需转载请自行联系原作者

相关文章
|
26天前
|
编译器 C++ 开发者
C++一分钟之-C++20新特性:模块化编程
【6月更文挑战第27天】C++20引入模块化编程,缓解`#include`带来的编译时间长和头文件管理难题。模块由接口(`.cppm`)和实现(`.cpp`)组成,使用`import`导入。常见问题包括兼容性、设计不当、暴露私有细节和编译器支持。避免这些问题需分阶段迁移、合理设计、明确接口和关注编译器更新。示例展示了模块定义和使用,提升代码组织和维护性。随着编译器支持加强,模块化将成为C++标准的关键特性。
58 3
|
27天前
|
存储 C++
【C++航海王:追寻罗杰的编程之路】一篇文章带你了解二叉搜索树
【C++航海王:追寻罗杰的编程之路】一篇文章带你了解二叉搜索树
16 1
|
27天前
|
算法 安全 编译器
【C++航海王:追寻罗杰的编程之路】C++11(四)
【C++航海王:追寻罗杰的编程之路】C++11(四)
21 0
|
27天前
|
存储 自然语言处理 C++
【C++航海王:追寻罗杰的编程之路】set|map|multiset|multimap简单介绍
【C++航海王:追寻罗杰的编程之路】set|map|multiset|multimap简单介绍
18 0
【C++航海王:追寻罗杰的编程之路】set|map|multiset|multimap简单介绍
|
27天前
|
存储 安全 程序员
【C++航海王:追寻罗杰的编程之路】C++11(一)
【C++航海王:追寻罗杰的编程之路】C++11(一)
17 0
【C++航海王:追寻罗杰的编程之路】C++11(一)
|
27天前
|
设计模式 编译器 C++
【C++航海王:追寻罗杰的编程之路】特殊类的设计方式你知道哪些?
【C++航海王:追寻罗杰的编程之路】特殊类的设计方式你知道哪些?
13 0
|
27天前
|
编译器 C++
【C++航海王:追寻罗杰的编程之路】多态你了解多少?
【C++航海王:追寻罗杰的编程之路】多态你了解多少?
14 0
|
27天前
|
编译器 C++ 容器
【C++航海王:追寻罗杰的编程之路】C++11(三)
【C++航海王:追寻罗杰的编程之路】C++11(三)
12 0
|
27天前
|
存储 编译器 C++
【C++航海王:追寻罗杰的编程之路】C++11(二)
【C++航海王:追寻罗杰的编程之路】C++11(二)
17 0
|
27天前
|
存储 Java 程序员
【C++航海王:追寻罗杰的编程之路】异常——错误处理方式之一
【C++航海王:追寻罗杰的编程之路】异常——错误处理方式之一
14 0