Copy unicode string to clipboard isn't working
Asked Answered
C

5

3

I don't know why this code isn't working properly:

#define UNICODE

#include <iostream>
#include <sstream>
#include <windows.h>

void main(void)
{
    wchar_t* strData = L"CreateWindowExA";

    MessageBox(NULL, strData, L"Warning", MB_OK);

    if (OpenClipboard(0)) {
        EmptyClipboard();
        HGLOBAL hClipboardData;
        hClipboardData = GlobalAlloc(GMEM_DDESHARE,
                                     wcslen(strData) + 1);
        char* pchData;
        pchData = (char*)GlobalLock(hClipboardData);
        strcpy(pchData, LPCSTR(strData));
        GlobalUnlock(hClipboardData);
        SetClipboardData(CF_TEXT, hClipboardData);
        CloseClipboard();
    }

    MessageBox(NULL, L"Copied to Clipboard", L"Title", MB_OK);
}
Clone answered 17/11, 2016 at 20:58 Comment(3)
The argument that specifies the amount to allocate in GlobalAlloc allocates the number of bytes, not the number of characters (which in your case are 2 bytes wide). You need to figure out how many bytes to allocate. Second, you don't convert wide strings to ANSI strings and vice-versa by merely casting. That (LPCSTR) cast is not going to work. If you're doing anything like that in some other parts of your code you're not showing us, then stop doing it as your program will be doomed for failure.Upshot
What is the return value from SetClipboardData()? DId you see my comment below and link to API documentation? Try skipping EmptyClipboard() if you use a NULL window handle.Envy
You don't seem to be a big fan of reading documentation: "The following values are obsolete, but are provided for compatibility with 16-bit Windows. They are ignored. GMEM_DDESHARE [...]". Besides, there's literally zero error checking in your code. Why do we have to guess, which API call fails? GlobalLock should be called on movable memory only (while you request fixed memory). It is used to convert a handle into a pointer, but you pass it a valid memory pointer already. Not good.Poff
E
8

Change this section:

hClipboardData = GlobalAlloc(GMEM_DDESHARE, sizeof(WCHAR) * (wcslen(strData) + 1));

WCHAR* pchData;
pchData = (WCHAR*)GlobalLock(hClipboardData);
wcscpy(pchData, strData);
GlobalUnlock(hClipboardData);
SetClipboardData(CF_UNICODETEXT, hClipboardData);

Allocate 2* number of bytes for WCHAR. Instead of char, use WCHAR. Instead of strcpy, use wcscpy. Instead of CF_TEXT, use CF_UNICODETEXT.

Envy answered 17/11, 2016 at 21:24 Comment(9)
From documentation: If an application calls OpenClipboard with hwnd set to NULL, EmptyClipboard sets the clipboard owner to NULL; this causes SetClipboardData to fail. msdn.microsoft.com/en-us/library/windows/desktop/…Envy
IOW, don't call EmptyClipboard(). Or, if you do, call OpenClipboard() with a valid HWND.Envy
I'd use sizeof(wchar_t) (or to be consistent with your answer sizeof(WCHAR)) instead of the magic constant 2, but that's just a style thing.Shaven
Works fine here. Only mods I made were the ones in my notes and to not use EmptyClipboard(). If I use CF_TEXT, it leaves whatever garbage is on the clipboard on the clipboard. If I use CF_UNICODETEXT, it puts the right text on the clipboard and when I paste into a text editor, I get the right text.Envy
No, not at all. Have no clue what it is. I'm talking about using the clipboard and getting the same information out of it that I put on it. If that plugin is getting it wrong, then it's a problem with the plugin. Do you get the correct data when you paste into notepad?Envy
@JoeWillcoxson Actually you're right, it wasn't fair from me to have added the broken plugin after receiving answers related to the little snippet. Thanks for your timeClone
It works if I use it on the same computer. But when moving to another computer (server/client) it won't work. Any suggestions?Kareykari
@YK What do you mean? What call is failing? Have you called GetLastError() after the last failing call to get the reason why?Envy
@JosephWillcoxson I fixed it. The problem was that when GetClipboardData() returned NULL and then tried to do some string operations like strlen() it crashed immediately. So added an check for NULL valuesKareykari
P
4

You need to apply the following changes, to fix your code:

