Forward Windows messages to a derived class's virtual CallWindowProc() method

Started by
20 comments, last by Kylotan 6 years, 9 months ago

Basically my question is about what is going on here -> https://github.com/jjuiddong/Introduction-to-3D-Game-Programming-With-DirectX11/blob/master/Common/d3dApp.cpp

 

He create a D3DApp framework from which other classes have to inherit in order to get a Window, a Renderer and a GameTimer.

Now as you know Windows CallWindowProc() function require that CALLBACK macro in order for it to work, so since the MsgProc() in the code above is a virtual method inside the class, the author of the code use a trick to forward windows messages into it from outside the class


#include "d3dApp.h"
#include <WindowsX.h>
#include <sstream>

namespace
{
	// This is just used to forward Windows messages from a global window
	// procedure to our member function window procedure because we cannot
	// assign a member function to WNDCLASS::lpfnWndProc.
	D3DApp* gd3dApp = 0;
}

LRESULT CALLBACK
MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	// Forward hwnd on because we can get messages (e.g., WM_CREATE)
	// before CreateWindow returns, and thus before mhMainWnd is valid.
	return gd3dApp->MsgProc(hwnd, msg, wParam, lParam);
}

D3DApp::D3DApp(HINSTANCE hInstance)

{
	// Get a pointer to the application object so we can forward 
	// Windows messages to the object's window procedure through
	// the global window procedure.
	gd3dApp = this;
}

 

Ok, I kind of get what's going on, my question is, if I inherit from D3DApp my derived class is going to call D3DApp constructor, in which case, what will be the value of "this"?

The derived class adress? (that would allow for re-defining the virtual member function from the derived class and that's how I wish it is)

Also, I don't quite get the need of wrapping the D3DApp* into an empty namespace, is this another trick? What it is achieving?

Furthermore, what would happen if I where to do something strange and derive two different classes from D3DApp? What I mean is, is the gd3dApp pointer a global variable or each derived class get it's own? Doesn't seems the case since is not even part of the class...

Much confusion :D

Advertisement

A few things:

1) Yes, gd3dApp is a global. Hence the 'g' prefix. :P

2) The global isn't actually necessary - Windows allows you to attach a pointer of an arbitrary type to a window, either at construction time or at any point later", albeit at the cost of type-safety. Look up "SetWindowLongPtr". Mind you, the point about not assigning a member function to the window still holds, though, because of how pointers to member functions work in C++.

3) The anonymous namespace is used to prevent the global from being accessible outside the source file in which it is defined. In C we would do this using the "static" keyword, but anonymous namespaces are how modern C++ accomplishes this.

4) Classes are constructed in order of inheritance hierarchy - and destructed in the opposite order. So the base class (D3DApp) would be constructed first, assigning the "this" pointer of the class to the global.

5) If you were to have two classes deriving from D3DApp, and you would instantiate both of them, the global would be set to the address of whichever one was most recently constructed without fanfare. You can probably see why this could be dangerous...

5 minutes ago, MarcusAseth said:

Ok, I kind of get what's going on, my question is, if I inherit from D3DApp my derived class is going to call D3DApp constructor, in which case, what will be the value of "this"?

 

During execution of the constructor for type T, the dynamic type of "this" is T*. That is, "this" is always a pointer to a type of the currently-executing constructor within that constructor. Even if the object will ultimately be some more-derived type.

So, in the D3DApp constructor, "this" will be a D3DApp*. In YourApp's constructor, "this" will be YourApp*.

8 minutes ago, MarcusAseth said:

