2

I am a newbie to C++. I want to get the content of the clipboard, which might contain Unicode chars, append a div tag with some content formatted in HTML and set that back to clipboard.

I have achieved successfully in getting the content and appending it. But could not set it back to the clipboard as an HTML text. I have achieved setting as simple text. Here is my code:

#include <shlwapi.h>
#include <iostream>
#include <conio.h>
#include <stdio.h>

using namespace std;

wstring getClipboard(){
    if (OpenClipboard(NULL)){
        HANDLE clip = GetClipboardData(CF_UNICODETEXT);
        WCHAR * c;
        c = (WCHAR *)clip;
        CloseClipboard();
        return (WCHAR *)clip;
    }
    return L"";
}

bool setClipboard(wstring textToclipboard)
{
    if (OpenClipboard(NULL)){
        EmptyClipboard();
        HGLOBAL hClipboardData;
        size_t size = (textToclipboard.length()+1) * sizeof(WCHAR);
        hClipboardData = GlobalAlloc(NULL, size);
        WCHAR* pchData = (WCHAR*)GlobalLock(hClipboardData);
        memcpy(pchData, textToclipboard.c_str(), size);
        SetClipboardData(CF_UNICODETEXT, hClipboardData);
        GlobalUnlock(hClipboardData);
        CloseClipboard();
        return true;
    }
    return false;
}

int main (int argc, char * argv[])
{
   wstring  s =  getClipboard();
   s += std::wstring(L"some extra text <b>hello</b>");
   setClipboard(s);
   getch();
   return 0;
}

I did try using the code described here and read the doc here. But I couldn't make it work. What I tried could be way off track or completely wrong.

Update: The code below is what I tried after the modifications suggested by Cody Gray to the original code presented here:

bool CopyHTML2(WCHAR *html ){

    wchar_t *buf = new wchar_t [400 + wcslen(html)];
    if(!buf) return false;

    static int cfid = 0;
    if(!cfid) cfid = RegisterClipboardFormat("HTML Format");


        // Create a template string for the HTML header...
    wcscpy(buf,
        L"Version:0.9\r\n"
        L"StartHTML:00000000\r\n"
        L"EndHTML:00000000\r\n"
        L"StartFragment:00000000\r\n"
        L"EndFragment:00000000\r\n"
        L"<html><body>\r\n"
        L"<!--StartFragment -->\r\n");

    // Append the HTML...
    wcscat(buf, html);
    wcscat(buf, L"\r\n");
    // Finish up the HTML format...
    wcscat(buf,
        L"<!--EndFragment-->\r\n"
        L"</body>\r\n"
        L"</html>");

    wchar_t *ptr = wcsstr(buf, L"StartHTML");
    wsprintfW(ptr+10, L"%08u", wcsstr(buf, L"<html>") - buf);
    *(ptr+10+8) = L'\r';

    ptr = wcsstr(buf, L"EndHTML");
    wsprintfW(ptr+8, L"%08u", wcslen(buf));
    *(ptr+8+8) = '\r';

    ptr = wcsstr(buf, L"StartFragment");
    wsprintfW(ptr+14, L"%08u", wcsstr(buf, L"<!--StartFrag") - buf);
    *(ptr+14+8) = '\r';

    ptr = wcsstr(buf, L"EndFragment");
    wsprintfW(ptr+12, L"%08u", wcsstr(buf, L"<!--EndFrag") - buf);
    *(ptr+12+8) = '\r';

    // Open the clipboard...
    if(OpenClipboard(0)) {
        EmptyClipboard();
        HGLOBAL hText = GlobalAlloc(GMEM_MOVEABLE |GMEM_DDESHARE, wcslen(buf)+4);
        wchar_t *ptr = (wchar_t *)GlobalLock(hText);
        wcscpy(ptr, buf);
        GlobalUnlock(hText);
        SetClipboardData(cfid, hText);
        CloseClipboard();
        GlobalFree(hText);
    }

    // Clean up...
    delete [] buf;
    return true;
}

This code compiles successfully, But I get the following error at SetClipboardData : HEAP[Project1.exe]: Heap block at 007A8530 modified at 007A860A past requested size of d2 Project1.exe has triggered a breakpoint.

