Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=51017
Debugging sessions and the official WinRT SDK show, that hstrings are aligned differently in memory, than they are currently used in Wine. E.g. The string buffer is stored at the end of hstring_private. Also if a string reference is created with WindowsCreateStringReference, a seperate pointer is used to point at the string-source.
Signed-off-by: Bernhard Kölbl besentv@gmail.com --- v4: Remove leftover debugging TRACE and minor style changes. v3: Add nested hstring_header struct to hstring_private and add a test for both. v2: I was mistaken about no reference counting being used. --- dlls/combase/string.c | 126 ++++++++++++++++++++++-------------- dlls/combase/tests/string.c | 73 ++++++++++++++++++++- 2 files changed, 150 insertions(+), 49 deletions(-)
diff --git a/dlls/combase/string.c b/dlls/combase/string.c index 2092e4360a3..1677915c244 100644 --- a/dlls/combase/string.c +++ b/dlls/combase/string.c @@ -26,44 +26,58 @@
WINE_DEFAULT_DEBUG_CHANNEL(winstring);
-struct hstring_private +#define HSTRING_REFERENCE_FLAG 1 + +struct hstring_header { - LPWSTR buffer; + UINT32 flags; UINT32 length; - BOOL reference; - LONG refcount; + UINT32 padding1; + UINT32 padding2; + const WCHAR *ptr; };
+struct hstring_private +{ + struct hstring_header header; + LONG refcount; + WCHAR buffer[1]; +}; + + static const WCHAR empty[1];
-C_ASSERT(sizeof(struct hstring_private) <= sizeof(HSTRING_HEADER)); +C_ASSERT(sizeof(struct hstring_header) <= sizeof(HSTRING_HEADER));
static inline struct hstring_private *impl_from_HSTRING(HSTRING string) { - return (struct hstring_private *)string; + return (struct hstring_private *)string; }
static inline struct hstring_private *impl_from_HSTRING_HEADER(HSTRING_HEADER *header) { - return (struct hstring_private *)header; + return CONTAINING_RECORD(header, struct hstring_private, header); }
static inline struct hstring_private *impl_from_HSTRING_BUFFER(HSTRING_BUFFER buffer) { - return (struct hstring_private *)buffer; + return CONTAINING_RECORD(buffer, struct hstring_private, buffer); }
static BOOL alloc_string(UINT32 len, HSTRING *out) { struct hstring_private *priv; - priv = HeapAlloc(GetProcessHeap(), 0, sizeof(*priv) + (len + 1) * sizeof(*priv->buffer)); + priv = HeapAlloc(GetProcessHeap(), 0, sizeof(*priv) + len * sizeof(*priv->buffer)); if (!priv) return FALSE; - priv->buffer = (LPWSTR)(priv + 1); - priv->length = len; - priv->reference = FALSE; + + priv->header.flags = 0; + priv->header.length = len; + priv->header.ptr = priv->buffer; + priv->refcount = 1; priv->buffer[len] = '\0'; + *out = (HSTRING)priv; return TRUE; } @@ -115,10 +129,12 @@ HRESULT WINAPI WindowsCreateStringReference(LPCWSTR ptr, UINT32 len, } if (ptr == NULL) return E_POINTER; - priv->buffer = (LPWSTR)ptr; - priv->length = len; - priv->reference = TRUE; - *out = (HSTRING)header; + + priv->header.ptr = (LPWSTR)ptr; + priv->header.length = len; + priv->header.flags = HSTRING_REFERENCE_FLAG; + + *out = (HSTRING)priv; return S_OK; }
@@ -127,16 +143,21 @@ HRESULT WINAPI WindowsCreateStringReference(LPCWSTR ptr, UINT32 len, */ HRESULT WINAPI WindowsDeleteString(HSTRING str) { - struct hstring_private *priv = impl_from_HSTRING(str); + struct hstring_private *priv;
TRACE("(%p)\n", str);
if (str == NULL) return S_OK; - if (priv->reference) + + priv = impl_from_HSTRING(str); + + if(priv->header.flags == HSTRING_REFERENCE_FLAG) return S_OK; + if (InterlockedDecrement(&priv->refcount) == 0) HeapFree(GetProcessHeap(), 0, priv); + return S_OK; }
@@ -156,8 +177,8 @@ HRESULT WINAPI WindowsDuplicateString(HSTRING str, HSTRING *out) *out = NULL; return S_OK; } - if (priv->reference) - return WindowsCreateString(priv->buffer, priv->length, out); + if (priv->header.flags == HSTRING_REFERENCE_FLAG) + return WindowsCreateString(priv->header.ptr, priv->header.length, out); InterlockedIncrement(&priv->refcount); *out = str; return S_OK; @@ -187,7 +208,7 @@ HRESULT WINAPI WindowsPreallocateStringBuffer(UINT32 len, WCHAR **outptr, return E_OUTOFMEMORY; priv = impl_from_HSTRING(str); *outptr = priv->buffer; - *out = (HSTRING_BUFFER)str; + *out = (HSTRING_BUFFER)&priv->buffer; return S_OK; }
@@ -196,9 +217,17 @@ HRESULT WINAPI WindowsPreallocateStringBuffer(UINT32 len, WCHAR **outptr, */ HRESULT WINAPI WindowsDeleteStringBuffer(HSTRING_BUFFER buf) { + + struct hstring_private *priv; + TRACE("(%p)\n", buf);
- return WindowsDeleteString((HSTRING)buf); + if(buf) + priv = impl_from_HSTRING_BUFFER(buf); + else + priv = NULL; + + return WindowsDeleteString((HSTRING)priv); }
/*********************************************************************** @@ -217,9 +246,9 @@ HRESULT WINAPI WindowsPromoteStringBuffer(HSTRING_BUFFER buf, HSTRING *out) *out = NULL; return S_OK; } - if (priv->buffer[priv->length] != 0 || priv->reference || priv->refcount != 1) + if (priv->buffer[priv->header.length] != 0 || priv->header.flags == HSTRING_REFERENCE_FLAG || priv->refcount != 1) return E_INVALIDARG; - *out = (HSTRING)buf; + *out = (HSTRING)priv; return S_OK; }
@@ -234,7 +263,7 @@ UINT32 WINAPI WindowsGetStringLen(HSTRING str)
if (str == NULL) return 0; - return priv->length; + return priv->header.length; }
/*********************************************************************** @@ -253,8 +282,9 @@ LPCWSTR WINAPI WindowsGetStringRawBuffer(HSTRING str, UINT32 *len) return empty; } if (len) - *len = priv->length; - return priv->buffer; + *len = priv->header.length; + + return priv->header.ptr; }
/*********************************************************************** @@ -274,9 +304,9 @@ HRESULT WINAPI WindowsStringHasEmbeddedNull(HSTRING str, BOOL *out) *out = FALSE; return S_OK; } - for (i = 0; i < priv->length; i++) + for (i = 0; i < (priv->header.length); i++) { - if (priv->buffer[i] == '\0') + if (priv->header.ptr[i] == '\0') { *out = TRUE; return S_OK; @@ -305,7 +335,7 @@ HRESULT WINAPI WindowsSubstring(HSTRING str, UINT32 start, HSTRING *out) *out = NULL; return S_OK; } - return WindowsCreateString(&priv->buffer[start], len - start, out); + return WindowsCreateString(&priv->header.ptr[start], len - start, out); }
/*********************************************************************** @@ -327,7 +357,7 @@ HRESULT WINAPI WindowsSubstringWithSpecifiedLength(HSTRING str, UINT32 start, UI *out = NULL; return S_OK; } - return WindowsCreateString(&priv->buffer[start], len, out); + return WindowsCreateString(&priv->header.ptr[start], len, out); }
/*********************************************************************** @@ -347,16 +377,16 @@ HRESULT WINAPI WindowsConcatString(HSTRING str1, HSTRING str2, HSTRING *out) return WindowsDuplicateString(str2, out); if (str2 == NULL) return WindowsDuplicateString(str1, out); - if (!priv1->length && !priv2->length) + if (!priv1->header.length && !priv2->header.length) { *out = NULL; return S_OK; } - if (!alloc_string(priv1->length + priv2->length, out)) + if (!alloc_string(priv1->header.length + priv2->header.length, out)) return E_OUTOFMEMORY; priv = impl_from_HSTRING(*out); - memcpy(priv->buffer, priv1->buffer, priv1->length * sizeof(*priv1->buffer)); - memcpy(priv->buffer + priv1->length, priv2->buffer, priv2->length * sizeof(*priv2->buffer)); + memcpy(priv->buffer, priv1->header.ptr, priv1->header.length * sizeof(*priv1->buffer)); + memcpy(priv->buffer + priv1->header.length, priv2->header.ptr, priv2->header.length * sizeof(*priv2->buffer)); return S_OK; }
@@ -371,7 +401,7 @@ BOOL WINAPI WindowsIsStringEmpty(HSTRING str)
if (str == NULL) return TRUE; - return priv->length == 0; + return priv->header.length == 0; }
/*********************************************************************** @@ -395,13 +425,13 @@ HRESULT WINAPI WindowsCompareStringOrdinal(HSTRING str1, HSTRING str2, INT32 *re } if (str1) { - buf1 = priv1->buffer; - len1 = priv1->length; + buf1 = priv1->header.ptr; + len1 = priv1->header.length; } if (str2) { - buf2 = priv2->buffer; - len2 = priv2->length; + buf2 = priv2->header.ptr; + len2 = priv2->header.length; } *res = CompareStringOrdinal(buf1, len1, buf2, len2, FALSE) - CSTR_EQUAL; return S_OK; @@ -418,19 +448,19 @@ HRESULT WINAPI WindowsTrimStringStart(HSTRING str1, HSTRING str2, HSTRING *out)
TRACE("(%p, %p, %p)\n", str1, str2, out);
- if (!out || !str2 || !priv2->length) + if (!out || !str2 || !priv2->header.length) return E_INVALIDARG; if (!str1) { *out = NULL; return S_OK; } - for (start = 0; start < priv1->length; start++) + for (start = 0; start < priv1->header.length; start++) { - if (!wmemchr(priv2->buffer, priv1->buffer[start], priv2->length)) + if (!wmemchr(priv2->header.ptr, priv1->header.ptr[start], priv2->header.length)) break; } - return start ? WindowsCreateString(&priv1->buffer[start], priv1->length - start, out) : + return start ? WindowsCreateString(&priv1->header.ptr[start], priv1->header.length - start, out) : WindowsDuplicateString(str1, out); }
@@ -445,18 +475,18 @@ HRESULT WINAPI WindowsTrimStringEnd(HSTRING str1, HSTRING str2, HSTRING *out)
TRACE("(%p, %p, %p)\n", str1, str2, out);
- if (!out || !str2 || !priv2->length) + if (!out || !str2 || !priv2->header.length) return E_INVALIDARG; if (!str1) { *out = NULL; return S_OK; } - for (len = priv1->length; len > 0; len--) + for (len = priv1->header.length; len > 0; len--) { - if (!wmemchr(priv2->buffer, priv1->buffer[len - 1], priv2->length)) + if (!wmemchr(priv2->header.ptr, priv1->header.ptr[len - 1], priv2->header.length)) break; } - return (len < priv1->length) ? WindowsCreateString(priv1->buffer, len, out) : + return (len < priv1->header.length) ? WindowsCreateString(priv1->header.ptr, len, out) : WindowsDuplicateString(str1, out); } diff --git a/dlls/combase/tests/string.c b/dlls/combase/tests/string.c index 5ebf669a426..bb477503a4b 100644 --- a/dlls/combase/tests/string.c +++ b/dlls/combase/tests/string.c @@ -37,7 +37,7 @@ static void _check_string(int line, HSTRING str, LPCWSTR content, UINT32 length,
ok_(__FILE__, line)(WindowsIsStringEmpty(str) == empty, "WindowsIsStringEmpty failed\n"); ok_(__FILE__, line)(WindowsStringHasEmbeddedNull(str, &out_null) == S_OK, "WindowsStringHasEmbeddedNull failed\n"); - ok_(__FILE__, line)(out_null == has_null, "WindowsStringHasEmbeddedNull failed\n"); + ok_(__FILE__, line)(out_null == has_null, "WindowsStringHasEmbeddedNull failed. %d != %d \n", out_null, has_null); ok_(__FILE__, line)(WindowsGetStringLen(str) == length, "WindowsGetStringLen failed\n"); ptr = WindowsGetStringRawBuffer(str, &out_length); /* WindowsGetStringRawBuffer should return a non-null, null terminated empty string @@ -479,6 +479,76 @@ static void test_trim(void) ok(WindowsDeleteString(str1) == S_OK, "Failed to delete string\n"); }
+static void test_hstring_struct(void) +{ + struct hstring_header + { + UINT32 flags; + UINT32 length; + UINT32 padding1; + UINT32 padding2; + const WCHAR *ptr; + }; + + struct hstring_private + { + struct hstring_header header; + LONG refcount; + WCHAR buffer[1]; + }; + + HSTRING str; + HSTRING str2; + HSTRING_HEADER hdr; + struct hstring_private* prv; + struct hstring_private* prv2; + + BOOL arch64 = (sizeof(void*) == 8); + + ok(arch64 ? (sizeof(prv->header) == 24) : (sizeof(prv->header) == 20), "hstring_header size incorrect.\n"); + + ok(WindowsCreateString(input_string, 6, &str) == S_OK, "Failed to create string.\n"); + + prv = CONTAINING_RECORD(str, struct hstring_private, header); + + ok(prv->header.flags == 0, "Expected 0 in flags field, got 0x%x.\n", prv->header.flags); + ok(prv->header.length == 6, "Expected 6 in length field, got %d.\n", prv->header.length); + ok(prv->header.ptr != NULL, "Unexpected nullptr in ptr field.\n"); + ok(prv->refcount == 1, "Expected 1 in refcount, got %d.\n", prv->refcount); + ok(prv->header.ptr == prv->buffer, "Expected ptr to point at buffer, instead pointing at %p.\n", prv->header.ptr); + ok(wcscmp(input_string, prv->buffer) == 0, "Strings didn't match.\n"); + + trace("hstr ptr(%p), flags(%d), length(%d), padding1(%p), padding2(%p), ptr(%p), refcount(%d), str(%ls)->addr(%p)\n", + prv, prv->header.flags, prv->header.length, &prv->header.padding1, &prv->header.padding2, + prv->header.ptr, prv->refcount, prv->buffer, prv->buffer); + + ok(WindowsDuplicateString(str, &str2) == S_OK, "Failed to duplicate string.\n"); + + prv2 = CONTAINING_RECORD(str2, struct hstring_private, header); + + ok(prv->refcount == 2, "Expected 2 in refcount, got %d.\n", prv->refcount); + ok(prv2->refcount == 2, "Expected 2 in refcount, got %d.\n", prv2->refcount); + ok(wcscmp(input_string, prv2->buffer) == 0, "Strings didn't match.\n"); + + ok(WindowsDeleteString(str) == S_OK, "Failed to delete string.\n"); + + ok(prv->refcount == 1, "Expected 1 in refcount, got %d.\n", prv->refcount); + + ok(WindowsDeleteString(str) == S_OK, "Failed to delete string.\n"); + + ok(WindowsCreateStringReference(input_string, 6, &hdr, &str) == S_OK, "Failed to create string ref.\n"); + + prv = CONTAINING_RECORD(&hdr, struct hstring_private, header); + prv2 = CONTAINING_RECORD(str, struct hstring_private, header); + + ok(prv == prv2, "Pointers not identical.\n"); + ok(prv2->header.flags == 1, "HSTRING_REFERENCE_FLAG not set.\n"); + ok(prv2->header.length == 6, "Length incorrect.\n"); + ok(prv2->header.ptr == input_string, "Pointer not pointing at input string.\n"); + + ok(WindowsDeleteString(str) == S_OK, "Failed to delete string ref.\n"); +} + START_TEST(string) { test_create_delete(); @@ -489,4 +559,5 @@ START_TEST(string) test_concat(); test_compare(); test_trim(); + test_hstring_struct(); }