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 ,如需转载请自行联系原作者

相关文章
|
3月前
|
C++
C++ 语言异常处理实战:在编程潮流中坚守稳定,开启代码可靠之旅
【8月更文挑战第22天】C++的异常处理机制是确保程序稳定的关键特性。它允许程序在遇到错误时优雅地响应而非直接崩溃。通过`throw`抛出异常,并用`catch`捕获处理,可使程序控制流跳转至错误处理代码。例如,在进行除法运算或文件读取时,若发生除数为零或文件无法打开等错误,则可通过抛出异常并在调用处捕获来妥善处理这些情况。恰当使用异常处理能显著提升程序的健壮性和维护性。
71 2
|
27天前
|
存储 C++ UED
【实战指南】4步实现C++插件化编程,轻松实现功能定制与扩展
本文介绍了如何通过四步实现C++插件化编程,实现功能定制与扩展。主要内容包括引言、概述、需求分析、设计方案、详细设计、验证和总结。通过动态加载功能模块,实现软件的高度灵活性和可扩展性,支持快速定制和市场变化响应。具体步骤涉及配置文件构建、模块编译、动态库入口实现和主程序加载。验证部分展示了模块加载成功的日志和配置信息。总结中强调了插件化编程的优势及其在多个方面的应用。
201 62
|
3月前
|
算法 C语言 C++
C++语言学习指南:从新手到高手,一文带你领略系统编程的巅峰技艺!
【8月更文挑战第22天】C++由Bjarne Stroustrup于1985年创立,凭借卓越性能与灵活性,在系统编程、游戏开发等领域占据重要地位。它继承了C语言的高效性,并引入面向对象编程,使代码更模块化易管理。C++支持基本语法如变量声明与控制结构;通过`iostream`库实现输入输出;利用类与对象实现面向对象编程;提供模板增强代码复用性;具备异常处理机制确保程序健壮性;C++11引入现代化特性简化编程;标准模板库(STL)支持高效编程;多线程支持利用多核优势。虽然学习曲线陡峭,但掌握后可开启高性能编程大门。随着新标准如C++20的发展,C++持续演进,提供更多开发可能性。
74 0
|
1月前
|
存储 搜索推荐 C++
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
48 2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
|
21天前
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
|
2月前
|
存储 算法 C++
C++提高篇:泛型编程和STL技术详解,探讨C++更深层的使用
文章详细探讨了C++中的泛型编程与STL技术,重点讲解了如何使用模板来创建通用的函数和类,以及模板在提高代码复用性和灵活性方面的作用。
49 2
C++提高篇:泛型编程和STL技术详解,探讨C++更深层的使用
|
1月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
77 11
|
1月前
|
存储 C++ 容器
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器1
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
51 5
|
1月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
37 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
1月前
|
算法 编译器 C++
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
72 2