The derived class adress? (that would allow for re-defining the virtual member function from the derived class and that's how I wish it is)

As above, no. And even if it were true that wouldn't allow "re-defining" virtual member functions in that constructor. Functions are not fields of any given class instance, so you couldn't do something like "this->myFunction = newVersionOfThatFunction," which is what it sounds like you were hoping to do.

10 minutes ago, MarcusAseth said:

Also, I don't quite get the need of wrapping the D3DApp* into an empty namespace, is this another trick? What it is achieving? Scoping reason, to keep that variable name confined to that file?

This is known as an unnamed (or anonymous) namespace. The purpose, here, is to prevent the global variable declared in that namespace from being accessible outside the translation unit it's defined in. So yes, basically to keep that global confined to that file.

12 minutes ago, MarcusAseth said:

Furthermore, what would happen if I where to do something strange and derive two different classes from D3DApp? What I mean is, is the gd3dApp pointer a global variable or each derived class get it's own?

In general, nothing bad. In this particular scenario, the gd3dApp pointer is assigned to in the constructor of D3DApp, so that's a problem. It's not really a problem if you derived two types from D3DApp, though. It's a problem if you create two instances of types derived from D3DApp (or of D3DApp itself). In that case, the gd3dApp pointer will refer to the instance that was constructed last, whichever one that happens to be. That may cause the earlier-constructed instances to misbehave if they rely on the assumption that "this == gd3dApp" anywhere.

gd3dApp is a singular global variable. There's only one, not one per D3DApp subclass.

 

Thanks guys, much more clear :P

23 minutes ago, jpetrie said:

As above, no. And even if it were true that wouldn't allow "re-defining" virtual member functions in that constructor. Functions are not fields of any given class instance, so you couldn't do something like "this->myFunction = newVersionOfThatFunction," which is what it sounds like you were hoping to do.

 

No that's not what I had in mind, probably I explained it poorly :P

What I meant is, let's say there is this D3DApp framework which has a virtual WinProc() with some default behaviours, let's say it closes the window if I press esc and some generic stuff like that.

Now if I inherit from D3DApp  in a class called D3D11Engine I can have another version of the virtual WinProc() defined inside the D3D11Engine class (if I want) so that I can define more specific behaviours to this derived class without going opening the D3DApp files and modifying that directly. This should be the whole point of inheritance and virtual keyword, right?

I need that global pointer to point to my derived class function ...

There is any way I can easily achieve that? :/

 

Edit: Oh wait, actually it's obvious, I just pass the derived class pointer "this" into the parent class constructor and assign that xD 

This should do (winProcOwner defaults to nullptr), for anyone else that's going to have the same doubt in the future :P

EDIT: don't do this code, read comment below 


D3DApp::D3DApp(HINSTANCE hInstance, std::string appName, UINT32 width, UINT32 height, D3DApp* winProcOwner)
	: mhAppInst{ hInstance }, mMainWndCaption{ appName }, mClientWidth{ width }, mClientHeight{ height }
{
	if (winProcOwner) {
		_gWinProcOwner = winProcOwner;
	}
	else {
		_gWinProcOwner = this;
	}
}

Direct3D11Engine::Direct3D11Engine(HINSTANCE hInstance, std::string appName, UINT32 width, UINT32 height)
	:D3DApp( hInstance, appName,  width,  height, this)
{
}

 

What does that achieve?

(Those pointers point to the same address)

As long as your MsgProc member function is virtual then it will dispatch to the correct implementation.

11 hours ago, MarcusAseth said:

Now if I inherit from D3DApp  in a class called D3D11Engine I can have another version of the virtual WinProc() defined inside the D3D11Engine class (if I want) so that I can define more specific behaviours to this derived class without going opening the D3DApp files and modifying that directly. This should be the whole point of inheritance and virtual keyword, right?

I need that global pointer to point to my derived class function ...

Ah, yes, what you are describing there is correct and supported. If your D3D11Engine inherits from D3DApp, and D3DApp's "WinProc" function is virtual, you may override that function in your D3D11Engine class. Your version of the WndProc function will be called when anybody calls WinProc on a D3DApp* (or D3DApp&) that actually refers to a D3D11Engine object.

11 hours ago, MarcusAseth said:

I need that global pointer to point to my derived class function ...

This is incorrect terminology on your part though, and probably what contributed to my misunderstanding your goal.

That global pointer is a pointer to an object, not a function, so that global pointer can never point to a function, let alone your derived class function. However, it can point to a D3D11Engine object. Which is what you actually want to accomplish the behavior you're describing.

11 hours ago, MarcusAseth said:

Edit: Oh wait, actually it's obvious, I just pass the derived class pointer "this" into the parent class constructor and assign that xD 

You don't need to do this, actually (but that was commendable problem solving). Even though, during D3DApp's constructor, the dynamic type of the this pointer is a D3DApp, the actual object will eventually be a D3D11Engine and the dynamic dispatch of anything via that pointer will work as it should.

However, as Oberon noted, the use of this global at all here is a bad idea. I'm really disappointed to find a book espousing this method. The correct way to handle the whole issue of the window procedure callback not being a member function is, as he noted, to stuff the actual pointer to the engine object into the window's user data storage.

Thanks for pointing that out dmatter, I did a cout inside the constructors and the resoult is this

Quote

D3DApp object "this" is: 00EFFB20
Direct3D11Engine object "this" is: 00EFFB20

So indeed, I was solving a problem wasn't even there to begin with thus adding problems x_x

Next I tried this



SetWindowLongPtr(mhWindow, GWLP_WNDPROC,reinterpret_cast<LONG_PTR>(this->MsgProc));

which fails completely, I suspect is for the same reason Oberon was saying 

14 hours ago, Oberon_Command said:

Mind you, the point about not assigning a member function to the window still holds, though, because of how pointers to member functions work in C++.

I won't try further on my own because right now I value more the time I spend learning Direct3D than learning WindowsAPI and as hacky the book solution is, at least allows me to proceed with Direct3D study :P, but if you guys want to continue the discussion and directly show me in code how you would make it work (having the WinProc() as a virtual non static class member) I'll be more than happy to replace my current code and learn in the process :)