Please guide me on how to proceed. I am using Visual Studio Express 2012 on Windows 8. Thanks.

  • To @gongzhitaao: Sorry, my mistake. Now updated the question with the error. I didn't think my code will be of any use. – Jayarathina Madharasan Apr 12 '13 at 04:56
  • Do you have unicode enabled for your program? It seems that the ``wsprintf`` here use the ANSI version, i.e. ``wsprintfA``, which accepts ``LPSTR``, i.e. ``char *``. – gongzhitaao Apr 12 '13 at 05:01
  • Yes I do have Unicode enabled. And as suggested by Cody Gray, I did change wsprintfA function to Unicode equivalent. Now I get a different error, i have updated the question accordingly. – Jayarathina Madharasan Apr 12 '13 at 06:43

3 Answers3

1

You're mismatching ANSI (narrow) and Unicode (wide) strings.

Unlike the wcscpy function, the w in the wsprintf function doesn't stand for "wide", it stands for "Windows". It is part of the Win32 API, rather than the C runtime library. All of the Win32 API functions that work with strings have two versions, one suffixed with an A that deals with ANSI strings and another suffixed with a W that deals with wide strings. The headers hide all of this from you with macros. I explain all of this in more detail here—recommended reading.

Anyway, the simple fix here is to explicitly call the wide variant of that function, since you're correctly using wide strings everywhere else. Make all the calls to wsprintf look like this:

wchar_t *ptr = wcsstr(buf, L"StartHTML");
wsprintfW(ptr+10, L"%08u", wcsstr(buf, L"<html>") - buf);
*(ptr+10+8) = L'\r';

Alternatively, you could use the swprintf function provided by the C runtime library instead of the Win32 version. This one works just like the wcsstr and wcscpy functions you're using elsewhere. The w in the name means "wide". The documentation for this series of functions is here.

Note also that when you use character or string literals, they also need to be wide characters. You accomplish that by prepending them with an L. You do that some places, but miss doing it others. Make sure that you do it consistently.

The compiler should warn you about all this, though. You just need to make sure you turn your warning level up and don't ignore any of the warnings. Also make sure that both the UNICODE and _UNICODE preprocessor symbols are defined globally for your project. That will ensure that you are always calling the Unicode/wide versions of functions. Although that should be the default for all new projects.

Community
  • 1
  • 1
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • Hi, I tried as you suggested, It compiles correctly. But I get `Unhandled exception at 0x76DC782F (user32.dll) in Project1.exe: 0xC0000005: Access violation writing location 0x00000014.` exactly at line `wsprintfW(ptr+10, L"%08u", wcsstr(buf, L"") - buf);` Any ideas on why this is so? – Jayarathina Madharasan Apr 12 '13 at 05:17
  • @jay You're attempting to write to an invalid location. [Note what the parameters to the `wsprintf` function mean](http://msdn.microsoft.com/en-us/library/windows/desktop/ms647550.aspx). The first one specifies a pointer to the buffer the function will write to. You can't write to `ptr`, it's a pointer to the interior of a constant string literal. – Cody Gray - on strike Apr 12 '13 at 05:20
  • I was trying to convert this function to unicode: http://support.microsoft.com/kb/274308 If I am not mistaken, they are indeed trying to write in the middle of a string. If I am wrong, any idea on how to do that for a Unicode string? – Jayarathina Madharasan Apr 12 '13 at 05:25
0

This is the function I came up with the help of Jochen Arndt at codeproject.com. Hope this helps somebody. Here is a complete working code, if you are interested in checking this out.

It still has one problem. That is when pasted to onenote alone, it pastes gibberish after a anchor tag. It does not happen with Word, PowerPoint or Excel. And it does not have this problem for normal English language texts. If you have a solution for this, please do let me know. The problem seems to be with OneNote. Not with the code.

