WinCE Win32 "Hello World" 应用程序的窗口包装器






4.71/5 (5投票s)
在本文中,我将标准的Windows CE“Hello World”Win32应用程序封装到一个可以继承的类中。
引言
我创建了一个基于对话框的MFC应用程序。这个程序的核心是一些不依赖于MFC的类,我小心地使它们易于移植,因为我知道我想在CE上尝试。我开始使用Embedded Visual C++ 3.0,并寻找一个应用向导来帮助我创建一个基于Win32对话框的应用。由于找不到,我使用Win32应用向导创建了一个新项目。就像Visual Studio中的Win32应用向导一样,这里一个类也没有。
我正准备忍受缺乏对象的痛苦时,偶然看到了Steve Hanov在C/C++ Users Journal上的一篇文章,解释了如何用类来封装Windows。于是我着手创建了2个入门项目。第一个是一个用对象封装的Win32“Hello World”应用,第二个是用对象封装的Win32对话框应用。
在本文中,我将标准的Windows CE“Hello World”Win32应用程序封装到一个可以继承的类中。
Windows回调函数问题
创建封装器的根本问题在于Windows回调函数。这个函数必须是一个静态过程,这样才能将地址提供给RegisterClassEx
函数。为了解决这个问题,函数必须是一个回调过程。
LRESULT CALLBACK CBaseWindow::BaseWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
现在,Windows过程是类的一部分,问题是如何访问类的其他成员。静态成员函数在没有this
指针的情况下无法访问非静态成员函数。为了将所需的指针提供给窗口,可以将其包含在CREATESTRUCT
中。当你调用CreateWindow
时,该结构会自动填充(注意最后一个参数中的this
指针)。
hWnd = CreateWindow( szWindowClass, szTitle, WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, (void *)this );
在WM_CREATE
消息期间,这个CREATESTRUCT
在回调过程中可以通过lParam
获得。该指针会从结构中检索出来,并使用Windows函数SetWindowLong
存储为用户数据。
LRESULT CALLBACK CBaseWindow::BaseWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) { if ( msg == WM_CREATE ) { CBaseWindow *pObj = reinterpret_cast<CBaseWindow *> ((long)((LPCREATESTRUCT)lParam)->lpCreateParams); ::SetWindowLong( hwnd, GWL_USERDATA, (LONG)((LPCREATESTRUCT)lParam)->lpCreateParams ); . . .
在之后所有来自该窗口的消息中,调用GetWindowLong
将检索到this
指针。现在你可以自由访问你的整个类了。
这种方法也可以用于创建对话框。对于模态对话框,窗口回调过程在DialogBox
调用中指定。要获取对象指针给回调过程,请使用DialogBoxParam
。对象指针将是最后一个参数。
DialogBoxParam( GetInstance(), (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)pAboutWindow->BaseWndProc, (long)pAboutWindow );
要检索指针,只需在调用基窗口过程的WM_INITDIALOG
消息时获取lParam
即可。然后像之前一样保存指针。
. . . if ( msg == WM_INITDIALOG ) { CBaseWindow *pObj = reinterpret_cast<CBaseWindow *>(lParam); ::SetWindowLong( hwnd, GWL_USERDATA, lParam ); . . .
桌面版Windows和CE版Windows在窗口创建过程上有一个区别。在桌面上,WM_NCCREATE
是第一条消息,所以this
指针应该在处理它的过程中捕获。在Windows CE中,这条消息不存在。第一条消息是WM_CREATE
,所以必须使用它。
虽然这种方法有点复杂,但它避免了声明静态数据结构来映射窗口句柄到指向正确对象的指针。每个窗口在创建时,都会存储指向其拥有对象的指针。
继承
大多数窗口被创建为现有窗口的更专业化的特例。使用C++,对象可以利用这种关系。基窗口类提供一个静态函数,该函数调用一个虚函数。下面是基窗口类的声明
class CBaseWindow { public: CBaseWindow(){hInst=0;} ~CBaseWindow(){} HWND hWnd; // The main window handle BOOL DlgFlag; // True if object is a dialog window static LRESULT CALLBACK BaseWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ); protected: HINSTANCE hInst; // The current instance HWND hwndCB; // The command bar handle HINSTANCE GetInstance () const { return hInst; } virtual LRESULT WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, PBOOL pbProcessed ) { *pbProcessed = FALSE; return NULL; } };
这里您可以看到静态函数BaseWndProc
,Windows将使用它作为回调函数,以及虚函数WndProc
,它将被调用作为窗口的消息处理器。对于基窗口的函数,WndProc
表示没有消息被处理并返回。
有趣的地方从这里开始。基类本身不能用于窗口,但程序中的每个窗口现在都可以在其自己的类声明中继承基类。从此以后,管理this
指针以将代码对象与窗口关联的所有细节都隐藏起来了。这里主窗口类继承自基类。
class CMainWindow : public CBaseWindow { public: CMainWindow(){} ~CMainWindow(){} BOOL InitInstance( HINSTANCE hInstance, int nCmdShow ); protected: HWND CreateRpCommandBar( HWND ); ATOM MyRegisterClass( HINSTANCE, LPTSTR ); virtual LRESULT WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, PBOOL pbProcessed ); };
随着窗口需求的日益专业化,您可以创建一个基于旧类的新类,并更改行为以满足新需求。例如,CMainWindow
类的WndProc
函数替换了CBaseWindow
类中同名函数。
当您重写旧函数并创建新虚函数时,应该首先调用继承的类以获取继承的特性,然后按照您需要的方式更改处理方式。这里,主窗口的WndProc
在进行消息处理之前,调用了CBaseWindow
类的WndProc
。
LRESULT CMainWindow::WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, PBOOL pbProcessed ) { int wmId, wmEvent; // call the base class first LRESULT lResult = CBaseWindow::WndProc( hWnd, message, wParam, lParam, pbProcessed ); switch ( message ) { . . .
Windows与对话框
任何看过标准Win32程序并见过普通Windows回调函数的人都知道,case语句的末尾会看到这一行。
lResult = DefWindowProc( hwnd, msg, wParam, lParam );
如果消息被链中的一个虚函数处理,那么该函数将返回FALSE
,而DefWindowProc
将不会被调用。
如果处理的是对话框,规则会稍有不同。如果需要,DefWindowProc
会自动为对话框调用。如果消息被链中的某个函数处理,则返回TRUE
。
这(在我看来)是令人烦恼的,也是错误的根源。所以我试图通过在我的基类中进行补偿来稍微统一窗口和对话框的处理方式。为了做到这一点,我设置了一个标志来提醒自己这是否是为对话框设计的。这样所有窗口回调都可以编码得一样。这是静态基窗口回调函数。
LRESULT CALLBACK CBaseWindow::BaseWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) { // check to see if a copy of the 'this' pointer needs to be saved if ( msg == WM_CREATE ) { CBaseWindow *pObj = reinterpret_cast<CBaseWindow *> ((long)((LPCREATESTRUCT)lParam)->lpCreateParams); ::SetWindowLong( hwnd, GWL_USERDATA, (LONG)((LPCREATESTRUCT)lParam)->lpCreateParams ); pObj->hWnd = hwnd; pObj->DlgFlag = FALSE; if ( pObj->hInst == 0 ) pObj->hInst = MainWindow.hInst; } if ( msg == WM_INITDIALOG ) { CBaseWindow *pObj = reinterpret_cast<CBaseWindow *>(lParam); ::SetWindowLong( hwnd, GWL_USERDATA, lParam ); pObj->hWnd = hwnd; pObj->DlgFlag = TRUE; pObj->hInst = MainWindow.hInst; } BOOL bProcessed = FALSE; LRESULT lResult; // Retrieve the pointer CBaseWindow *pObj = WinGetLong<CBaseWindow *>( hwnd, GWL_USERDATA ); // call the virtual window procedure for the object stored in the // windows user data if ( pObj ) lResult = pObj->WndProc( hwnd, msg, wParam, lParam, &bProcessed ); else return ( pObj->DlgFlag ? FALSE : TRUE ); // message not processed if ( pObj->DlgFlag ) return bProcessed; // processing a dialog message return TRUE // if processed else if ( !bProcessed ) // If message was unprocessed and not a dialog, send it back to Windows. lResult = DefWindowProc( hwnd, msg, wParam, lParam ); return lResult; // processing a window message return FALSE // if processed } /* BaseWndProc */
使用基窗口过程
要将对象注册为窗口的回调函数,在注册类时,将BaseWndProc
设置为Windows回调函数指针。
ATOM CMainWindow::MyRegisterClass( HINSTANCE hInstance, LPTSTR szWindowClass ) { WNDCLASS wc; wc.lpfnWndProc = (WNDPROC)BaseWndProc; . . .
要用对象封装对话框,在调用DialogBoxParam
时,将BaseWndProc
作为Windows回调函数指针传递。
CAboutWindow *pAboutWindow = new( CAboutWindow ); DialogBoxParam( GetInstance(), (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)pAboutWindow->BaseWndProc, (long)pAboutWindow ); delete pAboutWindow;
结论
我已经基本介绍了如何在Windows CE设备上将Windows消息封装到对象中。其中大部分也适用于桌面版的Windows。这并没有将所有内容都封装到对象中,但将窗口与对象关联是比较困难且最常见的问题之一。因此,我已经完成了我想要的第一个入门应用程序:一个封装在类中的标准Windows CE“Hello World”Win32应用程序。
我计划继续介绍如何将此项目作为起点来创建一个基于对话框的应用程序。
一个有用的技巧:在CodeProject上寻找Niek Albers开发的Visual Studio Project Renamer。它在Embedded Visual C++ 3.0项目上也效果很好。
参考文献
- Steve Hanov,《C/C++ Users Journal》,2000年8月,第26页,A Lightweight Windows Wrapper。
- Reliable Software网站,http://www.relisoft.com/win32/index.htm,各种win32教程。