This is the current code,

D3DApp.h

Spoiler



#pragma once
#include <Windows.h>
#include <d3d11.h>
#include <string>
#include "GameTimer.h"

class D3DApp
{
public:
	D3DApp(HINSTANCE hInstance,std::string appName, UINT32 width = 1280, UINT32 height = 960);
	virtual ~D3DApp();
	
	HINSTANCE AppInst()const;
	HWND MainWnd()const;
	float AspectRatio()const;
	int Run();

	// Framework methods. Derived client class overrides these methods to
	// implement specific application requirements.
	virtual bool Init();
	virtual void OnResize();
	virtual void UpdateScene(float dt) = 0;
	virtual void DrawScene() = 0;
	virtual LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

	// Convenience overrides for handling mouse input.
	virtual void OnMouseDown(WPARAM btnState, int x, int y) { }
	virtual void OnMouseUp(WPARAM btnState, int x, int y) { }
	virtual void OnMouseMove(WPARAM btnState, int x, int y) { }

protected:
	bool InitMainWindow();
	bool InitDirect3D();
	void CalculateFrameStats();

protected:
	//Window
	HINSTANCE mhAppInstance{};
	HWND mhWindow{};
	std::string mWindowCaption{};
	UINT32 mClientWidth{};
	UINT32 mClientHeight{};
	bool mWindowed{true};
	bool mMinimized{};
	bool mMaximized{};
	bool mResizing{};
	
	//Timer
	GameTimer mTimer{};
	bool mAppPaused{};

	//D3D 
	D3D_DRIVER_TYPE mDriverType{};
	ID3D11Device* mDevice{};
	ID3D11DeviceContext* mImmediateContext{};
	IDXGISwapChain* mSwapChain{};
	ID3D11Texture2D* mDepthStencilBuffer{};
	ID3D11RenderTargetView* mRenderTargetView{};
	ID3D11DepthStencilView* mDepthStencilView{};
	D3D11_VIEWPORT mScreenViewport{};
	
	//D3D Settings
	bool mEnable4xMsaa{true};
	UINT m4xMsaaQuality{};
};

 

D3DApp.cpp

Spoiler



#include "D3DApp.h"
#include <assert.h>
#include <iostream>
namespace {
	D3DApp* _gWinProcOwner = nullptr;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	if (_gWinProcOwner) { return _gWinProcOwner->MsgProc(hwnd, msg, wParam, lParam); }
	else { return DefWindowProc(hwnd, msg, wParam, lParam); }
}


D3DApp::D3DApp(HINSTANCE hInstance, std::string appName, UINT32 width, UINT32 height)
	: mhAppInstance{ hInstance }, mWindowCaption{ appName }, mClientWidth{ width }, mClientHeight{ height }
{
	_gWinProcOwner = this;
}

