对《在C++类中实现Windows窗口的创建》一文的补充
//========================================================================
//TITLE:
// 对《在C++类中实现Windows窗口的创建》一文的补充
//AUTHOR:
// norains
//DATE:
// 第一版 Sunday 26-November-2006
// 修正版 Wednesday 13-December-2006
//Passed Environment:
// WinCE4.2+EVC4.0
//========================================================================
《在C++类中实现Windows窗口的创建》(以下简称《创建》,文章地址:http://blog.csdn.net/norains/archive/2006/11/09/1376412.aspx)一文中提到,如果全局变量g_pDlg需要调用类的非public函数,需要借助于一个对于CallDlgProc()函数。正如在文中所说,这样会暴露一个无用的接口,虽然只有一个,但也有可能会造成使用者的迷惑。思考再三,其实可以采用定义一个静态的结构体,然后给该结构体的成员赋予地址实现CallDlgProc()的功能。
为方便对比,这里依然还是采用《创建》的代码。
首先我们需要定义一个结构,该结构的成员全是指针,并且指向的是CMainDlg类中的部分private或protected对象及其函数。
typedef struct _InternalMember
{
HINSTANCE *pm_hInst;
void (*pTempTestFuncion)();
}INTERNALMEMBER,*LPINTERNALMEMBER;
然后声明一个全局变量,并在构造函数中给其赋值.
INTERNALMEMBER g_internalMember; //声明全局变量
...
//构造函数
CMainDlg::CMainDlg(HINSTANCE hInst)
{
...
g_internalMember.pTempTestFunction = TempTestFnction; //Error! 此段代码无法编译通过!
g_internalMember.pm_hInst = &m_hInst; //OK!将private成员m_hInst的地址赋给g_internalMember的pm_hInst指针.
...
}
//消息的回调函数的实现
BOOL CALLBACK CMainDlg::MainDlgProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)
{
if(g_pDlg != NULL)
{
//因为在构造函数中赋值无法编译通过,所以在此不能这样调用
//(*g_internalMember.pTempTestFunction)();
//如果需要获取m_hInst值,可以这么简单调用:
HINSTANCE hInst = *(g_internalMember.pm_hInst);
}
return FALSE;
}
在此我们可以看到,成员数据可以正常调用,但成员函数在给指针赋值的时候无法编译通过.也就是说,我们不能直接通过_InternalMember来调用类的函数!难道我们为了实现Windows消息函数处理,就只能暴露CallDlgProc()这种丑陋的接口了么?非也!天无绝人之路,只是为了实现我们封装的目的,需要对原来的代码进行算是较大变样修改.
首先修改_InternalMember的成员:
typedef struct _InternalMember
{
HINSTANCE *pm_hInst;
CMainDlg *pMainDlgObject; //指向CMainDlg实例对象的指针
}INTERNALMEMBER,*LPINTERNALMEMBER;
当然了,CMainDlg也需要修改
class CMainDlg
{
...
protected:
//注意MainDlgProc的定义, 和《创建》一文中的不同,已经不是static !
//为方便比较,在此列出《创建》一文中的定义:
//static BOOL CALLBACK CMainDlg::MainDlgProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam);
BOOL MainDlgProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam);
...
};
因为CMainDlg里的MainDlgProc函数已经不是静态,所以是不能作为创建对话框时传递给系统的回调函数.换句话说,这种方式的调用肯定是无法正确编译通过:
CreateDialog(m_hInst,MAKEINTRESOURCE(IDD_MAIN_DLG),NULL,MainDlgProc);//ERROR!无法正确编译!
不过没关系,因为我们本来就不打算将CMainDlgProc类的MainDlgProc()作为系统消息处理的回调函数,真正的用来做回调函数的是这个全局的MainDlgProc():
LRESULT CALLBACK MainDlgProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
...
}
所以这时候我们的对话框创建的函数应该这么传递:
//注意到区别了么?最后面一个参数是::MainDlgProc,意指这个MainDlgProc是全局的,非CMainDlg类的.
CreateDialog(m_hInst,MAKEINTRESOURCE(IDD_MAIN_DLG),NULL,::MainDlgProc);
好了,对话框创建的代码可以顺利编译通过,但此时我们还是要回过头来看看这个全局的MainDlgProc().这个全局的MainDlgProc()实际作用仅仅是给系统提供一个回调函数的入口,实际上真正的处理还是需要调用CMainDlgProc里的MainDlgProc().
在此调用正常工作之前,当然是先给g_internalMember里的对象指针赋值,要不然,程序崩溃是肯定的.
CMainDlg::CMainDlg(HINSTANCE hInst)
{
...
g_internalMember.pMainDlgObject = this; //将实例对象赋值给全局变量
...
}
如果想要在全局的MainDlgProc()里调用对象的MainDlgProc()函数,我们能想到的最简便快捷的方法无非是:
g_internalMember.pMainDlgObject->MainDlgProc(hWnd,wMsg,wParam,lParam) //Error!
非常遗憾,但也是意料之中,这个调用肯定无法正确编译通过,因为MainDlgProc()是CMainDlg类的protected成员!难道我们为了能正常工作,要将CMainDlg里的MainDlgProc声明为public?这和暴露拙劣的CallDlgProc()接口有什么区别?!
不急,还记得"友元"么?这时候就可以派得上用场了!
CMainDlg
{
...
public:
//声明全局的MainDlgProc为类的友元函数
friend LRESULT CALLBACK ::MainDlgProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam);
...
}
这时候全局的MainDlgProc()就可以正常使用啦:
LRESULT CALLBACK MainDlgProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
g_internalMember.pMainDlgObject->MainDlgProc(hWnd,wMsg,wParam,lParam); //OK!
}
PS:
1.有的朋友看了《创建》一文后,觉得设置一个静态变量不是一个好方法,因为如果创建多个对象的话,静态变量只保持最后一个对象的地址。但在平时的使用中,我们发现同一个进程几乎不会同时出现两个及两个以上的一模一样功能相同的对话框,所以这个例子是针对于一次只出现一个对话框的实现。
严谨一点来说,其实这个类还应该设置个互斥量,让在同一进程中对象只存在一个。这个比较简单,但和本篇主题联系不大,故在此暂且不表。
2.也许有的朋友看了这篇文章之后,可能觉得这样比较繁琐.的确如此!但如果我们只把焦点放到CMainDlg类,就会觉得所暴露的接口非常合理,并且相应的代码也更为易懂.为这看似一点点的合理性作出的繁琐工序,我觉得是值得的.
附:更改后的完整代码
///////////////////////////
//MainDlg.h
///////////////////////////
typedef struct _InternalMember
{
HINSTANCE *pm_hInst;
CMainDlg *pMainDlgObject; //指向CMainDlg实例对象的指针
}INTERNALMEMBER,*LPINTERNALMEMBER;
class CMainDlg
{
public:
//构造函数
CMainDlg(HINSTANCE hInst);
//析构函数
virtual ~CMainDlg();
//创建对话框
BOOL CreateMainDlg();
//声明全局的MainDlgProc为类的友元函数
friend LRESULT CALLBACK ::MainDlgProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam);
protected:
MainDlgProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam);
HINSTANCE m_hInst;
HWND m_hDlg;
//临时测试调用函数,仅作为调试用
void TempTestFuncion(){MessageBox(NULL,L"test",L"",NULL);};
};
然后是MainDlg类的实现:
/////////////////////////////////
//MainDlg.cpp
////////////////////////////////
#include "stdafx.h"
#include "MainDlg.h"
#include "resource.h"
InternalMember g_internalMember;
//全局的对话框消息过程处理函数
LRESULT CALLBACK MainDlgProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
g_internalMember.pMainDlgObject->MainDlgProc(hWnd,wMsg,wParam,lParam); //OK!
}
//真正用来处理消息的函数.
BOOL MainDlgProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)
{
switch(wMsg)
{
case WM_INITDIALOG:
TempTestFuncion();
break;
}
return FALSE;
}
//构造函数
CMainDlg::CMainDlg(HINSTANCE hInst)
{
m_hInst = hInst;
g_internalMember.pm_hInst = &m_hInst;
g_internalMember.pMainDlgObject = this; //实例对象的指针
}
//析构函数
CMainDlg::~CMainDlg()
{
}
//创建对话框
BOOL CMainDlg::CreateMainDlg()
{
//norains:创建对话框,回调函数是全局的MainDlgProc
m_hDlg = CreateDialog(m_hInst,MAKEINTRESOURCE(IDD_MAIN_DLG),NULL,::MainDlgProc);
if(m_hDlg == NULL)
{
return FALSE;
}
ShowWindow(m_hDlg,TRUE);//显示窗口
return TRUE;
}
这是在主程序中对类的的调用:
//////////////////
//MainApp.cpp
//////////////////
#include "stdafx.h"
#include "MainDlg.h"
int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
//声明一个对象
CMainDlg mainDlg(hInstance);
//创建并显示窗口
if(mainDlg.CreateMainDlg() == FALSE)
{
return 0x05;
}
//消息循环
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}