正确使用 AddClipboardFormatListener 并订阅 WM_CLIPBOARDUPDATE 消息

Properly using AddClipboardFormatListener and subscribing to WM_CLIPBOARDUPDATE message

我目前正在尝试在我的应用程序中使用 Windows 剪贴板及其通知。具体来说,我正在尝试订阅 WM_CLIPBOARDUPDATE window message by using the AddClipboardFormatListener() function. Previously, I had been using the SetClipboardViewer() 函数,以便将我的 window 直接添加到剪贴板查看器链中。这工作得很好,我在预期的时候收到了相关消息 WM_DRAWCLIPBOARDWM_DESTROYCLIPBOARD。但是,我想避免继续使用剪贴板链,因为它的易变性。

我的理解是,在调用 AddClipboardFormatListener() 后,我将完全能够收到 WM_CLIPBOARDUPDATE。我还缺少另一个步骤吗?我需要做什么才能确保我正确收到此消息?目前,我在执行复制操作时没有收到它。

这里是我的代码的简化示例:

WNDPROC 覆盖:

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

构造函数调用:

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

这是一个由 .NET 包装器调用的 COM 接口,但我不认为这两件事中的任何一件与我在这种情况下的问题相关(我想我会添加以防万一)。

您不应该使用 SetWindowsHookEx(WH_CALLWNDPROC) 来接收发给您自己 window 的消息。使用 RegisterClass/Ex() 来注册您自己的自定义 window class 并分配了您的 WndProc,然后 CreateWindowEx() 可以创建该 [=19] 的实例=] class。无需挂钩。

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