c++winapiclipboardwndprocwindow-messages

Properly using AddClipboardFormatListener and subscribing to WM_CLIPBOARDUPDATE message


I am currently attempting to use the Windows clipboard and its notifications in my application. Specifically, I am attempting to subscribe to the WM_CLIPBOARDUPDATE window message by using the AddClipboardFormatListener() function. Previously, I had been using the SetClipboardViewer() function in order to add my window directly into the clipboard viewer chain. This had worked just fine, and I had received the relevant messages WM_DRAWCLIPBOARD and WM_DESTROYCLIPBOARD when expected. However, I would like to avoid continuing to use the clipboard chain because of how volatile it can be.

My understanding was that I would be perfectly able to receive WM_CLIPBOARDUPDATE after calling AddClipboardFormatListener(). Is there another step here that I am missing? What do I need to do to make sure that I receive this message properly? As it stands currently, I am not receiving it when performing a copy operation.

Here is an abridged example of what my code looks like:

WNDPROC override:

LRESULT CALLBACK ClipboardService::CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
  {
    switch ( pMsg->message )
    {
    case WM_DRAWCLIPBOARD:
        // Handle clipboard available event and forward message
        break;
    case WM_CLIPBOARDUPDATE:
        // This is never triggered
        break;
    case WM_DESTROYCLIPBOARD:
        // Handle clipboard cleared event and forward message
        break;
    }
    return ::CallNextHookEx( g_Hook, nCode, wParam, lParam );
} 

Called by Constructor:

HRESULT ClipboardService::SetOrRefreshWindowsHook()
{
    HRESULT hr = S_OK;
    try
    {
        if (!m_bHookSet)
        {
            g_hwndCurrent = ::CreateWindowEx(0, "Message", "ClipboardMessageWindow", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
            m_dwThreadID = ::GetWindowThreadProcessId(g_hwndCurrent, &m_dwProcessID);
            _Module.Lock();
            SetLastError(0);

            g_Hook = ::SetWindowsHookEx(WH_CALLWNDPROC, CallWndProc, 0, m_dwThreadID);
            //g_hwndNext = ::SetClipboardViewer(g_hwndCurrent); old way to subscribe

            // This is what I expect should subscribe me to WM_CLIPBOARDUPDATE messages
            if (!::AddClipboardFormatListener(g_hwndCurrent))
                hr_exit(E_UNEXPECTED); 

            DWORD dwLastError = ::GetLastError();
            g_This = this;
            m_bHookSet = true;
        }
    }
    catch (...)
    {
        hr_exit(E_UNEXPECTED);
    }
wrapup:
    return hr;
} 

This is a COM interface that is called by a .NET wrapper, but I don't think that either of those two things are relevant to my problem in this case (figured I would add just in case).


Solution

  • You should not be using SetWindowsHookEx(WH_CALLWNDPROC) to receive messages to your own window. Use RegisterClass/Ex() instead to register your own custom window class that has your WndProc assigned to it, and then CreateWindowEx() can create an instance of that window class. No hooking needed.

    HINSTANCE g_hThisInst = NULL;
    HWND g_hwndCurrent = NULL; 
    //HWND g_hwndNext = NULL;
    bool g_AddedListener = false;
    
    BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
    {
        g_hThisInst = hinstDLL;
        return TRUE;
    }
    
    
    LRESULT CALLBACK ClipboardService::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
        case WM_CREATE:
            //g_hwndNext = ::SetClipboardViewer(hwnd);
            g_AddedListener = ::AddClipboardFormatListener(hwnd);
            return g_AddedListener ? 0 : -1;
    
        case WM_DESTROY:
            /*
            ChangeClipboardChain(hwnd, g_hwndNext);
            g_hwndNext = NULL;
            */
            if (g_AddedListener)
            {
                RemoveClipboardFormatListener(hwnd);
                g_AddedListener = false;
            }
            return 0;
    
        /*
        case WM_CHANGECBCHAIN:
            if (g_hwndNext == (HWND)wParam)
                g_hwndNext = (HWND)lParam;
            else if (g_hwndNext)
                SendMessage(g_hwndNext, uMsg, wParam, lParam);
            break;
    
        case WM_DRAWCLIPBOARD:
            // Handle clipboard available event
            if (g_hwndNext)
                SendMessage(g_hwndNext, uMsg, wParam, lParam);
            break;
        */
    
        case WM_CLIPBOARDUPDATE:
            // Handle clipboard updated event
            return 0;
    
        case WM_DESTROYCLIPBOARD:
            // Handle clipboard cleared event and forward message
            break;
        }
    
        return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
    } 
    
    HRESULT ClipboardService::SetOrRefreshWindowsHook()
    {
        try
        {
            if (!g_hwndCurrent)
            {
                WNDCLASS wndClass = {};
                wndClass.lpfnWndProc = &ClipboardService::WndProc;
                wndClass.hInstance = g_hThisInst;
                wndClass.lpszClassName = TEXT("ClipboardMessageWindow");
    
                if (!::RegisterClass(&wndClass))
                {
                    DWORD dwLastError = ::GetLastError();
                    if (dwLastError != ERROR_CLASS_ALREADY_EXISTS)
                        return HRESULT_FROM_WIN32(dwLastError);
                }
    
                g_hwndCurrent = ::CreateWindowEx(0, wndClass.lpszClassName, "", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, g_hThisInst, NULL);
                if (!g_hwndCurrent)
                {
                    DWORD dwLastError = ::GetLastError();
                    return HRESULT_FROM_WIN32(dwLastError);
                }
    
                g_This = this;
            }
        }
        catch (...)
        {
            return E_UNEXPECTED;
        }
    
        return S_OK;
    }