1

I'm confused with a size issue. Running the following code throws an exception at runtime. Specifically it seems to appear at end, and the text still be pasted with success. Due to my limited skills I'm not able to interpret the exception clearly. It started when I decided to use the wcscpy_s function due to depreciation of wcscpy which worked fine in my noob program.

#define _CRT_SECURE_NO_WARNINGS
#include <afxwin.h>

int main() {
    wchar_t wcSource[7] = L"Testeu"; // Throws an exception error. However, wcSource[8] doesn't
    //wchar_t wcSource[9] = L"TestCopy"; // Runs fine

    UINT iSize = sizeof(wcSource);

    if (OpenClipboard(NULL)) {
        EmptyClipboard();
        HGLOBAL hClipboardData;
        hClipboardData = GlobalAlloc(GMEM_DDESHARE, iSize);
        wchar_t *wpchData;
        wpchData = (wchar_t*)GlobalLock(hClipboardData);

        //wcscpy(wpchData, wcSource); // Works fine
        wcscpy_s(wpchData, iSize, wcSource);

        GlobalUnlock(hClipboardData);
        SetClipboardData(CF_UNICODETEXT, hClipboardData);
        CloseClipboard();
    }

    return 0;
}
Bogey Jammer
  • 638
  • 2
  • 8
  • 23
  • 1
    There is no possible way that `wchar_t wcSource[7] = L"Testeu";` can throw an exception, since the chars shown as well as the null terminator all fit within 7 `wchar_t` elements. But, just to make sure, you can remove the explicit array size and let the compiler calculate it for you: `wchar_t wcSource[] = L"Testeu";` But where is your error handling? You are not checking to make sure that `GlobalAlloc()` and `GlobalLock()` succeed before you call `wcscpy_s()`, or that `SetClipboardData()` succeeds (if it fails, you leak `hClipboardData`) – Remy Lebeau Jun 12 '18 at 18:49
  • Yes, there is indeed a null terminator, but `wchar_t wcSource[8] = L"Testeu";`, although it has two null terminators, doesn't throw an exception. Why ? – Bogey Jammer Jun 12 '18 at 18:52
  • 1
    I can't see how using `wcscpy_s` instead of `wcscpy` could cause an exception on the line that declares the string. – Barmar Jun 12 '18 at 18:56
  • `wchar_t wcSource[] = L"Testeu";` throws an exception too. BTW it is the real code I'm running. – Bogey Jammer Jun 12 '18 at 18:58
  • `The following values are obsolete, but are provided for compatibility with 16-bit Windows. They are ignored. GMEM_DDESHARE` [GlobalAlloc](https://msdn.microsoft.com/en-us/library/windows/desktop/aa366574(v=vs.85).aspx) – Killzone Kid Jun 12 '18 at 19:00
  • Actually the exception is thrown after the 0 return, not at the string declaration. It's not clear in the comment because I thought it was in my question. – Bogey Jammer Jun 12 '18 at 19:06

1 Answers1

6

wcscpy_s() expects a CHARACTER count, but you are passing it a BYTE count instead. On Windows, sizeof(wchar_t) is 2 bytes.

You need a BYTE count when allocating memory for the clipboard buffer (which in your example will require 14 bytes), but since you are passing the BYTE count as a CHARACTER count to wcscpy_s(), you are telling it that the clipboard buffer can hold up to 14 wchar_t elements, when in actuality it can hold only 7. You are giving wcscpy_s() permission to go out of bounds of the clipboard buffer (for instance, if it wants to pre-fill the buffer memory before then filling it with actual characters). Doing so would corrupt the call stack, which could easily cause an exception when main() exits.

You need to pass wcscpy_s() the max number of CHARACTERS that the clipboard buffer can hold. Not the max number of BYTES it can hold.

You can do that by dividing iSize by sizeof(wchar_t), eg:

wcscpy_s(wpchData, iSize / sizeof(wchar_t), wcSource);

Alternatively, since you are using the exact BYTE size of the source array to allocate the clipboard buffer, you can use _countof() to get the number of CHARACTERS in the array (you cannot pass the allocated clipboard buffer to _countof()), eg:

wcscpy_s(wpchData, _countof(wcSource), wcSource);

Alternatively, you can use wsclen() instead, eg:

wchar_t wcSource[] = L"Testeu";

int iLen = wcslen(wcSource) + 1;
UINT iSize = iLen * sizeof(wchar_t);
...
hClipboardData = GlobalAlloc(GMEM_DDESHARE, iSize);
...
wcscpy_s(wpchData, iLen, wcSource);
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    You could use the [_countof](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/countof-macro) macro as well. It's also safer, as it determines at compile-time, whether it is being passed an array or a pointer. – IInspectable Jun 12 '18 at 19:03
  • `_countof()` doesn't work with pointers, only fixed arrays. Which is fine in this particular example, but easy to misuse in other situations if you are not careful – Remy Lebeau Jun 12 '18 at 19:04
  • 1
    Exactly. That's why `_countof` is safer than the `sizeof` arithmetic. The latter will silently fail, when passed a pointer. The former will fail to compile. – IInspectable Jun 12 '18 at 19:05
  • From the [documentation](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/countof-macro): *"The C++ version has extra template machinery to detect at compile time if a pointer is passed instead of a statically declared array."* – IInspectable Jun 12 '18 at 19:07
  • Now all is clear thanks. One last question, is the size passed to GlobalAlloc is still supposed to be the byte size or a character count ? – Bogey Jammer Jun 12 '18 at 19:16
  • 1
    The final piece from the documentation of [wcscpy_s](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/strcpy-s-wcscpy-s-mbscpy-s) to understand why it crashes in your case when passing the wrong size: The debug library versions of these functions first fill the buffer with 0xFE. – VolkerG Jun 12 '18 at 19:24
  • @BogeyJammer I covered that in my answer: "*You need a BYTE count when allocating memory for the clipboard buffer*". Please pay attention to what documentation tells you to use. See [`wcscpy_s()`](https://msdn.microsoft.com/en-us/library/td1esda9.aspx) and [`GlobalAlloc()`](https://msdn.microsoft.com/en-us/library/windows/desktop/aa366574.aspx) on MSDN for details. – Remy Lebeau Jun 12 '18 at 19:29