keelerjr12 keelerjr12 - 2 months ago 15
C++ Question

Why incorrect cast in WndProc?

If you look at CONTROL.CPP, you'll see CONTROL::CONTROLNATIVEWINDOW::WndProc(). Here, for a test, I am outputting the CONTROL name. I have multiple derived classes of CONTROL that override the GetName() method. However, the only derived class it ever prints is WINDOW, even though it should output TEXTBOX as well.

What I can't tell is that if the reinterpret_cast in the LRESULT CALLBACK InternalWinProc() is casting in incorrectly:

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);
}


Or, the line

window = new ControlNativeWindow(this);


in the base class CONTROL constructor is not saving the derived type this correctly.

When running through the debugger and looking at the line above, the this has a vptr pointing all to CONTROL::... methods instead of the respective derived class. Which makes me think that you can't save the this pointer of a derived class in the base class like I'm doing.

And then when I go to cast it back to NativeWindow* in the InternalWndProc function, it is giving a bad cast so when the line

control->GetName();


executes in the

Control::ControlNativeWindow::WndProc(Message * msg)


method it is incorrectly calling

Window::GetName();





Here is the rest of the code:

CONTROL.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


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);
}


NATIVEWINDOW.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


NATIVEWINDOW.CPP

#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);
}


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


TEXTBOX.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

Answer

A C++ object doesn't become a member of a derived class until after the base class' constructor finishes. In short, the object is not textbox type until after the control constructor leaves.

Consider an object with a vtable:

class A { public: A() x() { doit(); } virtual void doit(); private: int x; }

and a derived class:

class B { public: virtual void doit(); private: std::string myname; }

A B object upon entering A's constructor body is like this:

+--------------------+
| A vtable           | // A vtable
+--------------------+
| int x              | // A's member (0 from initializer list)
+--------------------+
| std::string myname | // B's member (uninit)
+--------------------+

Note that if B::doit() executes, it will access the uninitialized myname.

B's constructor will reassign the vtable pointer to B vtable and run myname's constructor, but that's after we've already executed the A constructor body. Java does this differently, among others, since reflection requires the object not to change type at runtime.

Calling one of the object's virtual methods therefore doesn't refer to the derived type's override yet.

Often, objects have an init method of some kind that the user is required to use after construction to allow derived classes to participate in initialization.

Comments