Thread: Struggling to call AddRef() in Windows Procedure - with user data & LongPtr

  1. #1
    Registered User
    Join Date
    Jan 2010
    Posts
    206

    Question 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

  2. #2
    Registered User
    Join Date
    Jan 2010
    Posts
    206

    Arrow Slight update

    I had a look at the code again and saw I was creating a WNDCLASSEX class but just using the typical CreateWindow call, not CreateWindowEx.

    Also reserved some extra space in the cbWndExtra variable on the window class.

    No longer need to use AddRef() anywhere to get it to work when using the Dialog Box. Which to be honest is a relief. I wouldn't really want to be having to worry about COM ptrs not keeping track of themselves properly.

    However the error persists when I just open and close the window. In the mdxgifactory COM ptr there's a problem with the Release() part:

    Struggling to call AddRef() in Windows Procedure - with user data &amp; LongPtr-com-issue_1-jpg

    I suspect I'm just handling a pointer badly somewhere. Just can't figure out where at the moment

  3. #3
    Registered User
    Join Date
    Jan 2010
    Posts
    206

    Exclamation Update

    Some more information after a lot of testing. I'll start by showing how the window is first created and then when the DirectX stuff is called. The DirectX stuff is initialised after the window is created as the SwapChain in DirectX needs a window handle to work.

    This code is in the main project:

    Code:
    MyRegisterClass(hInstance);
    
    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_BASIC_RENDER));
    
    MSG msg;
    
    try
    {
        // Perform application initialization:
        if (!InitInstance(hInstance, nCmdShow, pD3DInterface))
        {
            return FALSE;
        }
    
        (*pD3DInterface)->InitDirectX();
    I've left other parts of the code in as there isn't too much of it there. Note the window class is registered, and then InitInstance() is called. This is where the CreateWindowW function is contained. I'll show it below:

    Code:
    //
    BOOL InitInstance(HINSTANCE hInstance, int nCmdShow, D3DInterface** pD3DInterface)
    {
       hInst = hInstance; // Store instance handle in our global variable
    
       HWND hWnd = CreateWindowW( szWindowClass,
                                    szTitle,
                                    WS_OVERLAPPEDWINDOW,
                                    CW_USEDEFAULT,
                                    CW_USEDEFAULT,
                                    CW_USEDEFAULT,
                                    CW_USEDEFAULT,
                                    NULL,
                                    NULL,
                                    hInst,
                                    pD3DInterface);   
    
       if (!hWnd)
       {
          return FALSE;
       }
    
       ShowWindow(hWnd, nCmdShow);
       UpdateWindow(hWnd);   
    
       return TRUE;
    }
    Now what's interesting to note is that several messages actually go to the window message handler before the CreateWindowW() function is even complete. They're as follows (I tracked them all):

    Code:
    36 -  WM_GETMINMAXINFO
    
    129 - WM_NCCREATE
    
    131 - WM_NCCALCSIZE
    
    147 - UNKNOWN
    
    148 - UNKNOWN
    
    148 - UNKNOWN
    
    1   - WM_CREATE
    
    // finally the message sent by the ShowWindow() function turns up
    
    24  - WM_SHOWWINDOW
    So if I called anything related to the DirectX stuff in either of the WM_NCCREATE or WM_CREATE message blocks in the message handler it would fail as the code had not proceeded out of the CreateWindowW function yet and thus had not had chance to called the (*pD3DInterface)->InitDirectX(); line.

    Ok well that's a start. I was originally under the assumption I had to call the Dialog Box to enable to program to end properly. As it turns out this isn't true. What I do need to do however is call any function in the rendering class that has a nested call to the IDXGIFactory4 interface which is declared in the rendering class like so:

    Code:
    	Microsoft::WRL::ComPtr<IDXGIFactory4>				mdxgiFactory;
    Any valid call to any of this interface's methods cause the program to shut down correctly with no hang ups. Here's the window message handler again with some slight updates and only the relevant parts:

    Code:
    static HINSTANCE hInstance;
    static D3DRenderer** __pD3DRenderer;
    static D3DRenderer** _pD3DRenderer;
    
    
    switch (message)
    {
    	case WM_NCCREATE:
    	{
    		CREATESTRUCT* pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
    		__pD3DRenderer = reinterpret_cast<D3DRenderer**>(pCreate->lpCreateParams);
    		SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)__pD3DRenderer);
    
    		hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
    
    		// MSDN states this message must return TRUE if handled (checked) - 11/3/24
    		return TRUE;
    	}
    	case WM_CREATE:
    	{
    		OutputDebugStringW(L"WM_CREATE entered \n\n");	
    
    		LONG_PTR ptr = GetWindowLongPtr(hWnd, GWLP_USERDATA);
    		_pD3DRenderer = reinterpret_cast<D3DRenderer**>(ptr);
    
    		(*_pD3DRenderer)->mhMainWnd = hWnd;
    
    		// MSDN states this message must return 0 (not TRUE or FALSE) if handled (checked) - 11/3/24
    		return 0;
    	}
    
    .... further down
    
    case WM_DESTROY:
    {	
            //this line is what saves it
    	(*_pD3DRenderer)->LogAdapter();
    
    	OutputDebugStringW(L"WM_DESTROY entered \n\n");
    	PostQuitMessage(0);			
    }
    So the WM_NCCREATE block sets up the pointer, the WM_CREATE block gets the pointer (I wanted the two operations done in different message blocks just in case) and any command to close the window has to go through the WM_DESTROY block, which ultimately calls a method in the IDXGIFactory4 interface.

    And that means no more issues on shutdown.

    So in short, any valid call in any appropriate part of the window message handler, to any of the IDXGIFactory4 interface methods, means I can close the window with no unhandled exceptions or memory issues. If this call is omitted I get the errors shown above.

    This works....but I have absolutely no idea why

    Thanks

  4. #4
    Registered User
    Join Date
    Jan 2010
    Posts
    206
    Interesting to note this only occurs with the IDXGIFactory interface, and nothing else.

    I read this on MSDN:

    DXGI responses from DLLMain

    Because a DllMain function can't guarantee the order in which it loads and unloads DLLs, we recommend that your app's DllMain function not call Direct3D or DXGI functions or methods, including functions or methods that create or release objects. If your app's DllMain function calls into a particular component, that component might call another DLL that isn't present on the operating system, which causes the operating system to crash. Direct3D and DXGI might load a set of DLLs, typically a set of drivers, that differs from computer to computer. Therefore, even if your app doesn t crash on your development and test computers when its DllMain function calls Direct3D or DXGI functions or methods, it might crash when it runs on another computer.

    To prevent you from creating an app that might cause the operating system to crash, DXGI provides the following responses in the specified situations:

    - If your app's DllMain function releases its last reference to a DXGI factory, DXGI raises an exception.

    - If your app's DllMain function creates a DXGI factory, DXGI returns an error code.
    That's probably why. And I've basically been screwing around trying to find ways to fool the mechanism without knowing.

    I'm just not sure how else to structure my code. I would really like the rendering class in a DLL behind an abstracted class interface.

    To be honest I don't know enough about DLL's to know if I can even do that without using DllMain (or even exavtly what DllMain is)

    Looks like I got some research and learning to do
    Last edited by shrink_tubing; 03-11-2024 at 06:04 AM.

  5. #5
    Registered User
    Join Date
    Jan 2010
    Posts
    206

    Post 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

  6. #6
    Registered User
    Join Date
    Jan 2010
    Posts
    206

    Question A slight update.

    Hi there,

    I read this in MSDN:

    Fetching a global variable:
    When creating a local copy of an interface pointer from an existing copy of the pointer in a global variable, you must call AddRef on the local copy because another function might destroy the copy in the global variable while the local copy is still valid.
    When recreating the problem on purpose this is what seemed to be what it was. I had a global class declared which contained COM pointers, and then I made a pointer which held the class's address. I then cast this to the interface pointer class type. All of this was in a separate function that did not belong to the class.

    I did not call AddRef() on anything anywhere though. I'm still not totally sure but I suspect the DLL destroyed the global declared class inside itself but the interface pointer was still valid. Thus when the interface pointer itself was destroyed in the application it caused a knock on effect that led to an attempt to call Release() on a pointer to a COM object when the object no longer existed. Presumably the COM object deleted itself when the DLL was unloaded.

    I'm still not totally sure about this but I suspect calling AddRef() somewhere (or anything else that causes AddRef() to be called?) increased the COM object's reference count, and thus kept it alive long enough to receive the final Release() call on it. Which thus removed the error I was previously getting on shutdown.

    The way I've done it now seems just to consider only one pointer to the COM object to be valid, and thus there are no extra Release() calls that shouldn't be there.

    It would be good though if someone can confirm if there's any credibility in this theory though.

    Thanks

  7. #7
    Registered User
    Join Date
    Jan 2010
    Posts
    206

    Slight update

    Seems I really do have a knack of finding these bizarre things. Unhappy with conclusions to date (albeit good) I really wanted to nail it down. Further testing yielded the following (an excerpt from my notes):

    Further testing shows that the problem is created exactly by making a global class in the DLL, copying its address to a pointer, and then calling one of the class's COM Interface's methods. Running alongside working code (which uses the dynamic allocation method and only one pointer), one can create a global class of the renderer type, then create a pointer and copy the global class's address to that pointer and the program will still run and shutdown acceptably.

    Only when you call one of the methods in the COM interfaces of that class does the program fail to shutdown correctly.

    So the exact way to create the fault is:

    Make a global class in the DLL. Copy its address into a pointer. Call one of the COM interface's methods (especially CreateFactory) probably through either the pointer or through the global class itself. And do not use AddRef() anywhere. This seems to create two legitimate roads into the class but without the smart COM pointers being aware of it.

    Thus when the time comes, the COM interfaces will cause the COM objects they point to, to delete themselves but with one remaining legitimate route into the class staying active. This is what then attempts a release call on a COM object which is already gone, and causes the error on shutdown.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 9
    Last Post: 06-06-2011, 02:30 AM
  2. how to overload the windows procedure?
    By classicalhacker in forum Windows Programming
    Replies: 2
    Last Post: 03-20-2004, 06:56 AM
  3. Call procedure in DLL
    By Mithoric in forum Windows Programming
    Replies: 4
    Last Post: 01-04-2004, 08:17 AM
  4. Remote Procedure Call
    By Unregistered in forum C Programming
    Replies: 1
    Last Post: 02-23-2002, 08:43 AM

Tags for this Thread