Search code examples
winapiclipboard

GetClipboardData() returning first character of clipboard data only


I'm trying to use the GetClipboardData() function to retrieve whatever is inside the clipboard. To test whether this worked or not I made a small function which is supposed to print the clipboard into the console window.

What I'm experiencing is that, say I copy "test", I now have "test" on the clipboard, I run the program, and the program shows "t".

I've tried a char pointer, a WCHAR pointer, direct typecast to char* inside std::cout, and the string class, none of which seem to work. (They all only display the first character of the string.)

if (!OpenClipboard(NULL))
{
    ExitWithError("Could not open clipboard."); //My own function, works fine, not the issue
}

HANDLE cbData = GetClipboardData(CF_UNICODETEXT);

if (!cbData)
{
    ExitWithError("Could not retrieve clipboard data.");
}

CloseClipboard();

std::cout << (char*)cbData << std::endl;

Solution

  • As documented in the standard clipboard formats:

    CF_UNICODETEXT: Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data.

    Unicode in Windows means UTF-16LE. Your code ((char*)cbData) reinterprets that as ASCII or ANSI. The character t is encoded as 0x74 0x00 in UTF-16LE. The second byte is null. That's why the std::cout stops right after printing t.

    To fix this, use std::wcout instead:

    std::wcout << reinterpret_cast<const wchar_t*>(cbData) << std::endl;
    

    Note also, that there are a number of issues with your code:

    • You aren't checking, if the clipboard holds data in the expected format (CF_UNICODETEXT). Call IsClipboardFormatAvailable to find out.
    • The return value of GetClipBoardData needs to be passed to GlobalLock to receive a pointer. Depending on the type of memory (GMEM_MOVEABLE vs. GMEM_FIXED), the handle is not necessarily the same as the pointer to the memory.
    • Clipboard data is owned by the clipboard. Once the clipboard is closed, the HGLOBAL returned from GetClipboardData is no longer valid. Likewise, the pointer returned by GlobalLock is only valid until calling GlobalUnlock. If you need to persist the data, make a copy of it.

    A fixed version of the code might look like this:

    if (!IsClipboardFormatAvailable(CF_UNICODETEXT))
    {
        ExitWithError("Clipboard format not available.");
    }
    
    if (!OpenClipboard(NULL))
    {
        ExitWithError("Could not open clipboard."); // My own function, works fine,
                                                    // not the issue
    }
    
    HGLOBAL hglb = GetClipboardData(CF_UNICODETEXT);
    if (!hglb)
    {
        CloseClipboard();
        ExitWithError("Could not retrieve clipboard data.");
    }
    
    const wchar_t* lpStr = static_cast<const wchar_t*>(GlobalLock(hglb));
    if (!lpStr)
    {
        CloseClipboard();
        ExitWithError("Could not lock clipboard data.");
    }
    
    // Output data before closing the clipboard. Clipboard data is owned by the clipboard.
    std::wcout << lpStr << std::endl;
    
    GlobalUnlock(hglb);
    CloseClipboard();
    

    All of this is explained in exhaustive detail under using the clipboard in the MSDN.


    Mandatory reading: The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!).