Win32 C++ 在类中创建窗口和过程
Posted
技术标签:
【中文标题】Win32 C++ 在类中创建窗口和过程【英文标题】:Win32 C++ Create a Window and Procedure Within a Class 【发布时间】:2013-11-28 01:59:16 【问题描述】:前置文本/问题
我正在尝试制作一个相当简单的工具来帮助调试变量值。我的目标是让它在课堂上完全独立。最终产品我可以使用类中的函数,例如 ShowThisValue(whatever)。
我遇到的问题是,如果可能的话,我想不出在课堂上使用该程序。这是简短的版本,有问题。
-代码于 2013 年 11 月 29 日再次更新- -我现在已经把它放在自己的项目中了。
[main.cpp]
viewvars TEST; // global
TEST.CreateTestWindow(hThisInstance); // in WinMain() right before ShowWindow(hwnd, nFunsterStil);
[viewvars.h] 整个更新
class viewvars
private:
HWND hWindow; // the window, a pointer to
LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK ThisWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
public:
viewvars(); // blank constructor
int CreateTestWindow(HINSTANCE hInst);
;
// blank constructor
viewvars::viewvars()
// create the window
int viewvars::CreateTestWindow(HINSTANCE hInst)
// variables
char thisClassName[] = "viewVars";
MSG msg;
WNDCLASS wincl;
// check for class info and modify the info
if (!GetClassInfo(hInst, thisClassName, &wincl))
wincl.style = 0;
wincl.hInstance = hInst;
wincl.lpszClassName = thisClassName;
wincl.lpfnWndProc = &ThisWindowProc;
wincl.cbClsExtra = 0;
wincl.cbWndExtra = 0;
wincl.hIcon = NULL;
wincl.hCursor = NULL;
wincl.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;
wincl.lpszMenuName = NULL;
if (RegisterClass(&wincl) == 0)
MessageBox(NULL,"The window class failed to register.","Error",0);
return -1;
// create window
hWindow = CreateWindow(thisClassName, "Test", WS_POPUP | WS_CLIPCHILDREN, 10, 10, 200, 200, NULL, NULL, hInst, this);
if (hWindow == NULL)
MessageBox(NULL,"Problem creating the window.","Error",0);
return -1;
// show window
ShowWindow(hWindow, TRUE);
// message loop
while (GetMessage(&msg, hWindow, 0, 0))
TranslateMessage(&msg);
DispatchMessage(&msg);
// then quit window?
DestroyWindow(hWindow);
hWindow = NULL;
return msg.wParam;
// window proc
LRESULT CALLBACK viewvars::ThisWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
MessageBox(NULL,"Has it gone this far?","Bench",0);
// variable
viewvars *view;
// ????
if (message == WM_NCCREATE)
CREATESTRUCT *cs = (CREATESTRUCT*)lParam;
view = (viewvars*) cs->lpCreateParams;
SetLastError(0);
if (SetWindowLongPtr(hwnd, GWL_USERDATA, (LONG_PTR) view) == 0)
if (GetLastError() != 0)
MessageBox(NULL,"There has been an error near here.","Error",0);
return FALSE;
else
view = (viewvars*) GetWindowLongPtr(hwnd, GWL_USERDATA);
if (view) return view->WindowProc(message, wParam, lParam);
MessageBox(NULL,"If shown, the above statement did not return, and the statement below did.","Error",0);
return DefWindowProc(hwnd, message, wParam, lParam);
LRESULT viewvars::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
// you can access non-static members in here...
MessageBox(NULL,"Made it to window proc.","Error",0);
switch (message)
case WM_PAINT:
return 0;
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
default:
MessageBox(NULL,"DefWindowProc Returned.","Error",0);
return DefWindowProc(hWindow, message, wParam, lParam);
break;
消息框按以下顺序显示:
已经走到这一步了吗? 进入窗口进程 DefWindowProc 返回 已经走到这一步了吗? // 重复? 进入窗口进程 DefWindowProc 返回 创建窗口时出现问题感谢到目前为止的帮助。你知道问题可能出在哪里吗?
【问题讨论】:
将 windowproc 方法设为静态。 可以使用动态 thunking 技术:***.com/a/18326813/1768303 不要为每条消息调用 MessageBox。这会造成重入和一般的混乱。此外,一些消息预计不会产生。屈服会导致问题。 没错,我只是用这些消息来看看它在做什么。它一开始没有用,所以我只是看看它在做什么。就这件事而言,它相当于一次调试每个步骤。我可以打开控制台和 cout 而不是 MessageBox,并得到完全相同的结果。忽略所有消息 Create Window is not working... 你没有设置 wincl.cbSize。 【参考方案1】:要将非静态类方法用作窗口过程需要动态分配的 thunk,这是一种高级技术,我不会在此介绍。
另一种方法是将类方法声明为static
,然后它将作为窗口过程工作。当然,作为static
方法,它不能再在没有实例指针的情况下访问非静态类成员。要获取该指针,您可以让类将其this
指针传递给CreateWindow/Ex()
的lpParam
参数,然后窗口过程可以从WM_NCCREATE
消息中提取该指针并使用@987654327 将其存储在窗口中@。之后,后续消息可以使用GetWindowLong/Ptr(GWL_USERDATA)
检索该指针,从而能够访问该对象的非静态成员。例如:
class viewvars
private:
HWND hWindow;
LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK ThisWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
public:
int CreateTestWindow(HINSTANCE hInst);
;
int viewvars::CreateTestWindow(HINSTANCE hInst)
WNDCLASS wincl;
if (!GetClassInfo(hInst, thisClassName, &wincl))
...
wincl.hInstance = hInst;
wincl.lpszClassName = thisClassName;
wincl.lpfnWndProc = &ThisWindowProc;
if (RegisterClass(&wincl) == 0)
return -1;
hWindow = CreateWindow(..., hInst, this);
if (hWindow == NULL)
return -1;
...
MSG msg;
while (GetMessage(&msg, hWindow, 0, 0))
TranslateMessage(&msg);
DispatchMessage(&msg);
DestroyWindow(hWindow);
hWindow = NULL;
return msg.wParam;
LRESULT CALLBACK viewvars::ThisWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
viewvars *view;
if (message == WM_NCCREATE)
CREATESTRUCT *cs = (CREATESTRUCT*) lParam;
view = (viewvars*) cs->lpCreateParams;
SetLastError(0);
if (SetWindowLongPtr(hwnd, GWL_USERDATA, (LONG_PTR) view) == 0)
if (GetLastError() != 0)
return FALSE;
else
view = (viewvars*) GetWindowLongPtr(hwnd, GWL_USERDATA);
if (view)
return view->WindowProc(message, wParam, lParam);
return DefWindowProc(hwnd, message, wParam, lParam);
LRESULT viewvars::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
// you can access non-static members in here...
switch (message)
case WM_PAINT:
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWindow, message, wParam, lParam);
【讨论】:
+1 我在使用未包装(ATL、MFC、WTL 等)时经常执行此操作的代码。Windows API 窗口代码与此方法几乎相同,坦率地说,如果可以的话,我会再投票十几次。值得一提的是,在使用对话框过程(不同的窗口长常量等)时模板略有不同,但前提仍然相同。很好的答案。 谢谢,我想这行得通。不过,现在我遇到了一个问题,它挂在了注册课程部分。我的注册课程段落与您发布的相同。有什么我需要更改或添加的吗?我以前没有注册过它。我也有几个问题。那么它在哪里说访问成员信息,是指类中的公共/私有变量吗?我添加的其他功能呢?谢谢。 您是否填写了所有WNDCLASS
成员,而不仅仅是我展示的少数成员? RegisterClass()
挂起是不寻常的。是的,我指的是所有类成员(变量、方法等),公共的和私有的,没关系(static
过程本身就是一个成员,所以它可以访问所有这些成员)。
我完成了WNDCLASS成员// check for class info and modify the info if (!GetClassInfo(hInst, thisClassName, &wincl)) wincl.style = CS_DROPSHADOW; wincl.hInstance = hInst; wincl.lpszClassName = thisClassName; wincl.lpfnWndProc = &ThisWindowProc; wincl.cbClsExtra = 0; wincl.cbWndExtra = 0; wincl.hIcon = NULL; wincl.hCursor = NULL; wincl.hbrBackground = (HBRUSH)COLOR_BTNSHADOW; wincl.lpszMenuName = NULL; if (RegisterClass(&wincl) == 0) return -1;
-CONTINUED-
CreateWindow() 现在给我带来了麻烦。有任何想法吗? // create window
hWindow = CreateWindow(thisClassName, "Test", WS_POPUP | WS_CLIPCHILDREN, 10, 10, 200, 200, NULL, NULL, hInst, this); if (hWindow == NULL) MessageBox(0,0,0,0); return -1;
【参考方案2】:
主消息循环不能在你的类中,特别是不能在“CreateTestWindow”函数中,因为在你的线程收到使GetMessage
返回0的WM_QUIT
消息之前,你不会从该函数返回。
这是viewvars
类的简单实现。要点:
编辑:
根据陈先生的建议,早先将 HWND 绑定到对象(在 WM_NCCREATE 中)以允许消息处理程序作为方法在窗口创建期间。我更改了创建样式,以显示窗口并能够移动它。
// VIEWVARS.H
class viewvars
public:
static viewvars* CreateTestWindow( HINSTANCE hInstance );
viewvars() : m_hWnd( 0 )
~viewvars();
private:
static LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
static const char * m_pszClassName;
HWND m_hWnd;
;
// VIEWVARS.CPP
#include "viewvars.h"
const char * viewvars::m_pszClassName = "viewvars";
viewvars * viewvars::CreateTestWindow( HINSTANCE hInst )
WNDCLASS wincl;
if (!GetClassInfo(hInst, m_pszClassName, &wincl))
wincl.style = 0;
wincl.hInstance = hInst;
wincl.lpszClassName = m_pszClassName;
wincl.lpfnWndProc = WindowProc;
wincl.cbClsExtra = 0;
wincl.cbWndExtra = 0;
wincl.hIcon = NULL;
wincl.hCursor = NULL;
wincl.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
wincl.lpszMenuName = NULL;
if (RegisterClass(&wincl) == 0)
MessageBox(NULL,"The window class failed to register.","Error",0);
return 0;
viewvars * pviewvars = new viewvars;
HWND hWnd = CreateWindow( m_pszClassName, "Test", WS_VISIBLE | WS_OVERLAPPED, 50, 50, 200, 200, NULL, NULL, hInst, pviewvars );
if ( hWnd == NULL )
delete pviewvars;
MessageBox(NULL,"Problem creating the window.","Error",0);
return 0;
return pviewvars;
LRESULT CALLBACK viewvars::WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
switch ( uMsg )
case WM_NCCREATE:
CREATESTRUCT * pcs = (CREATESTRUCT*)lParam;
viewvars * pviewvars = (viewvars*)pcs->lpCreateParams;
pviewvars->m_hWnd = hwnd;
SetWindowLongPtr( hwnd, GWLP_USERDATA, (LONG)pcs->lpCreateParams );
return TRUE;
case WM_DESTROY:
viewvars * pviewvars = (viewvars *)GetWindowLongPtr( hwnd, GWLP_USERDATA );
if ( pviewvars ) pviewvars->m_hWnd = 0;
break;
default:
return DefWindowProc( hwnd, uMsg, wParam, lParam );
return 0;
viewvars::~viewvars()
if ( m_hWnd ) DestroyWindow( m_hWnd );
最后,一个“主要”示例,但请注意,这里无法结束该过程。这应该通过常规代码(另一个窗口)来处理。
// MAIN.CPP
#include <Windows.h>
#include "viewvars.h"
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
viewvars * pviewvars = viewvars::CreateTestWindow( hInstance );
if ( pviewvars == 0 ) return 0;
BOOL bRet;
MSG msg;
while( (bRet = GetMessage( &msg, 0, 0, 0 )) != 0)
if (bRet == -1)
// handle the error and possibly exit
else
TranslateMessage(&msg);
DispatchMessage(&msg);
delete pviewvars;
return 0;
【讨论】:
现在它更有意义了,这是一个工作示例。谢谢。 谢谢你。祝“viewvars”中的剩余工作好运:-) 请注意,由于您直到CreateWindow
返回后才设置GWLP_USERDATA
,这意味着您无法更改自定义作为窗口创建的一部分发送的消息的行为。也许这并不重要,但只是说出来,因为它可能会在以后咬你。
@RaymondChen 谢谢。确实。已更新。
在 WM_NCCREATE 消息中在 return
之前添加 SetWindowPos(hwnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
以防止第一个窗口出现时窗口背景闪烁。【参考方案3】:
不幸的是,将实例方法用作 WndProc 的 C 样式回调函数将不起作用。至少不是以任何直接的方式。
它不能这样工作的原因是实例方法需要传入this
指针(以指向实例),并且调用WndProc 的代码无法正确设置该指针。 Win32 API 最初设计时考虑了 C,因此这是您必须使用一些变通方法的领域。
解决此问题的一种方法是创建一个静态方法来充当窗口进程并将消息发送到您的类实例。必须注册类实例(读取添加到静态集合中),以便静态方法知道将 WndProc 消息分派给实例。实例将在构造函数中向静态调度程序注册自己,并在析构函数中删除自己。
当然,只有当您的 WndProc 处理程序需要调用其他实例成员函数或访问成员变量时,才需要所有注册和注销以及调度开销。否则,您可以将其设为静态,然后就完成了。
【讨论】:
您不需要静态集合来保存类实例指针。当类创建它的窗口时,它可以将它的this
指针传递给CreateWindow/Ex()
的lpParam
参数,然后窗口过程可以从WM_NCCREATE
消息中提取该指针并使用SetWindowLong/Ptr(GWL_USERDATA)
将其存储在窗口中.之后,后续消息可以使用GetWindowLong/Ptr(GWL_USERDATA)
检索该指针,并根据需要对其调用非静态方法。
@RemyLebeau - 好点!自从我不得不处理这个问题以来已经有一段时间了,所以我大部分时间都在摆脱旧的记忆【参考方案4】:
在 CreateWindow 期间调用您的窗口过程。您将 hWindow 传递给 DefWindowProc,但 hWindow 仅在 CreateWindow 返回后设置 - 因此您将 DefWindowProc 传递给垃圾窗口句柄。
我看不出有什么好的方法。您可以通过将 WindowProc 更改为:
在窗口过程中设置 hWindowLRESULT WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(添加了 hwnd 参数),将调用改为:
return view->WindowProc(hwnd, message, wParam, lParam);
像这样创建窗口:
hWindow = NULL;
hWindow = CreateWindow(..., hInst, this);
if (hWindow == NULL)
return -1;
(第一个任务是确保 hWindow 已初始化;第二个任务是在调用窗口过程后 CreateWindow 失败的情况下),并将其添加到 WindowProc 的开头:
if(!this->hWindow)
this->hWindow = hwnd;
【讨论】:
你是对的,这似乎是问题所在。我看彻底扔了它,找不到可以设置hWindow的地方。知道我可以在哪里设置吗?谢谢。 我也没有看到好的解决方案。也许将 HWND 作为第一个参数传递给 WindowProc。 (一旦 hWindow 被初始化,这将是多余的)。 我试过了,但由于静态,它最终给了我错误。我开始认为,如果可以做到这一点,那几乎是不可能的。我还查看了一个(10 年前的)Win32 的 C 包装类,最终出现了无休止的内存泄漏(如用户所述)。该代码与我所拥有的代码半相似。嗯…… 我用我的解决方案所需的代码更改更新了我的答案。 谢谢,我已经/有它半工作。起初窗口会出现,但会进入某种无限循环,整个窗口和关闭按钮闪烁。无论如何,现在我无法执行该功能。在主文件中,没有任何内容通过主窗口的 CreateWindow()/ShowWindow()。hwnd = CreateWindowEx (0, szClassName, "Windows App", WS_OVERLAPPEDWINDOW, ..., HWND_DESKTOP, NULL, hThisInstance, NULL);
ShowWindow (hwnd, nFunsterStil);
TEST.CreateTestWindow(hThisInstance);
主窗口显示,从不进行测试。有任何想法吗?谢谢。【参考方案5】:
单步调试器中的代码。当你到达线路时
MessageBox(NULL,"DefWindowProc Returned.","Error",0);
return DefWindowProc(hWindow, message, wParam, lParam);
你会看到一些错误:hWindow
是垃圾。您正在使用未初始化的变量。
【讨论】:
以上是关于Win32 C++ 在类中创建窗口和过程的主要内容,如果未能解决你的问题,请参考以下文章
如何在visual studio2008中创建,编译和运行C++程序,
在启用 CLR 但没有 CRT 的 VC++ 中创建 Win32 dll
c_cpp 在win32 gui应用程序中创建一个控制台窗口