Struggling to call AddRef() in Windows Procedure - with user data & LongPtr
Hi there,
I had a bit of a problem with one of the COM pointers in my program. I have a rendering class inside a DLL with a global class instance and a pointer to it. I make another pointer to its parent class in the main project and then have the pointer in DLL pass its address to the pointer in the main project through an interface.
In short there are two pointers that reference the class. One is defined inside the DLL as the child class, the other is defined in the main project as its parent class. Both seem to work fine.
Only trouble is I kept getting an issue on shutdown from the debugger stating that there was an unhandled exception on one of the COM pointers Release() method. I'm using smart pointers and the pointer in question resides inside the rendering class and is defined like so:
Code:
class D3DRenderer : public D3DInterface
{
public:
D3DRenderer();
~D3DRenderer();
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
static INT_PTR CALLBACK ChooseRender(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
....code omitted
protected:
Microsoft::WRL::ComPtr<IDXGIFactory4> mdxgiFactory;
I suspected that the somewhat convoluted way the project pointer references the class in the DLL may have caused an issue. I know COM objects when used with smart pointers will automatically keep track of the pointers that reference them and then reduce the reference count when those pointers go out of scope. When I made a call to AddRef() it fixed the issue. The program terminates normally with no unhandled exceptions or anything.
Only confusing thing is... I can only do this from inside the Dialog Box message handler, not the Window message handler:
This works:
Code:
// Message handler for dialog box.
LRESULT CALLBACK D3DRenderer::ChooseRender(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
LONG_PTR ptr = GetWindowLongPtr(GetParent(hDlg),
GWLP_USERDATA);
static D3DRenderer** pD3DRenderer = reinterpret_cast<D3DRenderer**>(ptr);
// Debug: Added function call from factory to see if this resolves issue. Even if it does, still need to know why - 9/3/24
(*pD3DRenderer)->mdxgiFactory->AddRef();
Trying it in the windows message handler doesn't:
Code:
LRESULT CALLBACK D3DRenderer::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HINSTANCE hInstance;
static D3DRenderer** _pD3DRenderer;
static D3DRenderer** pD3DRenderer;
switch (message)
{
case WM_CREATE:
{
OutputDebugStringW(L"WM_CREATE entered \n\n");
hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
CREATESTRUCT* pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
_pD3DRenderer = reinterpret_cast<D3DRenderer**>(pCreate->lpCreateParams);
SetWindowLongPtr(hWnd, GWLP_USERDATA (LONG_PTR)_pD3DRenderer);
LONG_PTR ptr = GetWindowLongPtr(hWnd, GWLP_USERDATA);
pD3DRenderer = reinterpret_cast<D3DRenderer**>(ptr);
//Causes a fail
(*pD3DRenderer)->mdxgiFactory->AddRef();
The dialog box is called in the window procedure further down inside a WM_COMMAND block in the switch case block.
Code:
case WM_COMMAND:
{
.... code omitted
case IDM_OPTIONS:
DialogBoxW(hInstance, MAKEINTRESOURCE(IDD_BASIC_RENDER_DIALOG), hWnd, D3DRenderer::ChooseRender);
So if I open the window and then hit the menu item "Options", then enter the dialog box and then shut everything down it's ok, provided I omit the AddRef() call from the window procedure. If I just try and run the window with the AddRef() call present it crashes.
I've no idea why. So time to ask on here!
Many Thanks in advance :)
Lessons learned (hopefully)
It actually seems to be fixed now. I made some changes to the code to adopt better practices and that seems to have fixed it.
For starters I changed the way the user variable passed into CreateWindowW() is handled in the message procedure, it's all done in one block now:
Code:
static HINSTANCE hInstance;
static D3DRenderer** __pD3DRenderer;
static D3DRenderer** _pD3DRenderer;
case WM_CREATE:
{
// Sets and gets the interface pointer passed in by the main application
OutputDebugStringW(L"WM_CREATE entered \n\n");
CREATESTRUCT* pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
__pD3DRenderer = reinterpret_cast<D3DRenderer**>(pCreate->lpCreateParams);
SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(__pD3DRenderer));
LONG_PTR ptr = GetWindowLongPtr(hWnd, GWLP_USERDATA);
_pD3DRenderer = reinterpret_cast<D3DRenderer**>(ptr);
hInstance = (reinterpret_cast<LPCREATESTRUCT>(lParam))->hInstance;
// MSDN states this message must return 0 (not TRUE or FALSE) if handled (checked) - 11/3/24
return 0;
}
I used appropriate and specific casts rather than just the I think c-style (VARIABLE*) type bracket cast. I did this throughout.
The data member of the class is also set in a later message block to avoid any issues with it being done too early:
Code:
case WM_ACTIVATE:
{
// Message used only for the HWND at the moment
OutputDebugStringW(L"WM_ACTIVATE entered \n\n");
(*_pD3DRenderer)->mhMainWnd = hWnd;
// MSDN states this message must return 0 (not TRUE or FALSE) if handled (checked) - 11/3/24
return 0;
}
I also changed the way I handled the class pointer in the DLL file. The DLL file just has a pointer to the child class initialised to null like so:
Code:
D3DRenderer* pgD3DRenderer = nullptr;
This is globally accessible throughout the DLL.
I changed the way the child class (the rendering class) is created also. It uses the new keyword with the global pointer in the DLL and then uses a specific cast to return it to the main project:
Code:
D3DInterface** CreateRenderDevice()
{
OutputDebugStringW(L"CreateRenderDevice function called inside DLL \n\n");
pgD3DRenderer = new D3DRenderer;
return reinterpret_cast<D3DInterface**>(&pgD3DRenderer);
}
The last thing I changed was the way the child class is released in the DLL. It checks if the interface pointer isn't NULL and then calls delete on the global pointer in the DLL (not the interface pointer), and then sets both to NULL.
Code:
void ReleaseRenderDevice(D3DInterface** pInterface)
{
OutputDebugStringW(L"ReleaseRenderDevice function called inside DLL \n\n");
if (*pInterface != nullptr)
{
OutputDebugStringW(L"Render class pointer still valid inside DLL \n\n");
// Learning: testing shows it's much preferable to delete to global pointer inside the DLL rather than use the interface pointer for it - 11/3/24
delete pgD3DRenderer;
*pInterface = nullptr;
pgD3DRenderer = nullptr;
}
}
And the last thing is the ReleaseCOM() function in the child class destructor:
Code:
D3DRenderer::~D3DRenderer()
{
OutputDebugStringW(L"Destructor function called inside class \n\n");
ReleaseCOM();
}
void D3DRenderer::ReleaseCOM()
{
OutputDebugStringW(L"ReleaseCOM function called inside class \n\n");
if (md3dDevice)
{
OutputDebugStringW(L"Flushing command queue \n\n");
FlushCommandQueue();
}
return;
}
This checks if the device is still valid and flushes the command queue.
Then after the interface's release function is called in the main project the library is freed.
Code:
_releaseRenderDevice(pD3DInterface);
FreeLibrary(hDll);
And that seems to have done it. I can open and close the window without opening the dialog box or running any DirectX interface methods and it still shuts down without memory leaks, hung up DirectX objects or any memory access issues with the COM release methods.
I don't really know what this has done it's just better coding practices seem to have cured it.
Now I need to pick-up on that DLL learning stuff I mentioned earlier :)
Hope all that may have been of use to someone, if not just have a chuckle at my expense :D