D3DApp::~D3DApp()
{
	mDepthStencilView->Release();
	mRenderTargetView->Release();
	mDepthStencilBuffer->Release();
	mSwapChain->Release();
	mImmediateContext->Release();
	mDevice->Release();
}

HINSTANCE D3DApp::AppInst() const
{
	return mhAppInstance;
}

HWND D3DApp::MainWnd() const
{
	return mhWindow;
}

float D3DApp::AspectRatio() const
{
	//TODO
	return 0.0f;
}

int D3DApp::Run()
{
	//TODO
	return 0;
}

bool D3DApp::Init()
{
	if (!mhWindow)
	{
		//Init Window
		if (!InitMainWindow()) {
			return false;
		}
		//Init Direct3D
		if (!InitDirect3D()) {
			return false;
		}
		return true;
	}
	return false;
}

void D3DApp::OnResize()
{
	//TODO
}

LRESULT D3DApp::MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
	case WM_CREATE:
	{
		MessageBox(0, "This is the D3DApp version of MsgProc()", 0, 0);
	}break;
	case WM_CLOSE:
	{
		PostQuitMessage(0);
	}break;
	default:
		return DefWindowProc(hwnd, msg, wParam, lParam);
		break;
	}
	return LRESULT();
}

bool D3DApp::InitMainWindow()
{
	//Define and Register Window Class
	WNDCLASS clientWindow{};
	clientWindow.style = CS_OWNDC;
	clientWindow.lpfnWndProc = WindowProc;
	clientWindow.hInstance = mhAppInstance;
	clientWindow.hCursor = LoadCursor(mhAppInstance, IDC_ARROW);
	clientWindow.lpszClassName = "clientWindow";

	if (FAILED(RegisterClass(&clientWindow)))
	{
		MessageBox(NULL, "Couldn't register window class", NULL, NULL);
		return false;
	}

	//Create Window
	RECT r{ 0,0,(LONG)mClientWidth,(LONG)mClientHeight };
	LONG newWidth = r.right - r.left;
	LONG newHeight = r.bottom - r.top;
	AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, false);
	mhWindow = CreateWindow("clientWindow", mWindowCaption.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
							 GetSystemMetrics(SM_CXFULLSCREEN) / 2 - newWidth / 2, GetSystemMetrics(SM_CYFULLSCREEN) / 2 - newHeight / 2,
							 newWidth, newHeight, NULL, NULL, mhAppInstance, 0);
	if (!mhWindow)
	{
		MessageBox(NULL, "Window creation failed", NULL, NULL);
		return false;
	}

	return true;
}

