好的,这是一个非常奇怪的问题。我想首先说我不是c ++的初学者,我当然不是先进的。我在中间的某个地方。我正在尝试做的是创建Win32 API的C ++ OOP包装器库(dll)。这是我的图书馆的课程。我用Mingw使用命令编译它:
g++ -shared -o bin\win32oop.dll src\Application.cpp src\Form\Form.cpp -Wall
SRC \ Application.h:
#ifndef WOOP_APPLICATION_H_
#define WOOP_APPLICATION_H_
namespace Woop
{
class Application
{
public:
bool Init(void);
virtual bool OnInit(void);
};
}
#endif // WOOP_APPLICATION_H_
SRC \ Application.cpp
#include <windows.h>
#include "Application.h"
#include "Form\Form.h"
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
namespace Woop
{
bool Application::Init(void)
{
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = GetModuleHandle(NULL);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = "woop";
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(RegisterClassEx(&wc) == 0)
{
MessageBox(NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);
return false;
}
this->OnInit();
return true;
}
bool Application::OnInit(void)
{
return true;
}
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
Woop::Form *wnd = 0;
if (uMsg == WM_NCCREATE)
{
SetWindowLong (hwnd, GWL_USERDATA, long((LPCREATESTRUCT(lParam))->lpCreateParams));
}
wnd = (Woop::Form *)(GetWindowLong (hwnd, GWL_USERDATA));
if (wnd) return wnd->WndProc(hwnd, uMsg, wParam, lParam);
return ::DefWindowProc (hwnd, uMsg, wParam, lParam);
}
SRC \表格\ Form.h
#ifndef WOOP_FORM_FORM_H_
#define WOOP_FORM_FORM_H_
namespace Woop
{
class Form
{
public:
bool Show(void);
virtual LRESULT WndProc(HWND, UINT, WPARAM, LPARAM);
protected:
HWND _handle;
};
}
#endif // WOOP_FORM_FORM_H_
SRC \表格\ Form.cpp
#include <windows.h>
#include "Form.h"
namespace Woop
{
bool Form::Show(void)
{
_handle = CreateWindowEx(WS_EX_CLIENTEDGE, "woop", "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, GetModuleHandle(NULL), this);
if(_handle == NULL)
{
MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);
return false;
}
ShowWindow(_handle, SW_SHOWNORMAL);
return true;
}
LRESULT Form::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
这是我用以下方法测试库的程序:
class SampleApp : public Woop::Application
{
bool OnInit(void)
{
Form form;
form.Show();
return true;
}
};
INT APIENTRY WinMain(HINSTANCE, HINSTANCE, LPSTR, INT)
{
SampleApp application;
if(application.Init() == false) return 0;
MSG Msg;
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return 0;
}
好吧,现在问题。您是否在Form类中看到了虚拟Window Procedure?如果我从声明中删除虚拟,程序编译并运行正常。但是当我把它添加回来时,它会崩溃。臭名昭着的“不发送”对话框出现了。我不确定它什么时候崩溃,我会试着用MessageBox()来解决这个问题(大声笑,这是我学习如何用gdb调试的结果)。我正在努力使它能够创建一个类如LoginForm并从Form派生并覆盖Window Procedure。我希望我能够很好地解释这个问题:D。这可能是编译器错误或我的愚蠢:P。无论如何,提前谢谢。
答案 0 :(得分:7)
问题在于:
bool OnInit(void)
{
Form form;
form.Show();
return true;
}
返回此方法时会破坏表单对象
因此,调用Show()时存储的this
指针不再有效。
_handle = CreateWindowEx(WS_EX_CLIENTEDGE, "woop", "", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL,
GetModuleHandle(NULL),
/* Here ----> */ this
);
当你尝试执行调度时,它会被搞砸,因为它正在使用this
指针来计算要调用的虚函数的地址。
它是虚拟的,而不是虚拟的原因是虚拟方法地址是在运行时计算的,而正常的方法地址是在编译时种植的。
在计算虚方法的地址时,this
指针以某种方式被解引用(在这种情况下会导致UB),但由于该对象已被破坏,该地址的数据已经被重新使用所以你为这个函数获得的地址是一些随机垃圾并且调用它永远不会好。
一个简单的解决方案是使表单成为应用程序对象的一部分 因此它的生命周期与应用程序相同:
class SampleApp : public Woop::Application
{
Form form;
bool OnInit(void)
{
form.Show();
return true;
}
};
答案 1 :(得分:2)
wc.lpfnWndProc = WndProc;
这在一般情况下不起作用,尽管 WndProc所在的并不明显。 Windows不会提供实例方法在进行回调时所需的“this”指针。您现在正在使用它,因为您不能在Form :: WndProc()方法中访问Form类的任何成员。它在没有虚拟关键字的情况下偶然工作,但一旦你开始接触成员,运气就会很快消失。这会以AccessViolation异常爆炸。
您需要将Form :: WndProc()方法设为静态方法。
将其设为虚拟将要求您编写将HWND映射到Form实例的代码。这是包装Win32 API的任何类库的一个非常标准的功能。没有必要重新发明那个轮子有很多价值。
答案 2 :(得分:0)
我不知道这是否是问题,但虚函数总是间接调用。这意味着在调用虚函数之前访问对象以读取虚函数表。这意味着如果对象被覆盖(已经删除,堆或堆栈上的缓冲区溢出等),虚拟方法比其他方法更容易崩溃。
这尤其正确,因为大多数成员变量在损坏时不会导致崩溃,因此有时您可以轻松监视堆/堆栈损坏问题。
BTW:有时候用gdb开始调试非常简单:只需将它加载到gdb(gdb myprogram)中并输入run即可。当程序崩溃时,使用“bt”获得回溯,你应该看看是否在虚拟方法内部或者在调用它时发生了崩溃。