C++ 为什么WndProc的演员阵容不正确?
如果查看CONTROL.CPP,您将看到CONTROL::CONTROLNATIVEWINDOW::WndProc()。在这里,为了测试,我输出控件名。我有多个派生的控件类,它们重写GetName()方法。然而,它打印的唯一派生类是WINDOW,即使它也应该输出TEXTBOX 我不知道的是,如果LRESULT回调InternalWinProc()中的reinterpret_转换错误:C++ 为什么WndProc的演员阵容不正确?,c++,winapi,C++,Winapi,如果查看CONTROL.CPP,您将看到CONTROL::CONTROLNATIVEWINDOW::WndProc()。在这里,为了测试,我输出控件名。我有多个派生的控件类,它们重写GetName()方法。然而,它打印的唯一派生类是WINDOW,即使它也应该输出TEXTBOX 我不知道的是,如果LRESULT回调InternalWinProc()中的reinterpret_转换错误: LRESULT CALLBACK InternalWndProc(HWND hWnd, UINT message
LRESULT CALLBACK InternalWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
NativeWindow* window = reinterpret_cast<NativeWindow*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
if (window) {
Message msg = Message::Create(hWnd, message, IntPtr((void*)wParam), IntPtr((void*)lParam));
window->WndProc(&msg);
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
在基类中,控件构造函数未正确保存派生类型
当运行调试器并查看上面的行时,this有一个vptr,它将all指向CONTROL:。。。方法而不是相应的派生类。这让我觉得你不能像我这样把派生类的this指针保存在基类中
然后,当我在InternalWndProc函数中将其转换回NativeWindow*时,它给出了一个错误的转换,因此当
control->GetName();
执行于
Control::ControlNativeWindow::WndProc(Message * msg)
它正在错误地调用的方法
Window::GetName();
以下是代码的其余部分: 控制.H
#ifndef CONTROL_H
#define CONTROL_H
#include "IntPtr.h"
#include "CreateParams.h"
#include "Point.h"
#include "Size.h"
#include "NativeWindow.h"
class Control
{
public:
Control();
~Control();
virtual void CreateControl();
Control* GetParent() { return parent; }
void SetParent(Control* parent) { this->parent = parent; }
IntPtr GetHandle() { return window->GetHandle(); }
Point GetLocation() { return Point(x, y); }
void SetLocation(Point point);
Size GetSize() { return Size(width, height); }
void SetSize(Size size);
std::string GetText();
void SetText(std::string text);
void Show();
bool IsHandledCreated();
class ControlNativeWindow : public NativeWindow
{
public:
ControlNativeWindow(Control* control);
virtual std::string GetName() { return "CONTROLNATIVEWindow\n"; }
protected:
virtual void WndProc(Message* msg);
private:
Control* control;
};
virtual std::string GetName() { return "Control\n"; }
protected:
virtual void OnPaint();
virtual void OnTextChanged();
virtual void DefWndProc(Message* msg);
virtual void WndProc(Message* msg);
virtual void CreateHandle();
virtual CreateParams GetCreateParams();
private:
Control* parent;
ControlNativeWindow* window;
int x;
int y;
int width;
int height;
std::string text;
bool visible;
};
#endif // CONTROL_H
#ifndef NATIVE_WINDOW_H
#define NATIVE_WINDOW_H
#include <string>
#include "IntPtr.h"
class CreateParams;
class Message;
class NativeWindow
{
public:
NativeWindow();
~NativeWindow();
virtual void CreateHandle(CreateParams* cp);
IntPtr GetHandle() { return handle; }
virtual void DefWndProc(Message* msg);
virtual void WndProc(Message* msg);
virtual std::string GetName() { return "NATIVEWindow\n"; }
private:
IntPtr handle;
class WindowClass
{
public:
WindowClass(std::string className, int classStyle);
std::string GetClsName() { return className; }
int GetClassStyle() { return classStyle; }
private:
void registerClass();
friend NativeWindow;
NativeWindow* targetWindow;
std::string className;
int classStyle;
};
};
#endif // NATIVE_WINDOW_H
#ifndef WINDOW_H
#define WINDOW_H
#include <Windows.h>
#include "Control.h"
#include "ControlCollection.h"
class Window : public Control
{
public:
Window();
~Window();
void Show();
virtual std::string GetName() { return "Window\n"; }
protected:
virtual CreateParams GetCreateParams();
private:
ControlCollection controls;
};
#endif // WINDOW_H
#ifndef TEXTBOX_H
#define TEXTBOX_H
#include "Control.h"
class TextBox : public Control
{
public:
TextBox();
~TextBox();
virtual std::string GetName() { return "TextBox\n"; }
protected:
virtual void WndProc(Message* msg);
virtual void OnTextChanged();
virtual CreateParams GetCreateParams();
private:
void reflectCommand(Message* msg);
};
#endif // TEXTBOX_H
CONTROL.CPP
#include "Control.h"
#include "Utility.h"
#include <string>
#include "Message.h"
using namespace std;
Control::Control()
: x(0), y(0),
width(200), height(20), // fix this
visible(false)
{
window = new ControlNativeWindow(this);
}
Control::~Control()
{
}
void Control::CreateControl()
{
CreateHandle();
}
void Control::SetLocation(Point point)
{
x = point.X;
y = point.Y;
}
void Control::SetSize(Size size)
{
width = size.Width;
height = size.Height;
}
std::string Control::GetText()
{
if (IsHandledCreated())
{
HWND hWnd = (HWND)GetHandle().ToPointer();
int len = GetWindowTextLength(hWnd);
wchar_t* str = new wchar_t[len + 1]; // fix
GetWindowText(hWnd, str, len);
return string(WStringToAnsi(str));
}
return text;
}
void Control::SetText(string text)
{
if (IsHandledCreated())
{
wstring str = AnsiToWString(text);
SetWindowText((HWND)GetHandle().ToPointer(), str.c_str());
}
this->text = text;
}
void Control::Show()
{
visible = true;
}
bool Control::IsHandledCreated()
{
return window->GetHandle() == IntPtr::Zero;
}
void Control::OnPaint()
{
}
void Control::OnTextChanged()
{
}
void Control::DefWndProc(Message * msg)
{
window->DefWndProc(msg);
}
void Control::WndProc(Message * msg)
{
switch (msg->Msg)
{
case WM_PAINT:
OnPaint();
break;
case WM_DESTROY:
PostQuitMessage(0);
default:
DefWndProc(msg);
};
}
void Control::CreateHandle()
{
CreateParams cp = GetCreateParams();
SetLocation(Point(cp.GetX(), cp.GetY()));
window->CreateHandle(&cp);
}
CreateParams Control::GetCreateParams()
{
CreateParams cp;
cp.SetParent(parent->GetHandle());
cp.SetCaption(text);
return cp;
}
Control::ControlNativeWindow::ControlNativeWindow(Control* control)
{
wstring ws = AnsiToWString(control->GetName());
OutputDebugString(ws.c_str());
this->control = static_cast<Control*>(control);
ws = AnsiToWString(this->control->GetName());
OutputDebugString(ws.c_str());
}
void Control::ControlNativeWindow::WndProc(Message * msg)
{
// HERE IS THE ISSUE
// IT IS OUTPUTTING WINDOW ALWAYS
// HOWEVER, IT SHOULD OUTPUT THE CORRECT DERIVED CLASS
wstring ws = AnsiToWString(control->GetName());
OutputDebugString(ws.c_str());
control->WndProc(msg);
}
#include "NativeWindow.h"
#include <Windows.h>
#include <string>
#include "CreateParams.h"
#include "Message.h"
#include "Utility.h"
using namespace std;
LRESULT CALLBACK InternalWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
NativeWindow::NativeWindow() : handle(IntPtr::Zero)
{
}
NativeWindow::~NativeWindow()
{
}
void NativeWindow::CreateHandle(CreateParams* cp)
{
WindowClass windowClass(cp->GetClsName(), cp->GetClassStyle());
wstring wideClassName = AnsiToWString(windowClass.GetClsName());
wstring wideCaption = AnsiToWString(cp->GetCaption());
HWND hWnd = CreateWindowEx(
cp->GetExStyle(),
wideClassName.c_str(),
wideCaption.c_str(),
cp->GetStyle(),
cp->GetX(), cp->GetY(),
cp->GetWidth(), cp->GetHeight(),
(HWND)cp->GetParent().ToPointer(),
nullptr,
nullptr,
nullptr
);
if (!hWnd)
{
MessageBox(nullptr, L"Call to CreateWindow Failed", L"FAIL", MB_OK);
return;
}
handle = hWnd;
windowClass.targetWindow = this;
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)this);
}
void NativeWindow::WndProc(Message* msg)
{
DefWndProc(msg);
}
void NativeWindow::DefWndProc(Message * msg)
{
DefWindowProc((HWND)msg->HWnd.ToPointer(), (LRESULT)msg->Result.ToPointer(), (WPARAM)msg->WParam.ToPointer(), (LPARAM)msg->LParam.ToPointer());
}
NativeWindow::WindowClass::WindowClass(std::string className, int classStyle)
{
this->className = className;
this->classStyle = classStyle;
registerClass();
}
void NativeWindow::WindowClass::registerClass()
{
WNDCLASSEX wndclass;
wndclass.cbSize = sizeof(WNDCLASSEX);
wndclass.style = classStyle;
wndclass.lpfnWndProc = InternalWndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = nullptr;
wndclass.hIcon = LoadIcon(nullptr, MAKEINTRESOURCE(IDI_APPLICATION));
wndclass.hCursor = LoadCursor(nullptr, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wndclass.lpszMenuName = nullptr;
wstring ws = AnsiToWString(className);
wndclass.lpszClassName = ws.c_str();
wndclass.hIconSm = LoadIcon(nullptr, MAKEINTRESOURCE(IDI_APPLICATION));
WNDCLASSEX wcex;
wcex.lpszClassName = ws.c_str();
bool found = GetClassInfoEx(nullptr, ws.c_str(), &wcex);
if (found)
return;
if (!RegisterClassEx(&wndclass))
{
DWORD dw = GetLastError();
if (dw == ERROR_CLASS_ALREADY_EXISTS)
{
MessageBox(nullptr, L"Class already exists", L"SUCCESS", MB_OK);
}
else
{
MessageBox(nullptr, L"Call to RegisterClassEx Failed", L"FAIL", MB_OK);
}
}
}
LRESULT CALLBACK InternalWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
NativeWindow* window = reinterpret_cast<NativeWindow*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
if (window) {
Message msg = Message::Create(hWnd, message, IntPtr((void*)wParam), IntPtr((void*)lParam));
window->WndProc(&msg);
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
C++对象在基类的构造函数完成后不会成为派生类的成员。简而言之,在控件构造函数离开之前,对象不是textbox类型
考虑一个具有vtable的对象:class A { public: A() x() { doit(); } virtual void doit(); private: int x; }
和派生类:
class B { public: virtual void doit(); private: std::string myname; }
B对象在进入A的构造函数体时如下所示:
+--------------------+
| A vtable | // A vtable
+--------------------+
| int x | // A's member (0 from initializer list)
+--------------------+
| std::string myname | // B's member (uninit)
+--------------------+
注意,如果执行B::doit(),它将访问未初始化的myname
B的构造函数将把vtable指针重新分配给B vtable并运行myname的构造函数,但那是在我们已经执行了A构造函数主体之后。Java的做法与其他方法不同,因为反射要求对象在运行时不更改类型
因此,调用对象的一个虚拟方法不会引用派生类型的重写
对象通常有一个<>代码> init < /C>方法,用户在构造后需要使用它来允许派生类参与初始化。
一个C++对象直到基类的构造函数完成后才成为派生类的成员。简而言之,在控件构造函数离开之前,对象不是textbox类型 考虑一个具有vtable的对象:class A { public: A() x() { doit(); } virtual void doit(); private: int x; }
和派生类:
class B { public: virtual void doit(); private: std::string myname; }
B对象在进入A的构造函数体时如下所示:
+--------------------+
| A vtable | // A vtable
+--------------------+
| int x | // A's member (0 from initializer list)
+--------------------+
| std::string myname | // B's member (uninit)
+--------------------+
注意,如果执行B::doit(),它将访问未初始化的myname
B的构造函数将把vtable指针重新分配给B vtable并运行myname的构造函数,但那是在我们已经执行了A构造函数主体之后。Java的做法与其他方法不同,因为反射要求对象在运行时不更改类型
因此,调用对象的一个虚拟方法不会引用派生类型的重写
通常,对象具有某种类型的
init
方法,用户需要在构造后使用该方法,以允许派生类参与初始化。这是一堵代码墙,但您指出的关于代码的两个观察结果可能会很麻烦。(1) 它不会从window->WndProc(&msg)返回值代码>。(2) 之后它总是调用默认处理。另外,Windows API现在通过直接支持窗口子类化。谢谢@Alf,但这是它应该如何工作的。这与C#.NET的工作方式类似。有两个类表示一个窗口,它们是不相关的。因此,为一个消息处理程序强制转换另一个消息处理程序注定会失败。NET不会犯这个错误,所有窗口类都派生自控件。NativeWindow不参与该继承方案,它始终是包装的,而不是继承的。封装和抽象之间有很大区别。@keelerjr12:这段代码只能用于非常有限的一组特殊情况。希望你知道自己在做什么。尽管如此,要使它在总体上正确是如此简单,以至于我强烈怀疑你是否正确。也就是说,要想成功做到这一点,你很可能需要抛弃“它应该如何工作”的想法。“相似”的想法也很好。@HansPassant,没错,ControlNativeWindow是用C#内部密封包装的;但是,它确实继承自NativeWindow。在我的例子中,Window实际上是C#NET中的一个表单。然而,这并不能解释我的问题。请看:这是一堵代码墙,但是关于您指出的代码的两个观察结果可能会很麻烦。(1) 它不会从window->WndProc(&msg)返回值代码>。(2) 之后它总是调用默认处理。另外,Windows API现在通过直接支持窗口子类化。谢谢@Alf,但这是它应该如何工作的。这与C#.NET的工作方式类似。有两个类表示一个窗口,它们是不相关的。因此,为一个消息处理程序强制转换另一个消息处理程序注定会失败。NET不会犯这个错误,所有窗口类都派生自控件。NativeWindow不参与该继承方案,它始终是包装的,而不是继承的。封装和抽象之间有很大区别。@keelerjr12:这段代码只能用于非常有限的一组特殊情况。希望你知道自己在做什么。尽管如此,要使它在总体上正确是如此简单,以至于我强烈怀疑你是否正确。也就是说,要想成功做到这一点,你很可能需要抛弃“它应该如何工作”的想法。“相似”的想法也很好。@HansPassant,没错,ControlNativeWindow是用C#内部密封包装的;但是,它确实继承自NativeWindow。在我的例子中,Window实际上是C#NET中的一个表单。然而,这并不能解释我的问题。请看:这同样适用于纯虚拟函数吗?是的。纯虚方法应该指向调用std::term的函数