bool D3DApp::InitDirect3D()
{
	//Device and ImmediateContext creation
	UINT createDeviceFlags{};

#if defined(DEBUG) || defined(_DEBUG)
	createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	D3D_FEATURE_LEVEL featureLevel{};
	HRESULT hr = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, createDeviceFlags, 0, 0,
								   D3D11_SDK_VERSION, &mDevice, &featureLevel, &mImmediateContext);
	if (FAILED(hr))
	{
		MessageBox(0, "D3D11CreateDevice failed", 0, 0);
		return false;
	}
	if (featureLevel != D3D_FEATURE_LEVEL_11_0)
	{
		MessageBox(0, "Direct3D Feature Level 11 not supported", 0, 0);
		return false;
	}

	//MSAA Quality Check
	mDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, 4, &m4xMsaaQuality);
	assert(m4xMsaaQuality > 0);

	//4xMSAA Description
	DXGI_SAMPLE_DESC sampleDesc{};
	sampleDesc.Count = mEnable4xMsaa ? 4 : 1;
	sampleDesc.Quality = mEnable4xMsaa ? (m4xMsaaQuality - 1) : 0;

	//SwapChain creation
	{
		//buffer Description
		DXGI_MODE_DESC bufferDesc{};
		bufferDesc.Width = mClientWidth;
		bufferDesc.Height = mClientHeight;
		bufferDesc.RefreshRate.Numerator = 60;
		bufferDesc.RefreshRate.Denominator = 1;
		bufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		bufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
		bufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

		//SwapChain Description
		DXGI_SWAP_CHAIN_DESC swapChainDesc{};
		swapChainDesc.BufferDesc = bufferDesc;
		swapChainDesc.SampleDesc = sampleDesc;
		swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		swapChainDesc.BufferCount = 1;
		swapChainDesc.OutputWindow = mhWindow;
		swapChainDesc.Windowed = mWindowed;
		swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
		swapChainDesc.Flags = 0;

		//Get Factory used to create ID3D11Device
		IDXGIDevice* device{};
		mDevice->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&device));

		IDXGIAdapter* adapter{};
		device->GetParent(__uuidof(IDXGIAdapter), reinterpret_cast<void**>(&adapter));

		IDXGIFactory* factory{};
		adapter->GetParent(__uuidof(IDXGIFactory), reinterpret_cast<void**>(&factory));

		//Create swapChain
		factory->CreateSwapChain(mDevice, &swapChainDesc, &mSwapChain);
		//Release COM interfaces
		device->Release();
		adapter->Release();
		factory->Release();
	}

	//Render Target View creation
	ID3D11Texture2D* backBuffer{};
	mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&backBuffer));
	mDevice->CreateRenderTargetView(backBuffer, NULL, &mRenderTargetView);
	backBuffer->Release();

	//Depth/Stencil Buffer creation
	D3D11_TEXTURE2D_DESC depthBufferDesc;
	depthBufferDesc.Width = mClientWidth;
	depthBufferDesc.Height = mClientHeight;
	depthBufferDesc.MipLevels = 1;
	depthBufferDesc.ArraySize = 1;
	depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depthBufferDesc.SampleDesc = sampleDesc;
	depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	depthBufferDesc.CPUAccessFlags = 0;
	depthBufferDesc.MiscFlags = 0;

	mDevice->CreateTexture2D(&depthBufferDesc, 0, &mDepthStencilBuffer);
	mDevice->CreateDepthStencilView(mDepthStencilBuffer, 0, &mDepthStencilView);

	//Bind RenderTargerView and DepthStencilView to the OutputMerger Stage
	mImmediateContext->OMSetRenderTargets(1, &mRenderTargetView, mDepthStencilView);

	//Set Vieport
	D3D11_VIEWPORT vieport{};
	vieport.TopLeftX = 0.f;
	vieport.TopLeftY = 0.f;
	vieport.Width = static_cast<float>(mClientWidth);
	vieport.Height = static_cast<float>(mClientHeight);
	vieport.MinDepth = 0;
	vieport.MaxDepth = 1;

	mImmediateContext->RSSetViewports(1, &vieport);

	return true;
}

void D3DApp::CalculateFrameStats()
{
	//TODO
}

 

 

Sorry, I should have been more clear - try using SetWindowLongPtr to attach the pointer to the D3DApp to the window. Attaching the method will not help you because you would still need the D3DApp instance to call it properly. 

In your wndproc you can retrieve the pointer, cast it to a D3DApp, and call the virtual function on it, just as you are with the global.

2 hours ago, Oberon_Command said:

Sorry, I should have been more clear - try using SetWindowLongPtr to attach the pointer to the D3DApp to the window. Attaching the method will not help you because you would still need the D3DApp instance to call it properly. 

In your wndproc you can retrieve the pointer, cast it to a D3DApp, and call the virtual function on it, just as you are with the global.

It works, no more hacks :D Thanks!

This is how it looks for anyone else will attempt this in the future

Edit: fixed following jpetrie suggestions


//intermediate WindowProc to forward messages to D3DApp* member function
LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	D3DApp* pD3dApp = reinterpret_cast<D3DApp*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
	if (pD3dApp) { return pD3dApp->MsgProc(hwnd, msg, wParam, lParam); }
	else { return DefWindowProc(hwnd, msg, wParam, lParam); }
}

bool D3DApp::InitMainWindow()
{
	//...CreateWindow(...)
	SetWindowLongPtr(mhWindow, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
}

//Class member function that handles the messages
LRESULT D3DApp::MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
	case WM_CLOSE:
	{
		PostQuitMessage(0);
	}break;
	default:
		return DefWindowProc(hwnd, msg, wParam, lParam);
		break;
	}
	return LRESULT();
}

 

This topic is closed to new replies.

Advertisement