-1
#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.

elmcls
  • 37
  • 4
  • `strcpy_s((char*)h, 4, "QWE");` this looks fishy: it should be `char *h = HeapAlloc(...` ...`strcpy_s(h, 4, ...` – Jabberwocky Aug 11 '22 at 14:50
  • if i comment `strcpy_s((char*)h, 4, "QWE");` this change nothing. – elmcls Aug 11 '22 at 14:51
  • Might be a duplicate of https://stackoverflow.com/questions/1264137/how-to-copy-string-to-clipboard-in-c – Jabberwocky Aug 11 '22 at 14:54
  • I still can't find an answer for my question: Why does `HeapAlloc` works and `new char` doesn't, if memory is the same in the memory inspector? – elmcls Aug 11 '22 at 14:57
  • 2
    https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclipboarddata says "If SetClipboardData succeeds, the system owns the object identified by the hMem parameter." which indicates the memory needs to have been allocated the way the system expects. While I don't have a definitive reference on this handy, the general idea is that the Windows API doesn't know about `new` and can't take ownership of memory allocated by it. So the memory needs to have been allocated by Windows API mechanisms. – TheUndeadFish Aug 11 '22 at 15:00
  • 1
    Why do you want to use new or HeapAlloc? a HANDLE is not a memory pointer (sometimes it is but it's an implementation detail). Just use GlobalAlloc with GMEM_MOVEABLE like what's demonstrated by official doc: https://learn.microsoft.com/en-us/windows/win32/dataxchg/using-the-clipboard – Simon Mourier Aug 11 '22 at 15:16
  • @the A reference isn't required, this can be answered using logical reasoning alone. `operator new` can be implemented by any given C++ implementation any which way. Since the Windows API doesn't know about every single C++ compiler (past, present, and future) it certainly cannot know every implementation detail about any given C++ compiler. – IInspectable Aug 11 '22 at 15:58
  • Patient: "Doctor, it hurts if I do this." - Doctor: "Don't do this." – IInspectable Aug 11 '22 at 16:00
  • Does this answer your question? [How to copy string to clipboard in C?](https://stackoverflow.com/questions/1264137/how-to-copy-string-to-clipboard-in-c) – Raymond Chen Aug 11 '22 at 16:54

1 Answers1

4

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_MOVABLE, 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.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I still wonder why the [documentation](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-openclipboard) doesn't get fixed (or whether this code is actually correct): *"If an application calls `OpenClipboard` with hwnd set to `NULL`, `EmptyClipboard` sets the clipboard owner to `NULL`; this causes `SetClipboardData` to fail."* – IInspectable Aug 13 '22 at 11:51
  • Agreed. I think it would fail only in a delay-rendering scenario, where an owner window is absolutely required, but outside of that, the owner window being null or not is moot, and I bet the API does allow it, but the docs don't reflect that. – Remy Lebeau Aug 13 '22 at 17:49
  • Oops, misremembered the events. The question is still there, without an answer: [Unexpected behavior when placing Unicode text on Clipboard with NULL owner window](https://stackoverflow.com/q/66475791/1889329). – IInspectable Aug 14 '22 at 09:19