复制Unicode字符串到剪贴板无法正常工作。

3
我不知道为什么这段代码不能正常工作:
#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);
}

指定在GlobalAlloc中分配的大小的参数分配字节的数量,而不是字符的数量(在您的情况下,字符宽度为2个字节)。 您需要确定要分配的字节数。 其次,您不能仅通过强制转换将宽字符串转换为ANSI字符串或反之亦然。 (LPCSTR) 强制转换不起作用。 如果您在代码的其他部分中做类似的事情,则应该停止这样做,因为您的程序注定会失败。 - PaulMcKenzie
1
SetClipboardData() 的返回值是什么?你看到我下面的评论和 API 文档链接了吗?如果使用 NULL 窗口句柄,请尝试跳过 EmptyClipboard() - Joseph Willcoxson
1
你似乎不是一个阅读文档的粉丝:“以下值已过时,但为了与16位Windows兼容而提供。它们被忽略了。 GMEM_DDESHARE[...]”。此外,你的代码中几乎没有错误检查。我们为什么要猜测哪个API调用失败了?GlobalLock只能在可移动内存上调用(而你请求的是固定内存)。它用于将句柄转换为指针,但你已经传递了一个有效的内存指针。这不好。 - IInspectable
5个回答

8

修改此部分:

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

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

WCHAR 分配 2* 字节的空间。使用 WCHAR 替代 char,使用 wcscpy 替代 strcpy,使用 CF_UNICODETEXT 替代 CF_TEXT


1
来自文档:如果一个应用程序使用NULL参数调用OpenClipboard函数,EmptyClipboard函数将会将剪贴板的所有权设置为NULL,这将导致SetClipboardData函数失败。 - Joseph Willcoxson
1
不要调用 EmptyClipboard()。或者,如果你确实需要调用它,请使用有效的 HWND 调用 OpenClipboard() - Joseph Willcoxson
2
我会使用sizeof(wchar_t)(或者为了与你的答案保持一致,使用sizeof(WCHAR))而不是魔术常量2,但这只是一个风格问题。 - Mark Ransom
@JoeWillcoxson 实际上你是对的,我在收到与小片段相关的答案后添加了损坏的插件是不公平的。感谢您的时间。 - BPL
如果我在同一台电脑上使用它,它可以正常工作。但是当移动到另一台电脑(服务器/客户端)时,它就无法工作了。有什么建议吗? - Y K
显示剩余4条评论

4
你需要应用以下更改来修复你的代码:
if (OpenClipboard(0)) {

您需要提供有效的窗口句柄,以取得剪贴板的所有权。拥有所有权是必需的,这样您才能更改剪贴板的内容。
    HGLOBAL hClipboardData;
    hClipboardData = GlobalAlloc(GMEM_DDESHARE,
                                 wcslen(strData) + 1);

有两个bug需要修复。如内存和剪贴板中所解释的,将对象放入剪贴板时,应使用GlobalAlloc函数分配内存,并使用GMEM_MOVEABLE标志。另一方面,GMEM_DDESHARE被忽略,没有传递任何标志,则默认使用GMEM_FIXED。这将返回一个内存指针,传递给GlobalLock将随后失败。
其次,此API调用需要字节大小。Windows中的Unicode代码单元为2个字节。您需要(wcslen(strData) + 1) * sizeof(wchar_t)
    char* pchData;
    pchData = (char*)GlobalLock(hClipboardData);
    strcpy(pchData, LPCSTR(strData));

strcpy 函数复制单个字节单位,直到遇到第一个 NUL 字符。使用 UTF-16LE 编码(在 Windows 中使用),您复制的是单个字符。您应该改用 wcscpy 函数,并将目标转换为 wchar_t* 类型:

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

    SetClipboardData(CF_TEXT, hClipboardData);

由于您复制了UTF-16LE编码的文本,因此剪贴板格式应为CF_UNICODETEXT


参考资料:


1
strcpy(pchData, LPCSTR(strData));  

对于UTF16数据,使用wcscpy而不是强制转换不是一个好的选择。

去掉强制转换。


由于缺失的内存,正如其他回答者所告诉你的那样(我先没有看到),但无论如何,你也需要这个。 - deviantfan

0

我在C++ MFC中尝试了@Joseph Willcoxson的答案,感谢提供的代码。我发现第一次调用剪贴板函数是有效的,但是当第二次调用时,它会抛出奇怪的异常,没有具体的错误消息。经过一些搜索和测试,我发现wcscpy会得到编译错误C4996:函数可能不安全。考虑使用wcscpy_s代替。

我在这里找到了wcscpy_s的用法,并修改了代码以使用wcscpy_s。此外,注释掉GlobalFree()使剪贴板函数可以成功地多次调用而不出错:

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

0

我尝试了所有这些解决方案,第一个有点起作用,但在末尾附加了两个无法读取的符号,并且在第二次复制到剪贴板时崩溃。

@yu yang Jian的解决方案立即崩溃。

因此,我查看了我的UTF-8解决方案,并将其标志为GlobalAlloc使用的不是GMEM_DDESHARE而是GMEM_MOVEABLE,现在它完美地工作。

所以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(宽字符/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();
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接