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