if (OpenClipboard(0)) {

You need to provide a valid window handle, to take ownership of the clipboard. Ownership is required, so that you can change the contents of the clipboard.

    HGLOBAL hClipboardData;
    hClipboardData = GlobalAlloc(GMEM_DDESHARE,
                                 wcslen(strData) + 1);

There are 2 bugs, that need to be fixed. As explained under Memory and the Clipboard, when placing an object into the clipboard, memory should be allocated by using the GlobalAlloc function with the GMEM_MOVEABLE flag. GMEM_DDESHARE, on the other hand, is ignored, and without passing any flags the call defaults to using GMEM_FIXED. This will return a memory pointer, and passing it to GlobalLock will subsequently fail.

Second, this API call requires the size in bytes. A Unicode code unit in Windows is 2 bytes. You need (wcslen(strData) + 1) * sizeof(wchar_t).

    char* pchData;
    pchData = (char*)GlobalLock(hClipboardData);
    strcpy(pchData, LPCSTR(strData));

strcpy copies single-byte units, up to the first NUL character. With UTF-16LE encoding (as used in Windows), you are copying a single character. You should be using wcscpy instead, and cast the destination to wchar_t*:

    wchar_t* pchData;
    pchData = (wchar_t*)GlobalLock(hClipboardData);
    wcscpy(pchData, strData);

    SetClipboardData(CF_TEXT, hClipboardData);

Since you copied UTF-16LE encoded text, the clipboard format should be CF_UNICODETEXT.


References:
Poff answered 18/11, 2016 at 0:22 Comment(0)
F
1
strcpy(pchData, LPCSTR(strData));  

isn't a good choice for UTF16 data.

Use wcscpy and remove the cast.

Feathers answered 17/11, 2016 at 21:6 Comment(1)
@Clone Because the missing memory, as the other answerer told you (didn't see it first). But nonetheless, you need this too.Feathers
S
0

I try @Joseph Willcoxson 's answer in C++ MFC, thanks for the code, and I find that first time call clipboard function works. but when call second time it throw strange exception without specific error message. After some search and test, I find that wcscpy will get compile error C4996 : function may be unsafe. Consider using wcscpy_s instead.

And I find wcscpy_s usage HERE, modify to use wcscpy_s, besides comment out GlobalFree() make clipboard function can be called multiple times successfully without error:

void toClipboardWStr(const wchar_t* strData) {
    if (OpenClipboard(0)) {
        EmptyClipboard();
        int size_m = sizeof(WCHAR) * (wcslen(strData) + 1);
        HGLOBAL hClipboardData = GlobalAlloc(GMEM_DDESHARE, size_m);
        WCHAR* pchData;
        pchData = (WCHAR*)GlobalLock(hClipboardData);
        //wcscpy(pchData, strData);
        wcscpy_s(pchData, size_m / sizeof(wchar_t), strData);
        GlobalUnlock(hClipboardData);
        SetClipboardData(CF_UNICODETEXT, hClipboardData);
        CloseClipboard();
        // if you need to call this function multiple times, I test no need to GlobalFree, or will occur error
        //GlobalFree(hClipboardData);
    }
}
Swanhilda answered 22/6, 2021 at 3:29 Comment(0)
L
0

I tries all of this solutions, first kinda works, but append two unreadable symbol at the end, and crash when i second time copy to clipboard.

@yu yang Jian solution crash immediately.

So a look up to my UTF-8 solution and see that as flag to GlobalAlloc i use not GMEM_DDESHARE but GMEM_MOVEABLE, and now its works perfectly fine.

So UTF-8 (char):

void CopyToClipboard(const char* buffer, size_t size) {

        //set the default size
        if (size <= 0)
            size = strlen(buffer) + 1;

        if (OpenClipboard(hWnd) {
            EmptyClipboard();
            HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, size);
            memcpy(GlobalLock(hMem), buffer, size);
            GlobalUnlock(hMem);
            SetClipboardData(CF_TEXT, hMem);
            CloseClipboard();
        }
    }

Utf-16 (wide char/unicode):

void CopyToClipboard(const wchar_t* buffer, size_t size) {

        //set the default size
        if (size <= 0)
            size = sizeof(WCHAR) * (wcslen(buffer) + 1);

        if (OpenClipboard(hWnd) {
            EmptyClipboard();
            HGLOBAL hClipboardData = GlobalAlloc(GMEM_MOVEABLE, size);
            WCHAR* pchData;
            pchData = (WCHAR*)GlobalLock(hClipboardData);
            wcscpy_s(pchData, size / sizeof(wchar_t), buffer);
            GlobalUnlock(hClipboardData);
            SetClipboardData(CF_UNICODETEXT, hClipboardData);
            CloseClipboard();
        }
    }
Lacrimatory answered 12/3, 2023 at 11:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.