bool setClipboard(LPCWSTR lpszWide){
    int nUtf8Size = ::WideCharToMultiByte(CP_UTF8, 0, lpszWide, -1, NULL, 0, NULL, NULL);
    if (nUtf8Size < 1) return false;

    const int nDescLen = 105;
    HGLOBAL hGlobal = ::GlobalAlloc(GMEM_MOVEABLE, nDescLen + nUtf8Size);
    if (NULL != hGlobal)
    {
        bool bErr = false;
        LPSTR lpszBuf = static_cast<LPSTR>(::GlobalLock(hGlobal));
        LPSTR lpszUtf8 = lpszBuf + nDescLen;
        if (::WideCharToMultiByte(CP_UTF8, 0, lpszWide, -1, lpszUtf8, nUtf8Size, NULL, NULL) <= 0)
        {
            bErr = true;
        }
        else
        {
            LPCSTR lpszStartFrag = strstr(lpszUtf8, "<!--StartFragment-->");
            LPCSTR lpszEndFrag = strstr(lpszUtf8, "<!--EndFragment-->");
            lpszStartFrag += strlen("<!--StartFragment-->") + 2;

            int i = _snprintf(
            lpszBuf, nDescLen,
            "Version:1.0\r\nStartHTML:%010d\r\nEndHTML:%010d\r\nStartFragment:%010d\r\nEndFragment:%010d\r\n",
            nDescLen, 
            nDescLen + nUtf8Size - 1,       // offset to next char behind string
            nDescLen + static_cast<int>(lpszStartFrag - lpszUtf8), 
            nDescLen + static_cast<int>(lpszEndFrag - lpszUtf8));
        }
        ::GlobalUnlock(hGlobal);
        if (bErr)
        {
            ::GlobalFree(hGlobal);
            hGlobal = NULL;
        }

        // Get clipboard id for HTML format...
        static int cfid = 0;
        cfid = RegisterClipboardFormat("HTML Format");
        // Open the clipboard...
        if(OpenClipboard(0)) {
            EmptyClipboard();
            HGLOBAL hText = GlobalAlloc(GMEM_MOVEABLE |GMEM_DDESHARE, strlen(lpszBuf)+4);
            char *ptr = (char *)GlobalLock(hText);
            strcpy(ptr, lpszBuf);
            GlobalUnlock(hText);
            ::SetClipboardData(cfid, hText);
            CloseClipboard();
            GlobalFree(hText);
        }
    }

    return NULL != hGlobal;
}
0

Your problem comes from the use of wchar_t instead of char in the cited example which makes you wrong on the offset computations.

I would however recommend you avoiding the use of wchar_t for transfering UNICODE text to the clipboard. Indeed, UTF-8 char could coded with a sequence of bytes comprised between 1 and 4 bytes, while wchar_t on Windows is a fixed 2 bytes type.

As explained in the Microsoft doc refered in your email, the content of the clipboard shall be UNICODE, which happens to be the same as ASCII for the characters contained in the header of the clipboard memory.

To transfert UNICODE in the clipboard, you can do it using the standard char C++ functions to prepare the content sent to clipboard (std::string for eg.)

While the cited example works, please find here another code sample using C++ framework that can actually copy UTF-8 chars to the clipboard in HTML format:

void copyHTMLtoClipboard(const std::string& html) {

std::string contextStart("Version:0.9\r\nStartHTML:0000000000\r\nEndHTML:0000000000\r\nStartFragment:0000000000\r\nEndFragment:0000000000\r\n<html><body>\r\n<!--StartFragment -->\r\n");
std::string contextEnd("\r\n<!--EndFragment -->\r\n</body></html>");

std::stringstream aux;
aux << contextStart << html << contextEnd;
std::string res = aux.str();

size_t htmlStart = 105 * sizeof(char);
size_t fragmentStart = 119 * sizeof(char);
size_t htmlEnd = res.size() * sizeof(char);
size_t fragmentEnd = htmlEnd - 35 * sizeof(char);

aux.fill('0');
aux.width(10);
aux.seekp(23);
aux << htmlStart;

aux.seekp(43);
aux.fill('0');
aux.width(10);
aux << htmlEnd;

aux.seekp(69);
aux.fill('0');
aux.width(10);
aux << fragmentStart;

aux.seekp(93);
aux.fill('0');
aux.width(10);
aux << fragmentEnd;

res = aux.str();

HGLOBAL hdst = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, htmlEnd + sizeof(char));
LPSTR dst = (LPSTR)GlobalLock(hdst);
memcpy(dst, res.c_str(), htmlEnd);
dst[htmlEnd] = 0;
GlobalUnlock(hdst);

OpenClipboard(NULL);
EmptyClipboard();
SetClipboardData(RegisterClipboardFormat(L"HTML Format"), hdst);
CloseClipboard();

GlobalFree(hdst);

}

Note that this code was compiled defining the macros _UNICODE and UNICODE.

elenfoiro78
  • 163
  • 8