c++winapiclipboard

SetClipboardData and new operator for HANDLE


#include <Windows.h>

int main()
{
    HANDLE h = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 4); //new char[4]{ "QWE" };
    if (!h) return 1;
    strcpy_s((char*)h, 4, "QWE");
    OpenClipboard(0);
    EmptyClipboard();
    SetClipboardData(CF_TEXT, h);
    CloseClipboard();
    HeapFree(h, 0, 0);
    return 0;
}

If I use new char[4]{ "QWE" }; instead of HeapAlloc() I recieve an error upon SetClipboardData() execution. Why does this work this way? Memory inspector for h shows the same result in both cases.


Solution

  • HeapAlloc() returns an LPVOID (void*) pointer, whereas new char[] returns a char* pointer. They are NOT the same type, and neither of them is a valid Win32 HANDLE to a memory object.

    SetClipboardData() wants a valid HANDLE pointing to a memory object, but not just any type of HANDLE. Per the SetClipboardData() documentation:

    If SetClipboardData succeeds, the system owns the object identified by the hMem parameter. The application may not write to or free the data once ownership has been transferred to the system, but it can lock and read from the data until the CloseClipboard function is called. (The memory must be unlocked before the Clipboard is closed.) If the hMem parameter identifies a memory object, the object must have been allocated using the [GlobalAlloc] function with the GMEM_MOVEABLE flag.

    Neither HeapAlloc() nor new[] satisfy those requirements. You MUST use GlobalAlloc() instead, so the clipboard can properly take ownership of the memory object, eg:

    #include <Windows.h>
    
    int main()
    {
        HANDLE h = GlobalAlloc(GMEM_MOVEABLE, 4);
        if (!h) return 1;
    
        char *pmem = (char*) GlobalLock(h);
        if (!pmem) {
            GlobalFree(h);
            return 1;
        }
    
        strcpy_s(pmem, 4, "QWE");
        GlobalUnlock(h);
    
        if (OpenClipboard(NULL)) {
            EmptyClipboard();
            if (SetClipboardData(CF_TEXT, h))
                h = NULL; // clipboard now owns it, don't free it!
            CloseClipboard();
        }
    
        if (h) GlobalFree(h);
        return 0;
    }
    

    At compile-time, HANDLE is just an alias for void*, and a char* pointer is implicitly convertible to void*, so the compiler will allow a HeapAlloc'ed LPVOID or a new[]'ed char* to be passed to SetClipboardData() without complaining. But at run-time, the OS expects the passed HANDLE to point at a valid movable global memory object, anything else will invoke undefined behavior.