Hi,
I'm currently working on implementing AppUserModelID[1] support in Wine. It's far from finished, but it's slowly getting there, so I want to show it off and get some feedback.
Rationale: AppUserModelIDs can be used to match open windows to shortcuts in order to make the Win7 Taskbar happen. The idea is similar to WM_CLASS in X11/freedesktop.org environments: Add the AppUserModelID to every window and every shortcut and let the shell figure out the matching. The shell can then do fancy things like pinning the app or showing a high quality icon.
While most windows apps rely on the fallback algorithm of doing the shortcut<->window mapping via the full path to the executable, some applications, like LibreOffice, are making use of the AppUserModelID feature to appear as a set of different applications (Writer, Calc, ...) while sharing the same exe file (soffice.bin).
Modern freedesktop.org shells work very similar to the Win7 Taskbar in this regard (using the X11 WM_CLASS attribute and the StartupWMClass= deskop entry key), and the goal is to have wine apps appear like any native app: With a high-res icon and pinnable.
At the moment, the following features are implemented and working: - SetCurrentProcessExplicitAppUserModelID - IPropertyStore implementation on IShellLink - Exporting the AppUserModelID as WM_CLASS in the X11 driver
The following things are not yet implemented, but I expect to complete them in the following weeks: - The window-specific property store (SHGetPropertyStoreForWindow) - MSI support for shortcut properties - Extracting the AppUserModelID in winemenubuilder.exe
The whole thing required me to implement a fair bit of infrastructure (mostly in propsys.dll), and I am afraid that more might follow.
Since I am kinda new to wine development, I appreciate any early feedback and comments.
Regards Jonas Kümmerlin
[1] Application User Model IDs: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).a...
Jonas Kümmerlin (6): shell32: Implement process-wide explicit AppUserModelID winex11.drv: Use process-wide AppUserModelID to populate WM_CLASS ole32, propsys: Rework PROPVARIANT (de)serialization propsys: Implement named properties, serialization on the in-memory store propsys: Partially implement property stringification shell32: Properly implement IPropertyStore for shell links
dlls/ole32/ole32.spec | 3 + dlls/ole32/stg_prop.c | 1711 ++++++++++++++++++++++++++++++++----- dlls/ole32/tests/propvariant.c | 55 +- dlls/propsys/propstore.c | 1088 ++++++++++++++++++++++- dlls/propsys/propsys.spec | 34 +- dlls/propsys/propsys_classes.idl | 7 +- dlls/propsys/propvar.c | 821 +++++++++++++++++- dlls/propsys/tests/Makefile.in | 2 +- dlls/propsys/tests/propstore.c | 400 ++++++++- dlls/propsys/tests/propsys.c | 460 +++++++++- dlls/shell32/shell32.spec | 1 + dlls/shell32/shell32_main.c | 109 ++- dlls/shell32/shelllink.c | 218 +++-- dlls/shell32/tests/Makefile.in | 1 + dlls/shell32/tests/appusermodel.c | 108 +++ dlls/shell32/tests/shell32_test.h | 9 + dlls/shell32/tests/shelllink.c | 123 +++ dlls/winex11.drv/window.c | 72 +- include/propidl.idl | 10 + include/propvarutil.h | 106 ++- include/shobjidl.idl | 6 + include/winbase.h | 2 + 22 files changed, 4971 insertions(+), 375 deletions(-) create mode 100644 dlls/shell32/tests/appusermodel.c
-- 2.4.3
We now support [Set|Get]CurrentProcessExplicitAppUserModelID. This is a basic building block for supporting Win7-like matching of shortcuts to open windows. --- dlls/shell32/shell32.spec | 1 + dlls/shell32/shell32_main.c | 109 +++++++++++++++++++++++++++++++++++++- dlls/shell32/tests/Makefile.in | 1 + dlls/shell32/tests/appusermodel.c | 108 +++++++++++++++++++++++++++++++++++++ include/shobjidl.idl | 6 +++ include/winbase.h | 2 + 6 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 dlls/shell32/tests/appusermodel.c
diff --git a/dlls/shell32/shell32.spec b/dlls/shell32/shell32.spec index ed34919..1aa2c6b 100644 --- a/dlls/shell32/shell32.spec +++ b/dlls/shell32/shell32.spec @@ -326,6 +326,7 @@ @ stub RealShellExecuteW @ stdcall RegenerateUserEnvironment(ptr long) @ stdcall SetCurrentProcessExplicitAppUserModelID(wstr) +@ stdcall GetCurrentProcessExplicitAppUserModelID(ptr) @ stdcall SHAddToRecentDocs (long ptr) @ stdcall SHAppBarMessage(long ptr) @ stdcall SHAssocEnumHandlers(wstr long ptr) diff --git a/dlls/shell32/shell32_main.c b/dlls/shell32/shell32_main.c index cabf5e4..886cdfb 100644 --- a/dlls/shell32/shell32_main.c +++ b/dlls/shell32/shell32_main.c @@ -40,6 +40,7 @@ #include "rpcproxy.h" #include "shlwapi.h" #include "propsys.h" +#include "winternl.h"
#include "undocshell.h" #include "pidl.h" @@ -1370,11 +1371,115 @@ HRESULT WINAPI SHGetLocalizedName(LPCWSTR path, LPWSTR module, UINT size, INT *r
/*********************************************************************** * SetCurrentProcessExplicitAppUserModelID (SHELL32.@) + * + * Sets the AppUserModelID for the current process. + * + * An AppUserModelID is a unique identifier like + * Company.ProductName.SubProduct.Version + * where the SubProduct and Version parts are optional, and no spaces + * are allowed (wine does not currently enforce this). + * The maximum length is 128 characters (including the null-terminator). + * + * AppUserModelIDs can be used to reliably match application windows + * to shortcuts, similar to the X11 WM_CLASS property. + * + * Each process can have an associated AppUserModelID which has to be + * set using this API call before any windows are created, and each + * window can have a custom AppUserModelID which overrides the process- + * wide one (see: SHGetPropertyStoreForWindow). */ HRESULT WINAPI SetCurrentProcessExplicitAppUserModelID(PCWSTR appid) { - FIXME("%s: stub\n", debugstr_w(appid)); - return E_NOTIMPL; + int len; + RTL_USER_PROCESS_PARAMETERS *params; + static WCHAR *id_buffer = NULL; + + if (!appid) + { + return E_INVALIDARG; + } + + len = lstrlenW(appid); + + if (len > 127) + { + return E_INVALIDARG; + } + + RtlAcquirePebLock(); + + params = NtCurrentTeb()->Peb->ProcessParameters; + + /* FIXME: we leak the old buffer, and the new one + * if shell32.dll is unloaded and then loaded again */ + if (!id_buffer) + { + id_buffer = HeapAlloc(GetProcessHeap(), 0, 128 * sizeof(WCHAR)); + + if (id_buffer == NULL) + { + RtlReleasePebLock(); + + return E_OUTOFMEMORY; + } + + params->WindowTitle.Buffer = id_buffer; + params->WindowTitle.MaximumLength = 128 * sizeof(WCHAR); + } + + memcpy(params->WindowTitle.Buffer, appid, (len + 1) * sizeof(WCHAR)); + + params->WindowTitle.Length = len * sizeof(WCHAR); + params->dwFlags &= ~STARTF_TITLEISLINKNAME; + params->dwFlags |= STARTF_TITLEISAPPID; + + RtlReleasePebLock(); + + return S_OK; +} + +/*********************************************************************** + * GetCurrentProcessExplicitAppUserModelID (SHELL32.@) + * + * Get the AppUserModelID for the current process if it was already set. + */ +HRESULT WINAPI GetCurrentProcessExplicitAppUserModelID(PWSTR *appid) +{ + HRESULT ret = S_OK; + RTL_USER_PROCESS_PARAMETERS *params; + + if (!appid) + { + return E_INVALIDARG; + } + + *appid = NULL; + + RtlAcquirePebLock(); + + params = NtCurrentTeb()->Peb->ProcessParameters; + + if ((params->dwFlags & STARTF_TITLEISAPPID) > 0) + { + *appid = CoTaskMemAlloc(params->WindowTitle.Length + sizeof(WCHAR)); + if (*appid) + { + memcpy(*appid, params->WindowTitle.Buffer, params->WindowTitle.Length); + (*appid)[params->WindowTitle.Length / sizeof(WCHAR)] = 0; + } + else + { + ret = E_OUTOFMEMORY; + } + } + else + { + ret = E_FAIL; + } + + RtlReleasePebLock(); + + return ret; }
/*********************************************************************** diff --git a/dlls/shell32/tests/Makefile.in b/dlls/shell32/tests/Makefile.in index cfe0c50..9a37e2e 100644 --- a/dlls/shell32/tests/Makefile.in +++ b/dlls/shell32/tests/Makefile.in @@ -3,6 +3,7 @@ IMPORTS = shell32 ole32 oleaut32 user32 advapi32
C_SRCS = \ appbar.c \ + appusermodel.c \ assoc.c \ autocomplete.c \ brsfolder.c \ diff --git a/dlls/shell32/tests/appusermodel.c b/dlls/shell32/tests/appusermodel.c new file mode 100644 index 0000000..09638f3 --- /dev/null +++ b/dlls/shell32/tests/appusermodel.c @@ -0,0 +1,108 @@ +/* Unit test suite for the AppUserModelID support + * + * Copyright 2015 Jonas Kümmerlin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define COBJMACROS + +#include "windows.h" +#include "shlguid.h" +#include "shobjidl.h" +#include "shlobj.h" +#include "shellapi.h" +#include "wine/test.h" + +#include "shell32_test.h" + +HRESULT (WINAPI *pSetCurrentProcessExplicitAppUserModelID)(const WCHAR *id); +HRESULT (WINAPI *pGetCurrentProcessExplicitAppUserModelID)(WCHAR **id); + +#define RESOLVE(hDll, proc) p##proc = (void*)GetProcAddress(hDll, #proc) + +static void test_process_aum_id(void) +{ + HMODULE hShell32; + HRESULT hr; + WCHAR *received = NULL; + + /* MSDN claims the maximum length to be 128 chars, but in reality, + it is 127 chars + terminating NUL byte */ + WCHAR test_id[] = { + 'W','i','n','e','.','T','e','s','t','.','A','a','a','a','a','a', + 'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a', + 'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a', + 'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a', + 'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a', + 'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a', + 'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a', + 'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a', 0 }; + + hShell32 = GetModuleHandleA("shell32.dll"); + + RESOLVE(hShell32, SetCurrentProcessExplicitAppUserModelID); + RESOLVE(hShell32, GetCurrentProcessExplicitAppUserModelID); + + if (!pGetCurrentProcessExplicitAppUserModelID + || !pSetCurrentProcessExplicitAppUserModelID) + { + win_skip("SetCurrentProcessExplicitAppUserModelID is not available"); + return; + } + + /* Receiving it without setting will fail with an unspecified error code */ + hr = pGetCurrentProcessExplicitAppUserModelID(&received); + ok(FAILED(hr), "receiving the AppUserModelID succeeded where it shouldn't\n"); + ok(received == NULL, "AppUserModelID '%s' has been returned even though none was set\n", + wine_dbgstr_w(received)); + + if (received) + { + CoTaskMemFree(received); + received = NULL; + } + + hr = pSetCurrentProcessExplicitAppUserModelID(test_id); + ok(hr == S_OK, "SetCurrentProcessExplicitAppUserModelID failed (0x%08x)\n", hr); + + /* retrieve it again and compare */ + hr = pGetCurrentProcessExplicitAppUserModelID(&received); + ok(hr == S_OK, "GetCurrentProcessExplicitAppUserModelID failed (0x%08x)\n", hr); + ok(received != NULL, "GetCurrentProcessExplicitAppUserModelID returned a NULL ID\n"); + + if (received) + { + int length; + int cmp; + int test_id_length; + + test_id_length = lstrlenW(test_id); + length = lstrlenW(received); + cmp = lstrcmpW(received, test_id); + + ok(length == test_id_length, "Expected id with length 127, got %d\n", length); + + ok(cmp == 0, "Expected '%s', but got '%s'\n", + wine_dbgstr_w(test_id), wine_dbgstr_w(received)); + + CoTaskMemFree(received); + } +} + +START_TEST(appusermodel) +{ + test_process_aum_id(); +} diff --git a/include/shobjidl.idl b/include/shobjidl.idl index 455045d..d1fee7c 100644 --- a/include/shobjidl.idl +++ b/include/shobjidl.idl @@ -3638,6 +3638,12 @@ typedef enum ASSOC_FILTER cpp_quote("HRESULT WINAPI SHAssocEnumHandlers(PCWSTR extra, ASSOC_FILTER filter, IEnumAssocHandlers **handlersenum);")
/***************************************************************************** + * AppUserModelID support + */ +cpp_quote("HRESULT WINAPI SetCurrentProcessExplicitAppUserModelID(PCWSTR AppID);") +cpp_quote("HRESULT WINAPI GetCurrentProcessExplicitAppUserModelID(PWSTR *AppID);") + +/***************************************************************************** * ShellObjects typelibrary */ [ diff --git a/include/winbase.h b/include/winbase.h index a1859b0..edbe04f 100644 --- a/include/winbase.h +++ b/include/winbase.h @@ -579,6 +579,8 @@ typedef VOID (CALLBACK *LPOVERLAPPED_COMPLETION_ROUTINE)(DWORD,DWORD,LPOVERLAPPE #define STARTF_FORCEOFFFEEDBACK 0x00000080 #define STARTF_USESTDHANDLES 0x00000100 #define STARTF_USEHOTKEY 0x00000200 +#define STARTF_TITLEISLINKNAME 0x00000800 +#define STARTF_TITLEISAPPID 0x00001000
typedef struct _STARTUPINFOA{ DWORD cb; /* 00: size of struct */
I'll let others comment on implementation part. Here are my remarks on the test part.
On 07/14/2015 05:07 PM, Jonas Kümmerlin wrote:
+HRESULT (WINAPI *pSetCurrentProcessExplicitAppUserModelID)(const WCHAR *id); +HRESULT (WINAPI *pGetCurrentProcessExplicitAppUserModelID)(WCHAR **id);
you can make them static as in other test files
+#define RESOLVE(hDll, proc) p##proc = (void*)GetProcAddress(hDll, #proc)
+static void test_process_aum_id(void) +{
<snip> + + hShell32 = GetModuleHandleA("shell32.dll"); + + RESOLVE(hShell32, SetCurrentProcessExplicitAppUserModelID); + RESOLVE(hShell32, GetCurrentProcessExplicitAppUserModelID); + + if (!pGetCurrentProcessExplicitAppUserModelID + || !pSetCurrentProcessExplicitAppUserModelID) + { + win_skip("SetCurrentProcessExplicitAppUserModelID is not available"); + return; + }
You can move that initialization part to an init method (see tests/shellole.c), defining RESOLVE there and undef'ining it at the end of the init method or doind that in START_TEST block (like tests/appbar.c).
Do you plan on adding other test_* functions ?
- /* MSDN claims the maximum length to be 128 chars, but in reality,
it is 127 chars + terminating NUL byte */
prove/test it
- WCHAR test_id[] = {
'W','i','n','e','.','T','e','s','t','.','A','a','a','a','a','a',
'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a',
'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a',
'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a',
'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a',
'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a',
'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a',
'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a', 0 };
If format is not important, a for loop can make it (so that you can test different lengths)
- /* Receiving it without setting will fail with an unspecified error code */
- hr = pGetCurrentProcessExplicitAppUserModelID(&received);
- ok(FAILED(hr), "receiving the AppUserModelID succeeded where it shouldn't\n");
- ok(received == NULL, "AppUserModelID '%s' has been returned even though none was set\n",
wine_dbgstr_w(received));
What do you mean by unspecified? Do you refer to MSDN? Is error code constant but not in the SDK headers?
- if (received)
- {
<snip> + ok(length == test_id_length, "Expected id with length 127, got %d\n", length);
Text is not what you're testing
Does Get* crash with NULL pointer? Same for Set*. What does happen when setting twice ? Error ? Get* retrives the latest ?
If an explicit AppUserModelID is present, it is used, if not, the full path of the executable is used as fallback.
This is fundamentally incompatible with the previous approach of using the basename only, but ultimately more similar to how Windows 7 and later work. --- dlls/winex11.drv/window.c | 72 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 21 deletions(-)
diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index 50c3290..7d9e33d 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -42,6 +42,10 @@ #include "winuser.h" #include "wine/unicode.h"
+/* rpcasync.h declares ULONG Status; Xlib.h does #define Status int */ +#undef Status +#include "shobjidl.h" + #include "x11drv.h" #include "wine/debug.h" #include "wine/server.h" @@ -797,34 +801,58 @@ static void set_style_hints( struct x11drv_win_data *data, DWORD style, DWORD ex
/*********************************************************************** - * get_process_name + * get_appusermodel_id * - * get the name of the current process for setting class hints + * Get the id of the current process for setting class hints. + * Free with HeapFree(GetProcessHeap(), ...). */ -static char *get_process_name(void) +static char *get_appusermodel_id(void) { - static char *name; + WCHAR *explicit_id = NULL; + WCHAR module[MAX_PATH]; + DWORD len; + + /* TODO: Get the window-specific AppUserModelID */
- if (!name) + /* A process-wide explicit AppUserModelID might be available */ + if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID( &explicit_id ))) { - WCHAR module[MAX_PATH]; - DWORD len = GetModuleFileNameW( 0, module, MAX_PATH ); - if (len && len < MAX_PATH) + char *ptr; + DWORD len; + + len = WideCharToMultiByte( CP_UTF8, 0, explicit_id, -1, NULL, 0, NULL, NULL ); + if ((ptr = HeapAlloc( GetProcessHeap(), 0, len ))) { - char *ptr; - WCHAR *p, *appname = module; + WideCharToMultiByte( CP_UTF8, 0, explicit_id, -1, ptr, len, NULL, NULL );
- if ((p = strrchrW( appname, '/' ))) appname = p + 1; - if ((p = strrchrW( appname, '\' ))) appname = p + 1; - len = WideCharToMultiByte( CP_UNIXCP, 0, appname, -1, NULL, 0, NULL, NULL ); - if ((ptr = HeapAlloc( GetProcessHeap(), 0, len ))) - { - WideCharToMultiByte( CP_UNIXCP, 0, appname, -1, ptr, len, NULL, NULL ); - name = ptr; - } + CoTaskMemFree( explicit_id ); + + return ptr; } + + CoTaskMemFree( explicit_id ); + return NULL; } - return name; + + /* Employ a fallback algorithm */ + len = GetModuleFileNameW( 0, module, MAX_PATH ); + if (len && len < MAX_PATH) + { + char fallback_prefix[] = "Wine.Fallback."; + char *ptr; + + len = WideCharToMultiByte( CP_UTF8, 0, module, -1, NULL, 0, NULL, NULL ); + if ((ptr = HeapAlloc( GetProcessHeap(), 0, len + sizeof(fallback_prefix) - 1 ))) + { + memcpy( ptr, fallback_prefix, sizeof(fallback_prefix) - 1 ); + WideCharToMultiByte( CP_UTF8, 0, module, -1, + ptr + sizeof(fallback_prefix) - 1, len, NULL, NULL ); + + return ptr; + } + } + + return NULL; }
@@ -839,7 +867,6 @@ static void set_initial_wm_hints( Display *display, Window window ) Atom protocols[3]; Atom dndVersion = WINE_XDND_VERSION; XClassHint *class_hints; - char *process_name = get_process_name();
/* wm protocols */ i = 0; @@ -853,11 +880,14 @@ static void set_initial_wm_hints( Display *display, Window window ) if ((class_hints = XAllocClassHint())) { static char wine[] = "Wine"; + char *id = get_appusermodel_id();
- class_hints->res_name = process_name; + class_hints->res_name = id; class_hints->res_class = wine; XSetClassHint( display, window, class_hints ); XFree( class_hints ); + + HeapFree( GetProcessHeap(), 0, id ); }
/* set the WM_CLIENT_MACHINE and WM_LOCALE_NAME properties */
char fallback_prefix[] = "Wine.Fallback.";
Is there a good reason not to use the path as-is in this case? Windows apps don't see the value of WM_CLASS, after all.
I agree that using the full path rather than the basename is the correct approach, although you haven't made it obvious why. In my testing, the Windows taskbar would distinguish between executable files with the same name in a different directory.
Can the AppUserModelID be changed after a window is first shown? If so, we'll want a mechanism to notify the user driver of changes.
Vincent Povirk wrote:
char fallback_prefix[] = "Wine.Fallback.";
Is there a good reason not to use the path as-is in this case? Windows apps don't see the value of WM_CLASS, after all.
Actually, I can't think of any good reason. So we'll likely end up using the path as-is.
I agree that using the full path rather than the basename is the correct approach, although you haven't made it obvious why. In my testing, the Windows taskbar would distinguish between executable files with the same name in a different directory.
That's what I discovered, too.
Can the AppUserModelID be changed after a window is first shown? If so, we'll want a mechanism to notify the user driver of changes.
According to MSDN, the process-wide AppUserModelID is not supposed to change (so we can probably get away with pretending that it never does), but I didn't test what happens if you try on windows. However, the (not yet implemented) window-wide AppUserModelID can change at any time (and Win7 picks it up correctly). Maybe invent a window message for this?
OLE32.dll now exports a few internal functions for propsys.dll to use. The newly implemented functions handle way more cases than the original implementations, properly protect themselves against buffer overflows and are more thoroughly tested.
This allows to provide a high-quality implementation of StgDeserializePropVariant and StgSerializePropVariant.
As a byproduct of implementing the tests, numerous InitPropVariant* routines have been implemented in propsys.dll, and PropVariantCompareEx has been extended to support all serializable properties. --- dlls/ole32/ole32.spec | 3 + dlls/ole32/stg_prop.c | 1711 ++++++++++++++++++++++++++++++++++------ dlls/ole32/tests/propvariant.c | 55 +- dlls/propsys/propsys.spec | 28 +- dlls/propsys/propvar.c | 502 +++++++++++- dlls/propsys/tests/propsys.c | 377 +++++++++ include/propidl.idl | 10 + include/propvarutil.h | 103 ++- 8 files changed, 2521 insertions(+), 268 deletions(-)
diff --git a/dlls/ole32/ole32.spec b/dlls/ole32/ole32.spec index b36e683..8ec9c83 100644 --- a/dlls/ole32/ole32.spec +++ b/dlls/ole32/ole32.spec @@ -287,3 +287,6 @@ @ stdcall WriteFmtUserTypeStg(ptr long ptr) @ stub WriteOleStg @ stub WriteStringStream +@ stdcall wine_WritePropertyToBuffer(ptr ptr long ptr long long) +@ stdcall wine_SerializedPropertySize(ptr ptr long long) +@ stdcall wine_ReadProperty(ptr ptr long ptr long long ptr ptr) diff --git a/dlls/ole32/stg_prop.c b/dlls/ole32/stg_prop.c index da361ce..6cf6c62 100644 --- a/dlls/ole32/stg_prop.c +++ b/dlls/ole32/stg_prop.c @@ -1038,183 +1038,1491 @@ static void* WINAPI Allocate_CoTaskMemAlloc(void *this, ULONG size) return CoTaskMemAlloc(size); }
-/* FIXME: there isn't any checking whether the read property extends past the - * end of the buffer. +static HRESULT ConvertCodepageSize(const char *str, DWORD cb, DWORD *cbSize, + UINT codepageFrom, UINT codepageTo, BOOL wcharByteSwap) +{ + int unicodeCharCount; + int narrowCharCount; + WCHAR *unicodeString; + + *cbSize = 0; + + if (codepageFrom == CP_UNICODE && cb % 2) + { + WARN("Unicode string has odd number of bytes\n"); + return STG_E_INVALIDPARAMETER; + } + + /* look for a shortcut */ + if (codepageFrom == codepageTo) + { + *cbSize = cb; + return S_OK; + } + + /* look for another shortcut */ + if (codepageTo == CP_UNICODE) + { + unicodeCharCount = MultiByteToWideChar(codepageFrom, + 0, + str, + cb, + NULL, + 0); + if (unicodeCharCount < 1) + return STG_E_INVALIDPARAMETER; + + *cbSize = unicodeCharCount * sizeof(WCHAR); + return S_OK; + } + + /* convert to unicode */ + if (codepageFrom == CP_UNICODE) + { + unicodeString = CoTaskMemAlloc(cb); + if (!unicodeString) + return STG_E_INSUFFICIENTMEMORY; + + memcpy(unicodeString, str, cb); + + if (wcharByteSwap) + { + PropertyStorage_ByteSwapString(unicodeString, cb/2); + } + + unicodeCharCount = (int)cb / 2; + } + else + { + unicodeCharCount = MultiByteToWideChar(codepageFrom, + 0, + str, + cb, + NULL, + 0); + if (unicodeCharCount < 1) + return STG_E_INVALIDPARAMETER; + + unicodeString = CoTaskMemAlloc(unicodeCharCount * sizeof(WCHAR)); + if (!unicodeString) + return STG_E_INSUFFICIENTMEMORY; + + MultiByteToWideChar(codepageFrom, + 0, + str, + cb, + unicodeString, + unicodeCharCount); + } + + /* attempt conversion to the target codepage */ + narrowCharCount = WideCharToMultiByte(codepageTo, + 0, + unicodeString, + unicodeCharCount, + NULL, + 0, + NULL, + NULL); + + CoTaskMemFree(unicodeString); + if (narrowCharCount < 1) + return STG_E_INVALIDPARAMETER; + + *cbSize = narrowCharCount; + return S_OK; +} + +static HRESULT ConvertCodepage(const char *str, DWORD cbStr, + char *strOut, DWORD cbBuf, + UINT codepageFrom, UINT codepageTo, BOOL wcharByteSwap) +{ + int unicodeCharCount; + int narrowCharCount; + WCHAR *unicodeString; + + TRACE("(%p %u %p %u %u %u %d)\n", str, cbStr, strOut, cbBuf, codepageFrom, codepageTo, (int)wcharByteSwap); + + if (codepageFrom == CP_UNICODE && cbStr % 2) + { + WARN("Unicode string has odd number of bytes\n"); + return STG_E_INVALIDPARAMETER; + } + + /* look for a shortcut */ + if (codepageFrom == codepageTo) + { + if (cbBuf < cbStr) + return STG_E_INSUFFICIENTMEMORY; + + memcpy(strOut, str, cbStr); + + if (codepageTo == CP_UNICODE && wcharByteSwap) + { + PropertyStorage_ByteSwapString(strOut, cbStr/2); + } + + return S_OK; + } + + /* look for another shortcut */ + if (codepageTo == CP_UNICODE) + { + unicodeCharCount = MultiByteToWideChar(codepageFrom, + 0, + str, + cbStr, + NULL, + 0); + + if (unicodeCharCount < 1 || unicodeCharCount > cbBuf) + return STG_E_INVALIDPARAMETER; + + MultiByteToWideChar(codepageFrom, + 0, + str, + cbStr, + (LPWSTR)strOut, + unicodeCharCount); + return S_OK; + } + + /* convert to unicode */ + if (codepageFrom == CP_UNICODE) + { + unicodeString = CoTaskMemAlloc(cbStr); + if (!unicodeString) + return STG_E_INSUFFICIENTMEMORY; + + memcpy(unicodeString, str, cbStr); + unicodeCharCount = (int)cbStr / 2; + + if (wcharByteSwap) + { + PropertyStorage_ByteSwapString(unicodeString, unicodeCharCount); + } + } + else + { + unicodeCharCount = MultiByteToWideChar(codepageFrom, + 0, + str, + cbStr, + NULL, + 0); + if (unicodeCharCount < 1) + return STG_E_INVALIDPARAMETER; + + unicodeString = CoTaskMemAlloc(unicodeCharCount * sizeof(WCHAR)); + if (!unicodeString) + return STG_E_INSUFFICIENTMEMORY; + + MultiByteToWideChar(codepageFrom, + 0, + str, + cbStr, + unicodeString, + unicodeCharCount); + } + + /* attempt conversion to the target codepage */ + narrowCharCount = WideCharToMultiByte(codepageTo, + 0, + unicodeString, + unicodeCharCount, + NULL, + 0, + NULL, + NULL); + + if (narrowCharCount < 1 || narrowCharCount > cbBuf) + { + CoTaskMemFree(unicodeString); + return STG_E_INVALIDPARAMETER; + } + + WideCharToMultiByte(codepageTo, + 0, + unicodeString, + unicodeCharCount, + strOut, + narrowCharCount, + NULL, + NULL); + + return S_OK; +} + +/* HACK to hide (ULONG)(something) < 0 from the analyzer */ +static inline int gt(ULONG a, ULONG b) { return a > b; } + +/* wine_ReadProperty [OLE32.@] + * + * Base implementation for deserializing properties. + * Used by: + * - IPropertyStorage + * - StgConvertPropertyToVariant + * - StgDeserializeProperty [propsys.dll] + * + * This handles all straightforward cases, only crazy stuff like indirect + * properties have to be handled by the caller itself. + * + * The only sane allocator you could pass is NULL, in which case CoTaskMemAlloc + * is used. The allocator parameter just there to make the brain-damaged + * interface of StgConvertPropertyToVariant work. + * + * NOTICE: In case the reading fails, a partially initialized PROPVARIANT may be + * returned that has to be freed (using PropVariantClear, unless you + * used a custom allocator, in which case you basically have to + * reimplement PropVariantClear). + * TODO: Array handling + */ +HRESULT WINAPI wine_ReadProperty(PROPVARIANT *prop, const BYTE *data, + ULONG cbData, ULONG *pcbDataUsed, UINT diskCodepage, UINT runningCodepage, + void* (WINAPI *allocate)(void *this, ULONG size), void *allocate_data) +{ + HRESULT hr = S_OK; + ULONG cbData_Start; + + cbData_Start = cbData; + if (pcbDataUsed) + *pcbDataUsed = 0; + +#define FAIL_IF_EOF(cbytes) \ + do { \ + if ((ULONG)cbytes > cbData) \ + return STG_E_INVALIDPARAMETER; \ + } while (0) +#define MOVE_FORWARD(bytes) \ + do { \ + FAIL_IF_EOF(bytes); \ + data += bytes; \ + cbData -= bytes; \ + } while (0) +#define READ_WORD_AND_MOVE(location) \ + do { \ + FAIL_IF_EOF(2); \ + StorageUtl_ReadWord(data, 0, (location)); \ + MOVE_FORWARD(2); \ + } while (0) +#define READ_DWORD_AND_MOVE(location) \ + do { \ + FAIL_IF_EOF(4); \ + StorageUtl_ReadDWord(data, 0, (location)); \ + MOVE_FORWARD(4); \ + } while (0) +#define ALLOCATE_OR_FAIL(pptr, cbEl, nEl) \ + do { \ + *(pptr) = allocate(allocate_data, (cbEl) * (nEl)); \ + if (!*(pptr)) \ + { \ + return STG_E_INSUFFICIENTMEMORY; \ + } \ + } while(0) + +#define FOR_POSSIBLE_VECTOR(ctype, singleMember, vectorMember, cbBlock) \ + ctype *vec; \ + DWORD vecsize; \ + if (!(prop->vt & VT_VECTOR)) \ + { \ + vecsize = 1; \ + vec = &prop->u.singleMember; \ + } \ + else \ + { \ + READ_DWORD_AND_MOVE(&vecsize); \ + prop->u.vectorMember.cElems = vecsize; \ + ALLOCATE_OR_FAIL(&prop->u.vectorMember.pElems, sizeof(ctype), vecsize); \ + vec = prop->u.vectorMember.pElems; \ + ZeroMemory(vec, sizeof(ctype) * vecsize); \ + } \ + for (; vecsize; --vecsize, ++vec, data += (cbBlock), cbData -= (cbBlock)) \ + if (gt((ULONG)(cbBlock), cbData)) \ + return STG_E_INVALIDPARAMETER; \ + else +#define FOR_POSSIBLE_VECTOR_OR_SINGLEP(ctype, singleMemberP, vectorMember, cbBlock) \ + ctype *vec; \ + DWORD vecsize; \ + if (!(prop->vt & VT_VECTOR)) \ + { \ + vecsize = 1; \ + ALLOCATE_OR_FAIL(&prop->u.singleMemberP, sizeof(ctype), 1); \ + vec = prop->u.singleMemberP; \ + } \ + else \ + { \ + READ_DWORD_AND_MOVE(&vecsize); \ + prop->u.vectorMember.cElems = vecsize; \ + ALLOCATE_OR_FAIL(&prop->u.vectorMember.pElems, sizeof(ctype), vecsize); \ + vec = prop->u.vectorMember.pElems; \ + ZeroMemory(vec, sizeof(ctype) * vecsize); \ + } \ + for (; vecsize; --vecsize, ++vec, data += (cbBlock), cbData -= (cbBlock)) \ + if (gt((ULONG)(cbBlock), cbData)) \ + return STG_E_INVALIDPARAMETER; \ + else +#define CASE_OR_VECTOR(vtype) \ + case vtype: \ + case VT_VECTOR|vtype: + + if (!allocate) + allocate = &Allocate_CoTaskMemAlloc; + + assert(prop); + assert(data); + + ZeroMemory(prop, sizeof(*prop)); + + READ_DWORD_AND_MOVE((DWORD *)&prop->vt); + + switch (prop->vt) + { + case VT_EMPTY: + case VT_NULL: + break; + CASE_OR_VECTOR(VT_I1) + { + FOR_POSSIBLE_VECTOR(char, cVal, cac, 1) + { + *vec = *(const char *)data; + TRACE("Read char 0x%x ('%c')\n", vec[0], vec[0]); + } + break; + } + CASE_OR_VECTOR(VT_UI1) + { + FOR_POSSIBLE_VECTOR(BYTE, bVal, caub, 1) + { + *vec = *data; + TRACE("Read byte 0x%x\n", *vec); + } + break; + } + CASE_OR_VECTOR(VT_I2) + { + FOR_POSSIBLE_VECTOR(SHORT, iVal, cai, 2) + { + StorageUtl_ReadWord(data, 0, (WORD*)vec); + TRACE("Read short %d\n", (int)*vec); + } + break; + } + CASE_OR_VECTOR(VT_UI2) + CASE_OR_VECTOR(VT_BOOL) /* VARIANT_BOOL == USHORT */ + { + FOR_POSSIBLE_VECTOR(USHORT, uiVal, caui, 2) + { + StorageUtl_ReadWord(data, 0, vec); + TRACE("Read ushort %d\n", (int)*vec); + } + break; + } + CASE_OR_VECTOR(VT_INT) + CASE_OR_VECTOR(VT_I4) + CASE_OR_VECTOR(VT_ERROR) /* SCODE == LONG */ + { + FOR_POSSIBLE_VECTOR(LONG, lVal, cal, 4) + { + StorageUtl_ReadDWord(data, 0, (DWORD*)vec); + TRACE("Read long %u\n", (unsigned)*vec); + } + break; + } + CASE_OR_VECTOR(VT_UINT) + CASE_OR_VECTOR(VT_UI4) + { + FOR_POSSIBLE_VECTOR(ULONG, ulVal, caul, 4) + { + StorageUtl_ReadDWord(data, 0, vec); + TRACE("Read ulong %d\n", (int)*vec); + } + break; + } + CASE_OR_VECTOR(VT_I8) + { + FOR_POSSIBLE_VECTOR(LARGE_INTEGER, hVal, cah, 8) + { + StorageUtl_ReadULargeInteger(data, 0, (ULARGE_INTEGER*)vec); + TRACE("Read longlong 0x%08x%08x\n", (unsigned)vec->HighPart, (unsigned)vec->LowPart); + } + break; + } + CASE_OR_VECTOR(VT_UI8) + { + FOR_POSSIBLE_VECTOR(ULARGE_INTEGER, uhVal, cauh, 8) + { + StorageUtl_ReadULargeInteger(data, 0, vec); + TRACE("Read ulonglong 0x%08x%08x\n", (unsigned)vec->HighPart, (unsigned)vec->LowPart); + } + break; + } + CASE_OR_VECTOR(VT_LPSTR) + { + FOR_POSSIBLE_VECTOR(LPSTR, pszVal, calpstr, 0) + { + DWORD count; + DWORD convertedCount; + + READ_DWORD_AND_MOVE(&count); + if (diskCodepage == CP_UNICODE && count % 2) + { + WARN("Unicode string has odd number of bytes\n"); + return STG_E_INVALIDHEADER; + } + + FAIL_IF_EOF(count); + + hr = ConvertCodepageSize((const char*)data, + count, + &convertedCount, + diskCodepage, + runningCodepage, + TRUE); + if (FAILED(hr)) + return hr; + + ALLOCATE_OR_FAIL(vec, 1, convertedCount); + hr = ConvertCodepage((const char *)data, count, + *vec, convertedCount, + diskCodepage, + runningCodepage, + TRUE); + + MOVE_FORWARD(count); + MOVE_FORWARD((4 - (count%4)) % 4); /* padding */ + + /* Ensure NULL termination */ + if (runningCodepage == CP_UNICODE) + { + ((WCHAR*)*vec)[convertedCount / sizeof(WCHAR) - 1] = '\0'; + TRACE("Read string value %s\n", + debugstr_w((PCWSTR)*vec)); + } + else + { + (*vec)[convertedCount - 1] = '\0'; + TRACE("Read string value %s\n", debugstr_a(*vec)); + } + } + break; + } + CASE_OR_VECTOR(VT_BSTR) + { + FOR_POSSIBLE_VECTOR(BSTR, bstrVal, cabstr, 0) + { + DWORD count, wcount; + + READ_DWORD_AND_MOVE(&count); + if (diskCodepage == CP_UNICODE && count % 2) + { + WARN("Unicode string has odd number of bytes\n"); + return STG_E_INVALIDHEADER; + } + else + { + FAIL_IF_EOF(count); + + if (diskCodepage == CP_UNICODE) + wcount = count / 2; + else + wcount = MultiByteToWideChar(diskCodepage, 0, (LPCSTR)data, count, NULL, 0); + + *vec = SysAllocStringLen(NULL, wcount); /* FIXME: use allocator? */ + + if (*vec) + { + if (diskCodepage == CP_UNICODE) + { + memcpy(*vec, data, count); + PropertyStorage_ByteSwapString(*vec, wcount); + } + else + MultiByteToWideChar(diskCodepage, 0, (LPCSTR)data, count, *vec, wcount); + + (*vec)[wcount - 1] = '\0'; + TRACE("Read string value %s\n", debugstr_w(*vec)); + } + else + { + return STG_E_INSUFFICIENTMEMORY; + } + } + + MOVE_FORWARD(count); + MOVE_FORWARD((4 - (count%4)) % 4); /* padding */ + } + break; + } + case VT_BLOB: + { + DWORD count; + + READ_DWORD_AND_MOVE(&count); + FAIL_IF_EOF(count); + prop->u.blob.cbSize = count; + prop->u.blob.pBlobData = allocate(allocate_data, count); + if (prop->u.blob.pBlobData) + { + memcpy(prop->u.blob.pBlobData, data, count); + TRACE("Read blob value of size %d\n", count); + + MOVE_FORWARD(count); + MOVE_FORWARD((4 - (count % 4)) % 4); /* padding to 4 bytes */ + } + else + return STG_E_INSUFFICIENTMEMORY; + break; + } + CASE_OR_VECTOR(VT_LPWSTR) + { + FOR_POSSIBLE_VECTOR(LPWSTR, pwszVal, calpwstr, 0) + { + DWORD count; + + READ_DWORD_AND_MOVE(&count); + FAIL_IF_EOF(count * sizeof(WCHAR)); + ALLOCATE_OR_FAIL(vec, sizeof(WCHAR), count); + + memcpy(*vec, data, count * sizeof(WCHAR)); + + /* make sure string is NULL-terminated */ + (*vec)[count - 1] = '\0'; + PropertyStorage_ByteSwapString(*vec, count); + TRACE("Read string value %s\n", debugstr_w(*vec)); + + MOVE_FORWARD(count * sizeof(WCHAR)); + if (count % 2) + MOVE_FORWARD(sizeof(WCHAR)); /* padding to 4 bytes */ + } + break; + } + CASE_OR_VECTOR(VT_FILETIME) + { + ULARGE_INTEGER tmp; + FOR_POSSIBLE_VECTOR(FILETIME, filetime, cafiletime, 8) + { + StorageUtl_ReadULargeInteger(data, 0, &tmp); + + vec->dwLowDateTime = tmp.LowPart; + vec->dwHighDateTime = tmp.HighPart; + } + break; + } + CASE_OR_VECTOR(VT_CY) + { + ULARGE_INTEGER tmp; + FOR_POSSIBLE_VECTOR(CY, cyVal, cacy, 8) + { + StorageUtl_ReadULargeInteger(data, 0, &tmp); + vec->int64 = (LONGLONG)tmp.QuadPart; + } + break; + } + CASE_OR_VECTOR(VT_CF) + { + FOR_POSSIBLE_VECTOR_OR_SINGLEP(CLIPDATA, pclipdata, caclipdata, 0) + { + DWORD len = 0, tag = 0; + + READ_DWORD_AND_MOVE(&len); + FAIL_IF_EOF(len); + READ_DWORD_AND_MOVE(&tag); + if (len > 4) + { + vec->cbSize = len; + vec->ulClipFmt = tag; + + ALLOCATE_OR_FAIL(&vec->pClipData, 1, len - 4); + + memcpy(vec->pClipData, data, len - 4); + MOVE_FORWARD(len - 4); + MOVE_FORWARD((4 - (len % 4)) % 4); /* padding */ + } + else + return STG_E_INVALIDPARAMETER; + } + break; + } + CASE_OR_VECTOR(VT_DATE) + CASE_OR_VECTOR(VT_R8) + { + FOR_POSSIBLE_VECTOR(double, dblVal, cadbl, sizeof(double)) + { + memcpy(vec, data, sizeof(double)); + } + break; + } + CASE_OR_VECTOR(VT_R4) + { + FOR_POSSIBLE_VECTOR(float, fltVal, caflt, sizeof(float)) + { + memcpy(vec, data, sizeof(double)); + } + break; + } + case VT_DECIMAL: + { + DECIMAL *dec; + + FAIL_IF_EOF(16); + + dec = (DECIMAL*)prop; + dec->u.scale = data[2]; + dec->u.sign = data[3]; + StorageUtl_ReadDWord(data, 4, &dec->Hi32); + StorageUtl_ReadULargeInteger(data, 8, (ULARGE_INTEGER*)&dec->u1); + + MOVE_FORWARD(16); + break; + } + CASE_OR_VECTOR(VT_CLSID) + { + FOR_POSSIBLE_VECTOR_OR_SINGLEP(GUID, puuid, cauuid, 16) + { + StorageUtl_ReadGUID(data, 0, vec); + } + break; + } + case VT_VECTOR|VT_VARIANT: + { + FOR_POSSIBLE_VECTOR_OR_SINGLEP(PROPVARIANT, /* dummy */ pvarVal, capropvar, 0) + { + ULONG cbDataUsed; + hr = wine_ReadProperty(vec, data, cbData, &cbDataUsed, + diskCodepage, runningCodepage, allocate, allocate_data); + if (FAILED(hr)) + { + return hr; + } + + data += cbDataUsed; + cbData -= cbDataUsed; + } + break; + } + default: + FIXME("unsupported type %d\n", prop->vt); + return STG_E_INVALIDPARAMETER; + } + + /* Padding to 4 bytes, even for arrays of smaller integers */ + /* This is important for handling VT_VECTOR|VT_VARIANT */ + if (pcbDataUsed) { + ULONG paddingMissing; + + paddingMissing = (4 - ((cbData_Start - cbData) % 4)) % 4; + + if (cbData >= paddingMissing) + cbData -= paddingMissing; + else + cbData = 0; + + *pcbDataUsed = cbData_Start - cbData; + } + +#undef FAIL_IF_EOF +#undef READ_DWORD_AND_MOVE +#undef READ_WORD_AND_MOVE +#undef MOVE_FORWARD +#undef FOR_POSSIBLE_VECTOR +#undef FOR_POSSIBLE_VECTOR_OR_SINGLEP +#undef ALLOCATE_OR_FAIL +#undef CASE_OR_VECTOR + + return hr; +} + +/* + * Converts VT_BYREF properties to their non-ref counterparts + * + * NOTICE: This does not copy arrays, vectors etc., these are shared + * by the input and output properties. Therefore, only one may be + * eventually freed. + */ +static HRESULT ScrapeByRefOfProperty(const PROPVARIANT *in, PROPVARIANT *out) +{ + if (!(in->vt & VT_BYREF)) + { + /* no ref, no problem */ + memcpy(out, in, sizeof(*in)); + return S_OK; + } + + out->vt = in->vt & ~VT_BYREF; + + /* BYREF properties all carry a pointer, which must not be NULL here */ + /* It doesn't matter which one we check */ + if (!in->u.pcVal) + return STG_E_INVALIDPARAMETER; + + if (out->vt & VT_ARRAY) + { + out->u.parray = *in->u.pparray; + return S_OK; + } + + switch (out->vt) + { + case VT_I1: + out->u.cVal = *in->u.pcVal; + break; + case VT_UI1: + out->u.bVal = *in->u.pbVal; + break; + case VT_I2: + out->u.iVal = *in->u.piVal; + break; + case VT_UI2: + out->u.uiVal = *in->u.puiVal; + break; + case VT_I4: + case VT_INT: + out->u.lVal = *in->u.plVal; + break; + case VT_UI4: + case VT_UINT: + out->u.ulVal = *in->u.pulVal; + break; + case VT_R4: + out->u.fltVal = *in->u.pfltVal; + break; + case VT_R8: + out->u.dblVal = *in->u.pdblVal; + break; + case VT_BOOL: + out->u.boolVal = *in->u.pboolVal; + break; + case VT_DECIMAL: + memcpy(out, in->u.pdecVal, sizeof(DECIMAL)); + out->vt = VT_DECIMAL; + break; + case VT_ERROR: + out->u.scode = *in->u.pscode; + break; + case VT_CY: + memcpy(&out->u.cyVal, in->u.pcyVal, sizeof(CY)); + break; + case VT_DATE: + memcpy(&out->u.date, in->u.pdate, sizeof(DATE)); + break; + case VT_BSTR: + out->u.bstrVal = *in->u.pbstrVal; + break; + case VT_UNKNOWN: + out->u.punkVal = *in->u.ppunkVal; + break; + case VT_DISPATCH: + out->u.pdispVal = *in->u.ppdispVal; + break; + default: + return STG_E_INVALIDPARAMETER; + } + + return S_OK; +} + +/* wine_SerializedPropertySize [OLE32.@] + * + * Returns the buffer size needed to serialize the given property. + * + * This handles all straightforward cases, only crazy stuff like indirect + * properties have to be handled by the caller itself. + * + * TODO: Array handling + */ +HRESULT WINAPI wine_SerializedPropertySize(const PROPVARIANT *in_prop, + ULONG *pcbSize, UINT diskCodepage, UINT runningCodepage) +{ + ULONG size = 4; /* the header */ + HRESULT hr; + PROPVARIANT prop; + + TRACE("(%p %p %u %u)\n", in_prop, pcbSize, diskCodepage, runningCodepage); + + if (!in_prop) + return E_INVALIDARG; + if (!pcbSize) + return E_INVALIDARG; + + *pcbSize = 0; + + hr = ScrapeByRefOfProperty(in_prop, &prop); + if (FAILED(hr)) + return hr; + + switch (prop.vt) + { + case VT_EMPTY: + case VT_NULL: + break; + case VT_I1: + case VT_UI1: + size += 1; + break; + case VT_VECTOR|VT_I1: + case VT_VECTOR|VT_UI1: + size += 4; /* Vector Header */ + size += prop.u.cac.cElems * 1; + break; + case VT_I2: + case VT_UI2: + case VT_BOOL: + size += 2; + break; + case VT_VECTOR|VT_I2: + case VT_VECTOR|VT_UI2: + case VT_VECTOR|VT_BOOL: + size += 4; /* Vector Header */ + size += prop.u.cai.cElems * 2; + break; + case VT_UI4: + case VT_I4: + case VT_INT: + case VT_UINT: + case VT_ERROR: + size += 4; + break; + case VT_VECTOR|VT_UI4: + case VT_VECTOR|VT_I4: + case VT_VECTOR|VT_INT: + case VT_VECTOR|VT_UINT: + case VT_VECTOR|VT_ERROR: + size += 4; /* Vector Header */ + size += prop.u.cal.cElems * 4; + break; + case VT_I8: + case VT_UI8: + size += 8; + break; + case VT_VECTOR|VT_I8: + case VT_VECTOR|VT_UI8: + size += 4; /* Vector header */ + size += prop.u.cah.cElems * 8; + break; + case VT_LPSTR: + { + ULONG len; + ULONG convertedLen; + + size += 4; /* DWORD byte count */ + + if (runningCodepage == CP_UNICODE) + len = lstrlenW(prop.u.pwszVal) * sizeof(WCHAR) + 2; + else + len = lstrlenA(prop.u.pszVal) + 1; + + hr = ConvertCodepageSize(prop.u.pszVal, + len, + &convertedLen, + runningCodepage, + diskCodepage, FALSE); + if (FAILED(hr)) + return hr; + + size += convertedLen; + + break; + } + case VT_VECTOR|VT_LPSTR: + { + ULONG i; + + size += 4; /* Vector Header */ + for (i = 0; i < prop.u.calpstr.cElems; ++i) + { + ULONG len; + ULONG convertedLen; + + size += 4; /* DWORD byte count */ + + if (runningCodepage == CP_UNICODE) + len = lstrlenW((LPCWSTR)prop.u.calpstr.pElems[i]) * sizeof(WCHAR) + sizeof(WCHAR); + else + len = lstrlenA(prop.u.calpstr.pElems[i]) + 1; + + hr = ConvertCodepageSize(prop.u.calpstr.pElems[i], + len, + &convertedLen, + runningCodepage, + diskCodepage, FALSE); + if (FAILED(hr)) + return hr; + + size += convertedLen; + + size += (4 - (len % 4)) % 4; /* padding */ + } + break; + } + case VT_BSTR: + { + /* bstrs are possibly saved as ansi string */ + if (diskCodepage == CP_UNICODE) + { + ULONG len = 4; /* DWORD byte count */ + len += SysStringLen(prop.u.bstrVal) * sizeof(WCHAR) + sizeof(WCHAR); + size += len; + } + else + { + UINT cw = SysStringLen(prop.u.bstrVal); + + size += 4; /* DWORD byte count */ + size += WideCharToMultiByte(diskCodepage, 0, prop.u.bstrVal, cw, NULL, 0, NULL, NULL); + size += 1; /* terminating null */ + } + + break; + } + case VT_VECTOR|VT_BSTR: + { + ULONG i; + size += 4; /* Vector Header */ + for (i = 0; i < prop.u.cabstr.cElems; ++i) + { + ULONG len = 4; /* DWORD byte count */ + + /* bstrs are possibly saved as ansi string */ + if (diskCodepage == CP_UNICODE) + { + len += SysStringLen(prop.u.cabstr.pElems[i]) * sizeof(WCHAR) + sizeof(WCHAR); + } + else + { + UINT cw = SysStringLen(prop.u.cabstr.pElems[i]); + + len += WideCharToMultiByte(diskCodepage, 0, + prop.u.cabstr.pElems[i], cw, NULL, 0, NULL, NULL); + len += 1; /* terminating null */ + } + + len += (4 - (len % 4)) % 4; /* padding */ + size += len; + } + break; + } + case VT_BLOB: + size += 4; /* DWORD byte count */ + size += prop.u.blob.cbSize; + break; + case VT_LPWSTR: + size += 4; /* DWORD char count */ + size += (lstrlenW(prop.u.pwszVal) + 1) * sizeof(WCHAR); + break; + case VT_VECTOR|VT_LPWSTR: + { + ULONG i; + size += 4; /* Vector header */ + for (i = 0; i < prop.u.calpwstr.cElems; ++i) + { + ULONG len = 4; /* DWORD char count */ + len += (lstrlenW(prop.u.calpwstr.pElems[i]) + 1) * sizeof(WCHAR); + len += (4 - (len % 4)) % 4; /* padding */ + + size += len; + } + break; + } + case VT_FILETIME: + case VT_CY: + size += 8; /* 64bit integer */ + break; + case VT_VECTOR|VT_FILETIME: + case VT_VECTOR|VT_CY: + size += 4; /* Vector header */ + size += prop.u.cafiletime.cElems * 8; + break; + case VT_DATE: + case VT_R8: + size += 8; /* double */ + break; + case VT_VECTOR|VT_DATE: + case VT_VECTOR|VT_R8: + size += 4; /* Vector header */ + size += prop.u.cadbl.cElems * 8; + break; + case VT_R4: + size += 4; /* float */ + break; + case VT_VECTOR|VT_R4: + size += 4; /* Vector header */ + size += prop.u.caflt.cElems * 4; + break; + case VT_DECIMAL: + size += 16; + break; + case VT_CLSID: + size += sizeof(GUID); + break; + case VT_VECTOR|VT_CLSID: + size += 4; /* Vector header */ + size += prop.u.cauuid.cElems * sizeof(GUID); + break; + case VT_CF: + size += 4; /* size field */ + size += prop.u.pclipdata->cbSize; /* includes tag field */ + break; + case VT_VECTOR|VT_CF: + { + ULONG i; + + size += 4; /* Vector header */ + for (i = 0; i < prop.u.caclipdata.cElems; ++i) + { + ULONG len = 4; /* DWORD size field */ + len += prop.u.caclipdata.pElems[i].cbSize; /* includes tag field */ + len += (4 - (len % 4)) % 4; /* padding */ + + size += len; + } + break; + } + case VT_VECTOR|VT_VARIANT: + { + HRESULT hr; + ULONG vsize; + ULONG i; + + size += 4; /* Vector header */ + for (i = 0; i < prop.u.capropvar.cElems; ++i) + { + hr = wine_SerializedPropertySize(&prop.u.capropvar.pElems[i], + &vsize, + diskCodepage, runningCodepage); + if (FAILED(hr)) + return hr; + + size += vsize; + size += (4 - (vsize % 4)) % 4; + } + break; + } + default: + FIXME("unsupported type %d\n", prop.vt); + return STG_E_INVALIDPARAMETER; + } + + /* always pad to 4 bytes */ + size += (4 - (size % 4)) % 4; + *pcbSize = size; + + return S_OK; +} + +/* wine_WritePropertyToBuffer [OLE32.@] + * + * Base implementation for serializing properties. + * Used by: + * - IPropertyStorage + * - StgConvertVariantToProperty + * - StgSerializeProperty [propsys.dll] + * + * This handles all straightforward cases, only crazy stuff like indirect + * properties have to be handled by the caller itself. + * + * TODO: Array handling */ -static HRESULT PropertyStorage_ReadProperty(PROPVARIANT *prop, const BYTE *data, - UINT codepage, void* (WINAPI *allocate)(void *this, ULONG size), void *allocate_data) +HRESULT WINAPI wine_WritePropertyToBuffer(const PROPVARIANT *in_prop, + BYTE *buffer, + ULONG cbBufSize, + ULONG *pcbBufSizeUsed, + UINT diskCodepage, + UINT runningCodepage) { HRESULT hr = S_OK; + ULONG cbBufSize_Start; + PROPVARIANT prop;
- assert(prop); - assert(data); - StorageUtl_ReadDWord(data, 0, (DWORD *)&prop->vt); - data += sizeof(DWORD); - switch (prop->vt) + TRACE("(%p %p %u %p %u %u)\n", in_prop, buffer, cbBufSize, + pcbBufSizeUsed, diskCodepage, runningCodepage); + + cbBufSize_Start = cbBufSize; + + if (pcbBufSizeUsed) + *pcbBufSizeUsed = 0; + + hr = ScrapeByRefOfProperty(in_prop, &prop); + if (FAILED(hr)) + return hr; + +#define FAIL_IF_EOF(cbytes) \ + do { \ + if ((ULONG)cbytes > cbBufSize) \ + return STG_E_INVALIDPARAMETER; \ + } while (0) +#define MOVE_FORWARD(bytes) \ + do { \ + FAIL_IF_EOF(bytes); \ + buffer += bytes; \ + cbBufSize -= bytes; \ + } while (0) +#define WRITE_WORD_AND_MOVE(value) \ + do { \ + FAIL_IF_EOF(2); \ + StorageUtl_WriteWord(buffer, 0, (value)); \ + MOVE_FORWARD(2); \ + } while (0) +#define WRITE_DWORD_AND_MOVE(value) \ + do { \ + FAIL_IF_EOF(4); \ + StorageUtl_WriteDWord(buffer, 0, (value)); \ + MOVE_FORWARD(4); \ + } while (0) +#define PAD_TO_MUL_AND_MOVE(cbLen, cbMul) \ + do { \ + FAIL_IF_EOF(((cbMul) - ((cbLen) % (cbMul))) % (cbMul)); \ + ZeroMemory(buffer, ((cbMul) - ((cbLen) % (cbMul))) % (cbMul)); \ + MOVE_FORWARD(((cbMul) - ((cbLen) % (cbMul))) % (cbMul)); \ + } while(0) +#define FOR_POSSIBLE_VECTOR(ctype, singleMember, vectorMember, cbBlock) \ + const ctype *vec; \ + DWORD vecsize; \ + if (!(prop.vt & VT_VECTOR)) \ + { \ + vecsize = 1; \ + vec = &prop.u.singleMember; \ + } \ + else \ + { \ + vecsize = prop.u.vectorMember.cElems; \ + WRITE_DWORD_AND_MOVE(vecsize); \ + vec = prop.u.vectorMember.pElems; \ + } \ + for (; vecsize; --vecsize, ++vec, buffer += (cbBlock), cbBufSize -= (cbBlock)) \ + if (gt((ULONG)(cbBlock), cbBufSize)) \ + return STG_E_INVALIDPARAMETER; \ + else + +#define FOR_POSSIBLE_VECTOR_OR_SINGLEP(ctype, singleMemberP, vectorMember, cbBlock) \ + const ctype *vec; \ + DWORD vecsize; \ + if (!(prop.vt & VT_VECTOR)) \ + { \ + vecsize = 1; \ + vec = prop.u.singleMemberP; \ + } \ + else \ + { \ + vecsize = prop.u.vectorMember.cElems; \ + WRITE_DWORD_AND_MOVE(vecsize); \ + vec = prop.u.vectorMember.pElems; \ + } \ + for (; vecsize; --vecsize, ++vec, buffer += (cbBlock), cbBufSize -= (cbBlock)) \ + if (gt((ULONG)(cbBlock), cbBufSize)) \ + return STG_E_INVALIDPARAMETER; \ + else + +#define CASE_OR_VECTOR(vtype) \ + case vtype: \ + case VT_VECTOR|vtype: + + assert(buffer); + + TRACE("Serializing variant of type %d to buffer size %lu\n", prop.vt, (unsigned long)cbBufSize); + + /* variant type */ + WRITE_DWORD_AND_MOVE((DWORD)prop.vt); + + switch (prop.vt) { case VT_EMPTY: case VT_NULL: break; - case VT_I1: - prop->u.cVal = *(const char *)data; - TRACE("Read char 0x%x ('%c')\n", prop->u.cVal, prop->u.cVal); - break; - case VT_UI1: - prop->u.bVal = *data; - TRACE("Read byte 0x%x\n", prop->u.bVal); + CASE_OR_VECTOR(VT_I1) + { + FOR_POSSIBLE_VECTOR(char, cVal, cac, 1) + { + *(char *)buffer = *vec; + TRACE("Write char 0x%x ('%c')\n", vec[0], vec[0]); + } break; - case VT_I2: - StorageUtl_ReadWord(data, 0, (WORD*)&prop->u.iVal); - TRACE("Read short %d\n", prop->u.iVal); + } + CASE_OR_VECTOR(VT_UI1) + { + FOR_POSSIBLE_VECTOR(BYTE, bVal, caub, 1) + { + *buffer = *vec; + TRACE("Write byte 0x%x\n", *vec); + } break; - case VT_UI2: - StorageUtl_ReadWord(data, 0, &prop->u.uiVal); - TRACE("Read ushort %d\n", prop->u.uiVal); + } + CASE_OR_VECTOR(VT_I2) + { + FOR_POSSIBLE_VECTOR(SHORT, iVal, cai, 2) + { + StorageUtl_WriteWord(buffer, 0, *(WORD*)vec); + TRACE("Write short %d\n", (int)*vec); + } break; - case VT_INT: - case VT_I4: - StorageUtl_ReadDWord(data, 0, (DWORD*)&prop->u.lVal); - TRACE("Read long %d\n", prop->u.lVal); + } + CASE_OR_VECTOR(VT_UI2) + CASE_OR_VECTOR(VT_BOOL) /* VARIANT_BOOL == USHORT */ + { + FOR_POSSIBLE_VECTOR(USHORT, uiVal, caui, 2) + { + StorageUtl_WriteWord(buffer, 0, *vec); + TRACE("Write ushort %d\n", (int)*vec); + } break; - case VT_UINT: - case VT_UI4: - StorageUtl_ReadDWord(data, 0, &prop->u.ulVal); - TRACE("Read ulong %d\n", prop->u.ulVal); + } + CASE_OR_VECTOR(VT_INT) + CASE_OR_VECTOR(VT_I4) + CASE_OR_VECTOR(VT_ERROR) /* SCODE == LONG */ + { + FOR_POSSIBLE_VECTOR(LONG, lVal, cal, 4) + { + StorageUtl_WriteDWord(buffer, 0, *(DWORD*)vec); + TRACE("Write long %d\n", *vec); + } break; - case VT_LPSTR: + } + CASE_OR_VECTOR(VT_UINT) + CASE_OR_VECTOR(VT_UI4) { - DWORD count; - - StorageUtl_ReadDWord(data, 0, &count); - if (codepage == CP_UNICODE && count % 2) + FOR_POSSIBLE_VECTOR(ULONG, ulVal, caul, 4) { - WARN("Unicode string has odd number of bytes\n"); - hr = STG_E_INVALIDHEADER; + StorageUtl_WriteDWord(buffer, 0, *vec); + TRACE("Write ulong %u\n", *vec); } - else + break; + } + CASE_OR_VECTOR(VT_I8) + { + FOR_POSSIBLE_VECTOR(LARGE_INTEGER, hVal, cah, 8) { - prop->u.pszVal = allocate(allocate_data, count); - if (prop->u.pszVal) - { - memcpy(prop->u.pszVal, data + sizeof(DWORD), count); - /* This is stored in the code page specified in codepage. - * Don't convert it, the caller will just store it as-is. - */ - if (codepage == CP_UNICODE) - { - /* Make sure it's NULL-terminated */ - prop->u.pszVal[count / sizeof(WCHAR) - 1] = '\0'; - TRACE("Read string value %s\n", - debugstr_w(prop->u.pwszVal)); - } - else - { - /* Make sure it's NULL-terminated */ - prop->u.pszVal[count - 1] = '\0'; - TRACE("Read string value %s\n", debugstr_a(prop->u.pszVal)); - } - } - else - hr = STG_E_INSUFFICIENTMEMORY; + StorageUtl_WriteULargeInteger(buffer, 0, (ULARGE_INTEGER*)vec); + TRACE("Write longlong 0x%lx%08lx\n", (unsigned long)vec->HighPart, (unsigned long)vec->LowPart); } break; } - case VT_BSTR: + CASE_OR_VECTOR(VT_UI8) { - DWORD count, wcount; - - StorageUtl_ReadDWord(data, 0, &count); - if (codepage == CP_UNICODE && count % 2) + FOR_POSSIBLE_VECTOR(ULARGE_INTEGER, uhVal, cauh, 8) { - WARN("Unicode string has odd number of bytes\n"); - hr = STG_E_INVALIDHEADER; + StorageUtl_WriteULargeInteger(buffer, 0, vec); + TRACE("Write ulonglong 0x%lx%08lx\n", (unsigned long)vec->HighPart, (unsigned long)vec->LowPart); } - else + break; + } + CASE_OR_VECTOR(VT_LPSTR) + { + FOR_POSSIBLE_VECTOR(LPSTR, pszVal, calpstr, 0) { - if (codepage == CP_UNICODE) - wcount = count / 2; + DWORD len; + DWORD convertedLen; + + if (runningCodepage == CP_UNICODE) + len = lstrlenW((LPCWSTR)*vec) * sizeof(WCHAR) + 2; else - wcount = MultiByteToWideChar(codepage, 0, (LPCSTR)(data + sizeof(DWORD)), count, NULL, 0); + len = lstrlenA(*vec) + 1; + + hr = ConvertCodepageSize(*vec, len, &convertedLen, + runningCodepage, diskCodepage, FALSE); + if (FAILED(hr)) + return hr; + + WRITE_DWORD_AND_MOVE(convertedLen); + FAIL_IF_EOF(convertedLen);
- prop->u.bstrVal = SysAllocStringLen(NULL, wcount); /* FIXME: use allocator? */ + hr = ConvertCodepage(*vec, len, + (char*)buffer, convertedLen, + runningCodepage, diskCodepage, FALSE); + if (FAILED(hr)) + return hr;
- if (prop->u.bstrVal) + if (diskCodepage == CP_UNICODE) { - if (codepage == CP_UNICODE) - memcpy(prop->u.bstrVal, data + sizeof(DWORD), count); - else - MultiByteToWideChar(codepage, 0, (LPCSTR)(data + sizeof(DWORD)), count, prop->u.bstrVal, wcount); + PropertyStorage_ByteSwapString(buffer, convertedLen/2); + TRACE("Write lpstr %s\n", debugstr_w((LPCWSTR)buffer)); + } + else + { + TRACE("Write lpstr %s\n", debugstr_a((char*)buffer)); + } + + MOVE_FORWARD(convertedLen); + PAD_TO_MUL_AND_MOVE(convertedLen, 4); + } + break; + } + CASE_OR_VECTOR(VT_BSTR) + { + FOR_POSSIBLE_VECTOR(BSTR, bstrVal, cabstr, 0) + { + /* BSTRs are saved in the codepage */ + DWORD len = SysStringLen(*vec) + 1; + + if (diskCodepage == CP_UNICODE) + { + WRITE_DWORD_AND_MOVE(len * sizeof(WCHAR));
- prop->u.bstrVal[wcount - 1] = '\0'; - TRACE("Read string value %s\n", debugstr_w(prop->u.bstrVal)); + FAIL_IF_EOF(len * sizeof(WCHAR)); + memcpy(buffer, *vec, len * sizeof(WCHAR)); + MOVE_FORWARD(len * sizeof(WCHAR)); + PAD_TO_MUL_AND_MOVE(len * sizeof(WCHAR), 4); } else - hr = STG_E_INSUFFICIENTMEMORY; + { + DWORD narrowLen; + + narrowLen = WideCharToMultiByte(diskCodepage, 0, *vec, len, NULL, 0, NULL, NULL); + + WRITE_DWORD_AND_MOVE(narrowLen); + + FAIL_IF_EOF(narrowLen); + WideCharToMultiByte(diskCodepage, 0, *vec, len, (char*)buffer, narrowLen, NULL, NULL); + MOVE_FORWARD(narrowLen); + PAD_TO_MUL_AND_MOVE(narrowLen, 4); + } + TRACE("Write BSTR %s\n", debugstr_w(*vec)); } break; } case VT_BLOB: { - DWORD count; + WRITE_DWORD_AND_MOVE(prop.u.blob.cbSize); + FAIL_IF_EOF(prop.u.blob.cbSize); + memcpy(buffer, prop.u.blob.pBlobData, prop.u.blob.cbSize); + MOVE_FORWARD(prop.u.blob.cbSize); + PAD_TO_MUL_AND_MOVE(prop.u.blob.cbSize, 4);
- StorageUtl_ReadDWord(data, 0, &count); - prop->u.blob.cbSize = count; - prop->u.blob.pBlobData = allocate(allocate_data, count); - if (prop->u.blob.pBlobData) + TRACE("Write blob value of size %d\n", prop.u.blob.cbSize); + break; + } + CASE_OR_VECTOR(VT_LPWSTR) + { + FOR_POSSIBLE_VECTOR(LPWSTR, pwszVal, calpwstr, 0) { - memcpy(prop->u.blob.pBlobData, data + sizeof(DWORD), count); - TRACE("Read blob value of size %d\n", count); + DWORD len; + + len = lstrlenW((LPCWSTR)*vec) + 1; + WRITE_DWORD_AND_MOVE(len); + + FAIL_IF_EOF(len * sizeof(WCHAR)); + memcpy(buffer, *vec, len * sizeof(WCHAR)); + MOVE_FORWARD(len * sizeof(WCHAR)); + PAD_TO_MUL_AND_MOVE(len * sizeof(WCHAR), 4); + TRACE("Write lpwstr %s\n", debugstr_w(*vec)); } - else - hr = STG_E_INSUFFICIENTMEMORY; break; } - case VT_LPWSTR: + CASE_OR_VECTOR(VT_FILETIME) { - DWORD count; + ULARGE_INTEGER tmp; + FOR_POSSIBLE_VECTOR(FILETIME, filetime, cafiletime, 8) + { + tmp.LowPart = vec->dwLowDateTime; + tmp.HighPart = vec->dwHighDateTime;
- StorageUtl_ReadDWord(data, 0, &count); - prop->u.pwszVal = allocate(allocate_data, count * sizeof(WCHAR)); - if (prop->u.pwszVal) + StorageUtl_WriteULargeInteger(buffer, 0, &tmp); + } + break; + } + CASE_OR_VECTOR(VT_CY) + { + ULARGE_INTEGER tmp; + FOR_POSSIBLE_VECTOR(CY, cyVal, cacy, 8) { - memcpy(prop->u.pwszVal, data + sizeof(DWORD), - count * sizeof(WCHAR)); - /* make sure string is NULL-terminated */ - prop->u.pwszVal[count - 1] = '\0'; - PropertyStorage_ByteSwapString(prop->u.pwszVal, count); - TRACE("Read string value %s\n", debugstr_w(prop->u.pwszVal)); + *((LONGLONG*)&tmp.QuadPart) = vec->int64; + StorageUtl_WriteULargeInteger(buffer, 0, &tmp); } - else - hr = STG_E_INSUFFICIENTMEMORY; break; } - case VT_FILETIME: - StorageUtl_ReadULargeInteger(data, 0, - (ULARGE_INTEGER *)&prop->u.filetime); + CASE_OR_VECTOR(VT_CF) + { + FOR_POSSIBLE_VECTOR_OR_SINGLEP(CLIPDATA, pclipdata, caclipdata, 0) + { + WRITE_DWORD_AND_MOVE(vec->cbSize); + WRITE_DWORD_AND_MOVE(vec->ulClipFmt); + + FAIL_IF_EOF(vec->cbSize - 4); + memcpy(buffer, vec->pClipData, vec->cbSize - 4); + MOVE_FORWARD(vec->cbSize - 4); + + PAD_TO_MUL_AND_MOVE(vec->cbSize, 4); + TRACE("Written CLPIDATA with size %lu\n", (unsigned long)vec->cbSize); + } break; - case VT_CF: + } + CASE_OR_VECTOR(VT_DATE) + CASE_OR_VECTOR(VT_R8) + { + FOR_POSSIBLE_VECTOR(double, dblVal, cadbl, sizeof(double)) { - DWORD len = 0, tag = 0; + memcpy(buffer, vec, sizeof(double)); + } + break; + } + CASE_OR_VECTOR(VT_R4) + { + FOR_POSSIBLE_VECTOR(float, fltVal, caflt, sizeof(float)) + { + memcpy(buffer, vec, sizeof(double)); + } + break; + } + case VT_DECIMAL: + { + DECIMAL *dec; + + FAIL_IF_EOF(16); + + dec = (DECIMAL*)∝ + buffer[0] = 0; + buffer[1] = 0; + buffer[2] = dec->u.scale; + buffer[3] = dec->u.sign; + StorageUtl_WriteDWord(buffer, 4, dec->Hi32); + StorageUtl_WriteULargeInteger(buffer, 8, (ULARGE_INTEGER*)&dec->u1);
- StorageUtl_ReadDWord(data, 0, &len); - StorageUtl_ReadDWord(data, 4, &tag); - if (len > 8) + MOVE_FORWARD(16); + break; + } + CASE_OR_VECTOR(VT_CLSID) + { + FOR_POSSIBLE_VECTOR_OR_SINGLEP(GUID, puuid, cauuid, 16) + { + StorageUtl_WriteGUID(buffer, 0, vec); + } + break; + } + case VT_VECTOR|VT_VARIANT: + { + FOR_POSSIBLE_VECTOR_OR_SINGLEP(PROPVARIANT, /* dummy */pvarVal, capropvar, 0) + { + ULONG cbBufferUsed; + hr = wine_WritePropertyToBuffer(vec, buffer, cbBufSize, + &cbBufferUsed, diskCodepage, runningCodepage); + if (FAILED(hr)) { - len -= 8; - prop->u.pclipdata = allocate(allocate_data, sizeof (CLIPDATA)); - prop->u.pclipdata->cbSize = len; - prop->u.pclipdata->ulClipFmt = tag; - prop->u.pclipdata->pClipData = allocate(allocate_data, len - sizeof(prop->u.pclipdata->ulClipFmt)); - memcpy(prop->u.pclipdata->pClipData, data+8, len - sizeof(prop->u.pclipdata->ulClipFmt)); + return hr; } - else - hr = STG_E_INVALIDPARAMETER; + + buffer += cbBufferUsed; + cbBufSize -= cbBufferUsed; } break; + } default: - FIXME("unsupported type %d\n", prop->vt); - hr = STG_E_INVALIDPARAMETER; + FIXME("unsupported type %d\n", prop.vt); + return STG_E_INVALIDPARAMETER; } + + /* Pad to 4 bytes. Always. */ + PAD_TO_MUL_AND_MOVE(cbBufSize_Start - cbBufSize, 4); + + if (pcbBufSizeUsed) + *pcbBufSizeUsed = cbBufSize_Start - cbBufSize; + +#undef FAIL_IF_EOF +#undef WRITE_DWORD_AND_MOVE +#undef WRITE_WORD_AND_MOVE +#undef MOVE_FORWARD +#undef FOR_POSSIBLE_VECTOR +#undef FOR_POSSIBLE_VECTOR_OR_SINGLEP +#undef PAD_TO_MUL_AND_MOVE +#undef CASE_OR_VECTOR + return hr; }
@@ -1439,11 +2747,14 @@ static HRESULT PropertyStorage_ReadFromStream(PropertyStorage_impl *This) else { PROPVARIANT prop; + HRESULT hr;
PropVariantInit(&prop); - if (SUCCEEDED(PropertyStorage_ReadProperty(&prop, - buf + idOffset->dwOffset - sizeof(PROPERTYSECTIONHEADER), - This->codePage, Allocate_CoTaskMemAlloc, NULL))) + hr = wine_ReadProperty(&prop, + buf + idOffset->dwOffset - sizeof(PROPERTYSECTIONHEADER), + sectionHdr.cbSection - idOffset->dwOffset, + NULL, This->codePage, This->codePage, NULL, NULL); + if (SUCCEEDED(hr)) { TRACE("Read property with ID 0x%08x, type %d\n", idOffset->propid, prop.vt); @@ -1468,6 +2779,11 @@ static HRESULT PropertyStorage_ReadFromStream(PropertyStorage_impl *This) idOffset->propid, &prop, This->codePage); } } + else + { + WARN("Failed to read property with ID 0x%08x, hr=0x%08x\n", + idOffset->propid, hr); + } PropVariantClear(&prop); } } @@ -1689,7 +3005,9 @@ static HRESULT PropertyStorage_WritePropertyToStream(PropertyStorage_impl *This, LARGE_INTEGER seek; PROPERTYIDOFFSET propIdOffset; ULONG count; - DWORD dwType, bytesWritten; + DWORD bytesWritten; + BYTE *propBuffer = NULL; + ULONG bufferUsed = 0;
assert(var); assert(sectionOffset); @@ -1711,112 +3029,33 @@ static HRESULT PropertyStorage_WritePropertyToStream(PropertyStorage_impl *This, hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); if (FAILED(hr)) goto end; - StorageUtl_WriteDWord((LPBYTE)&dwType, 0, var->vt); - hr = IStream_Write(This->stm, &dwType, sizeof(dwType), &count); - if (FAILED(hr)) - goto end; - *sectionOffset += sizeof(dwType);
- switch (var->vt) - { - case VT_EMPTY: - case VT_NULL: - bytesWritten = 0; - break; - case VT_I1: - case VT_UI1: - hr = IStream_Write(This->stm, &var->u.cVal, sizeof(var->u.cVal), - &count); - bytesWritten = count; - break; - case VT_I2: - case VT_UI2: + hr = wine_SerializedPropertySize(var, &count, This->codePage, This->codePage); + if (FAILED(hr)) { - WORD wTemp; - - StorageUtl_WriteWord((LPBYTE)&wTemp, 0, var->u.iVal); - hr = IStream_Write(This->stm, &wTemp, sizeof(wTemp), &count); - bytesWritten = count; - break; + WARN("Property space could not be calculated: hr=%08x\n", hr); + goto end; } - case VT_I4: - case VT_UI4: - { - DWORD dwTemp;
- StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, var->u.lVal); - hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); - bytesWritten = count; - break; - } - case VT_LPSTR: + propBuffer = CoTaskMemAlloc(count); + if (!propBuffer) { - DWORD len, dwTemp; - - if (This->codePage == CP_UNICODE) - len = (lstrlenW(var->u.pwszVal) + 1) * sizeof(WCHAR); - else - len = lstrlenA(var->u.pszVal) + 1; - StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, len); - hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); - if (FAILED(hr)) - goto end; - hr = IStream_Write(This->stm, var->u.pszVal, len, &count); - bytesWritten = count + sizeof(DWORD); - break; + hr = STG_E_INSUFFICIENTMEMORY; + goto end; } - case VT_LPWSTR: - { - DWORD len = lstrlenW(var->u.pwszVal) + 1, dwTemp;
- StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, len); - hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); - if (FAILED(hr)) - goto end; - hr = IStream_Write(This->stm, var->u.pwszVal, len * sizeof(WCHAR), - &count); - bytesWritten = count + sizeof(DWORD); - break; - } - case VT_FILETIME: + hr = wine_WritePropertyToBuffer(var, propBuffer, count, &bufferUsed, This->codePage, This->codePage); + if (SUCCEEDED(hr)) { - FILETIME temp; + if (bufferUsed != count) + WARN("Property buffer calculation was off by %d bytes\n", (int)(count - bufferUsed));
- StorageUtl_WriteULargeInteger((BYTE *)&temp, 0, - (const ULARGE_INTEGER *)&var->u.filetime); - hr = IStream_Write(This->stm, &temp, sizeof(FILETIME), &count); + hr = IStream_Write(This->stm, propBuffer, bufferUsed, &count); bytesWritten = count; - break; - } - case VT_CF: - { - DWORD cf_hdr[2], len; - - len = var->u.pclipdata->cbSize; - StorageUtl_WriteDWord((LPBYTE)&cf_hdr[0], 0, len + 8); - StorageUtl_WriteDWord((LPBYTE)&cf_hdr[1], 0, var->u.pclipdata->ulClipFmt); - hr = IStream_Write(This->stm, cf_hdr, sizeof(cf_hdr), &count); - if (FAILED(hr)) - goto end; - hr = IStream_Write(This->stm, var->u.pclipdata->pClipData, - len - sizeof(var->u.pclipdata->ulClipFmt), &count); - if (FAILED(hr)) - goto end; - bytesWritten = count + sizeof cf_hdr; - break; } - case VT_CLSID: + else { - CLSID temp; - - StorageUtl_WriteGUID((BYTE *)&temp, 0, var->u.puuid); - hr = IStream_Write(This->stm, &temp, sizeof(temp), &count); - bytesWritten = count; - break; - } - default: - FIXME("unsupported type: %d\n", var->vt); - return STG_E_INVALIDPARAMETER; + WARN("Property could not be written: hr=%08x\n", hr); }
if (SUCCEEDED(hr)) @@ -2748,11 +3987,14 @@ BOOLEAN WINAPI StgConvertPropertyToVariant(const SERIALIZEDPROPERTYVALUE* prop, { HRESULT hr;
- hr = PropertyStorage_ReadProperty(pvar, (const BYTE*)prop, CodePage, Allocate_PMemoryAllocator, pma); + hr = wine_ReadProperty(pvar, (const BYTE*)prop, /*FIXME*/0xFFFFFFFF, NULL, + CodePage, CP_ACP, Allocate_PMemoryAllocator, pma);
if (FAILED(hr)) { FIXME("should raise C++ exception on failure\n"); + /* FIXME: can't clear partially initialized PROPVARIANT because + * PropVariantClear can't deal with a custom Free function */ PropVariantInit(pvar); }
@@ -2763,7 +4005,32 @@ SERIALIZEDPROPERTYVALUE* WINAPI StgConvertVariantToProperty(const PROPVARIANT *p USHORT CodePage, SERIALIZEDPROPERTYVALUE *pprop, ULONG *pcb, PROPID pid, BOOLEAN fReserved, ULONG *pcIndirect) { - FIXME("%p,%d,%p,%p,%d,%d,%p\n", pvar, CodePage, pprop, pcb, pid, fReserved, pcIndirect); + HRESULT hr; + ULONG buffer_size; + + TRACE("%p,%d,%p,%p,%d,%d,%p\n", pvar, CodePage, pprop, pcb, pid, fReserved, pcIndirect); + + if (pprop && pcb) + buffer_size = *pcb; + else + buffer_size = (ULONG)-1; + + /* TODO: indirect properties and other crazyness */ + if (pprop) + { + hr = wine_WritePropertyToBuffer(pvar, (BYTE*)pprop, buffer_size, pcb, CodePage, CP_ACP); + } + else + { + hr = wine_SerializedPropertySize(pvar, pcb, CodePage, CP_ACP); + } + + if (FAILED(hr)) + { + FIXME("should raise exception on failure\n"); + *pcb = 0; + return NULL; + }
- return NULL; + return pprop; } diff --git a/dlls/ole32/tests/propvariant.c b/dlls/ole32/tests/propvariant.c index c45ca2a..d9b4320 100644 --- a/dlls/ole32/tests/propvariant.c +++ b/dlls/ole32/tests/propvariant.c @@ -438,6 +438,23 @@ static const char serialized_bstr_mb[] = { 0,0,0,0 };
+static const char serialized_ascii_str_wc[] = { + 30,0, /* VT_LPSTR */ + 0,0, /* padding */ + 10,0,0,0, /* size */ + 't',0,'e',0, + 's',0,'t',0, + 0,0,0,0 +}; + +static const char serialized_ascii_str_mb[] = { + 30,0, /* VT_LPSTR */ + 0,0, /* padding */ + 5,0,0,0, /* size */ + 't','e','s','t', + 0,0,0,0 +}; + static void test_propertytovariant(void) { HANDLE hole32; @@ -447,6 +464,7 @@ static void test_propertytovariant(void) struct _PMemoryAllocator_vtable vtable; BOOLEAN ret; static const WCHAR test_string[] = {'t','e','s','t',0}; + static const char test_astring[] = "test";
hole32 = GetModuleHandleA("ole32");
@@ -495,6 +513,22 @@ static void test_propertytovariant(void) ok(propvar.vt == VT_BSTR, "unexpected vt %x\n", propvar.vt); ok(!lstrcmpW(U(propvar).bstrVal, test_string), "unexpected string value\n"); PropVariantClear(&propvar); + + ret = pStgConvertPropertyToVariant((SERIALIZEDPROPERTYVALUE*)serialized_ascii_str_wc, + CP_WINUNICODE, &propvar, &allocator); + + ok(ret == 0, "StgConvertPropertyToVariant returned %i\n", ret); + ok(propvar.vt == VT_LPSTR, "unexpected vt %x\n", propvar.vt); + ok(!lstrcmpA(U(propvar).pszVal, test_astring), "unexpected string value '%s'\n", U(propvar).pszVal); + PropVariantClear(&propvar); + + ret = pStgConvertPropertyToVariant((SERIALIZEDPROPERTYVALUE*)serialized_ascii_str_mb, + CP_UTF8, &propvar, &allocator); + + ok(ret == 0, "StgConvertPropertyToVariant returned %i\n", ret); + ok(propvar.vt == VT_LPSTR, "unexpected vt %x\n", propvar.vt); + ok(!lstrcmpA(U(propvar).pszVal, test_astring), "unexpected string value '%s'\n", U(propvar).pszVal); + PropVariantClear(&propvar); }
static void test_varianttoproperty(void) @@ -506,6 +540,7 @@ static void test_varianttoproperty(void) const PROPVARIANT*,USHORT,SERIALIZEDPROPERTYVALUE*,ULONG*,PROPID,BOOLEAN,ULONG*); ULONG len; static const WCHAR test_string[] = {'t','e','s','t',0}; + static char test_astring[] = "test"; BSTR test_string_bstr;
hole32 = GetModuleHandleA("ole32"); @@ -530,7 +565,7 @@ static void test_varianttoproperty(void) 0, FALSE, 0);
ok(propvalue == NULL, "got nonnull propvalue\n"); - todo_wine ok(len == 8, "unexpected length %d\n", len); + ok(len == 8, "unexpected length %d\n", len);
if (len == 0xdeadbeef) { @@ -588,6 +623,24 @@ static void test_varianttoproperty(void)
SysFreeString(test_string_bstr);
+ propvar.vt = VT_LPSTR; + U(propvar).pszVal = test_astring; + len = 20; + propvalue = pStgConvertVariantToProperty(&propvar, CP_WINUNICODE, own_propvalue, &len, + 0, FALSE, 0); + + ok(propvalue == own_propvalue, "unexpected propvalue %p\n", propvalue); + ok(len == 20, "unexpected length %d\n", len); + ok(!memcmp(propvalue, serialized_ascii_str_wc, 20), "got wrong data\n"); + + len = 20; + propvalue = pStgConvertVariantToProperty(&propvar, CP_UTF8, own_propvalue, &len, + 0, FALSE, 0); + + ok(propvalue == own_propvalue, "unexpected propvalue %p\n", propvalue); + ok(len == 16, "unexpected length %d\n", len); + ok(!memcmp(propvalue, serialized_ascii_str_mb, 16), "got wrong data\n"); + HeapFree(GetProcessHeap(), 0, own_propvalue); }
diff --git a/dlls/propsys/propsys.spec b/dlls/propsys/propsys.spec index 92b6d7c..50bdf6e 100644 --- a/dlls/propsys/propsys.spec +++ b/dlls/propsys/propsys.spec @@ -27,24 +27,24 @@ @ stdcall -private DllGetClassObject(ptr ptr ptr) @ stdcall -private DllRegisterServer() @ stdcall -private DllUnregisterServer() -@ stub InitPropVariantFromBooleanVector +@ stdcall InitPropVariantFromBooleanVector(ptr long ptr) @ stdcall InitPropVariantFromBuffer(ptr long ptr) -@ stub InitPropVariantFromCLSID -@ stub InitPropVariantFromDoubleVector -@ stub InitPropVariantFromFileTime -@ stub InitPropVariantFromFileTimeVector +@ stdcall InitPropVariantFromCLSID(ptr ptr) +@ stdcall InitPropVariantFromDoubleVector(ptr long ptr) +@ stdcall InitPropVariantFromFileTime(ptr ptr) +@ stdcall InitPropVariantFromFileTimeVector(ptr long ptr) @ stdcall InitPropVariantFromGUIDAsString(ptr ptr) -@ stub InitPropVariantFromInt16Vector -@ stub InitPropVariantFromInt32Vector -@ stub InitPropVariantFromInt64Vector +@ stdcall InitPropVariantFromInt16Vector(ptr long ptr) +@ stdcall InitPropVariantFromInt32Vector(ptr long ptr) +@ stdcall InitPropVariantFromInt64Vector(ptr long ptr) @ stub InitPropVariantFromPropVariantVectorElem @ stub InitPropVariantFromResource @ stub InitPropVariantFromStrRet @ stub InitPropVariantFromStringAsVector -@ stub InitPropVariantFromStringVector -@ stub InitPropVariantFromUInt16Vector -@ stub InitPropVariantFromUInt32Vector -@ stub InitPropVariantFromUInt64Vector +@ stdcall InitPropVariantFromStringVector(ptr long ptr) +@ stdcall InitPropVariantFromUInt16Vector(ptr long ptr) +@ stdcall InitPropVariantFromUInt32Vector(ptr long ptr) +@ stdcall InitPropVariantFromUInt64Vector(ptr long ptr) @ stub InitPropVariantVectorFromPropVariant @ stub InitVariantFromBooleanArray @ stdcall InitVariantFromBuffer(ptr long ptr) @@ -151,8 +151,8 @@ @ stub PropVariantToUInt64VectorAlloc @ stub PropVariantToUInt64WithDefault @ stub PropVariantToVariant -@ stub StgDeserializePropVariant -@ stub StgSerializePropVariant +@ stdcall StgDeserializePropVariant(ptr long ptr) +@ stdcall StgSerializePropVariant(ptr ptr ptr) @ stub VariantCompare @ stub VariantGetBooleanElem @ stub VariantGetDoubleElem diff --git a/dlls/propsys/propvar.c b/dlls/propsys/propvar.c index fd22c9d..e7ba3f3 100644 --- a/dlls/propsys/propvar.c +++ b/dlls/propsys/propvar.c @@ -30,6 +30,8 @@ #include "winuser.h" #include "shlobj.h" #include "propvarutil.h" +#include "mimeole.h" +#include "oleauto.h"
#include "wine/debug.h" #include "wine/unicode.h" @@ -346,6 +348,20 @@ HRESULT WINAPI InitVariantFromGUIDAsString(REFGUID guid, VARIANT *pvar) return S_OK; }
+HRESULT WINAPI InitPropVariantFromCLSID(REFCLSID clsid, PROPVARIANT *ppropvar) +{ + TRACE("(%p %p)\n", clsid, ppropvar); + + ppropvar->u.puuid = CoTaskMemAlloc(sizeof(CLSID)); + if (!ppropvar->u.puuid) + return E_OUTOFMEMORY; + + *ppropvar->u.puuid = *clsid; + ppropvar->vt = VT_CLSID; + + return S_OK; +} + HRESULT WINAPI InitPropVariantFromBuffer(const VOID *pv, UINT cb, PROPVARIANT *ppropvar) { TRACE("(%p %u %p)\n", pv, cb, ppropvar); @@ -391,6 +407,95 @@ HRESULT WINAPI InitVariantFromBuffer(const VOID *pv, UINT cb, VARIANT *pvar) return S_OK; }
+#define DEFINE_INIT_FROM_VECTOR(ctype, functype, vtype, usuffix) \ + HRESULT WINAPI InitPropVariantFrom##functype##Vector(const ctype *pval, ULONG cEl, PROPVARIANT *ppropvar) \ + { \ + TRACE("(%p %u %p)\n", pval, cEl, ppropvar); \ + \ + ppropvar->u.ca##usuffix.pElems = CoTaskMemAlloc(cEl * sizeof(ctype)); \ + if(!ppropvar->u.ca##usuffix.pElems) \ + return E_OUTOFMEMORY; \ + \ + ppropvar->vt = VT_VECTOR|VT_##vtype; \ + ppropvar->u.ca##usuffix.cElems = cEl; \ + memcpy(ppropvar->u.ca##usuffix.pElems, pval, cEl * sizeof(ctype)); \ + return S_OK; \ + } + +DEFINE_INIT_FROM_VECTOR(SHORT, Int16, I2, i) +DEFINE_INIT_FROM_VECTOR(USHORT, UInt16, UI2, ui) +DEFINE_INIT_FROM_VECTOR(LONG, Int32, I4, l) +DEFINE_INIT_FROM_VECTOR(ULONG, UInt32, UI4, ul) +DEFINE_INIT_FROM_VECTOR(LONGLONG, Int64, I8, h) +DEFINE_INIT_FROM_VECTOR(ULONGLONG, UInt64, UI8, uh) +DEFINE_INIT_FROM_VECTOR(double, Double, R8, dbl) +DEFINE_INIT_FROM_VECTOR(FILETIME, FileTime, FILETIME, filetime) + +#undef DEFINE_INIT_FROM_VECTOR + +HRESULT WINAPI InitPropVariantFromBooleanVector(const BOOL *pbool, ULONG cEl, PROPVARIANT *ppropvar) +{ + ULONG i; + TRACE("(%p %u %p)\n", pbool, cEl, ppropvar); + + ppropvar->u.cabool.pElems = CoTaskMemAlloc(cEl * sizeof(VARIANT_BOOL)); + if (!ppropvar->u.cabool.pElems) + return E_OUTOFMEMORY; + + ppropvar->vt = VT_VECTOR|VT_BOOL; + ppropvar->u.cabool.cElems = cEl; + + for (i = 0; i < cEl; ++i) + { + ppropvar->u.cabool.pElems[i] = pbool[i] ? VARIANT_TRUE : VARIANT_FALSE; + } + + return S_OK; +} + +HRESULT WINAPI InitPropVariantFromFileTime(const FILETIME *pft, PROPVARIANT *pprop) +{ + TRACE("(%p %p)\n", pft, pprop); + + pprop->vt = VT_FILETIME; + pprop->u.filetime = *pft; + + return S_OK; +} + +HRESULT WINAPI InitPropVariantFromStringVector(const WCHAR **pstr, ULONG c, PROPVARIANT *pprop) +{ + ULONG i; + TRACE("(%p %u %p)\n", pstr, c, pprop); + + pprop->u.calpwstr.pElems = CoTaskMemAlloc(c * sizeof(WCHAR *)); + if (!pprop->u.calpwstr.pElems) + return E_OUTOFMEMORY; + + ZeroMemory(pprop->u.calpwstr.pElems, c * sizeof(WCHAR *)); + + pprop->vt = VT_VECTOR|VT_LPWSTR; + pprop->u.calpwstr.cElems = c; + + for (i = 0; i < c; ++i) + { + ULONG len; + + len = lstrlenW(pstr[i]) + 1; + + pprop->u.calpwstr.pElems[i] = CoTaskMemAlloc(len * sizeof(WCHAR)); + if (!pprop->u.calpwstr.pElems[i]) + { + PropVariantClear(pprop); /* release already allocated memory */ + return E_OUTOFMEMORY; + } + + memcpy(pprop->u.calpwstr.pElems[i], pstr[i], len * sizeof(WCHAR)); + } + + return S_OK; +} + static inline DWORD PROPVAR_HexToNum(const WCHAR *hex) { DWORD ret; @@ -511,7 +616,17 @@ static BOOL isemptyornull(const PROPVARIANT *propvar) } return i == propvar->u.parray->cDims; } - /* FIXME: vectors, byrefs, errors? */ + if ((propvar->vt & VT_VECTOR) == VT_VECTOR) + { + /* all vector structs are equal */ + return !propvar->u.cac.cElems || !propvar->u.cac.pElems; + } + if ((propvar->vt & VT_BYREF) == VT_BYREF) + { + /* all pointers are equal */ + return !propvar->u.punkVal; + } + /* FIXME: errors? */ return FALSE; }
@@ -555,36 +670,321 @@ INT WINAPI PropVariantCompareEx(REFPROPVARIANT propvar1, REFPROPVARIANT propvar2 else \ res = 0; \ } while (0) +#define CMP_INT_VECTOR(var) do { \ + if (propvar1->u.var.cElems > propvar2_converted->u.var.cElems) \ + { res = 1; } \ + else if (propvar1->u.var.cElems < propvar2_converted->u.var.cElems) \ + { res = -1; } \ + else \ + { \ + ULONG i; \ + res = 0; \ + for (i = 0; i < propvar1->u.var.cElems; ++i) \ + { \ + if (propvar1->u.var.pElems[i] > propvar2_converted->u.var.pElems[i]) \ + { \ + res = 1; \ + break; \ + } \ + else if (propvar1->u.var.pElems[i] < propvar2_converted->u.var.pElems[i]) \ + { \ + res = -1; \ + break; \ + } \ + } \ + } \ + } while(0) + +#define INT_CASE(vtype, svar, vecvar) \ + case vtype: \ + CMP_INT_VALUE(svar); \ + break; \ + case VT_VECTOR|vtype: \ + CMP_INT_VECTOR(vecvar); \ + break; \
switch (propvar1->vt) { - case VT_I1: - CMP_INT_VALUE(cVal); - break; - case VT_UI1: - CMP_INT_VALUE(bVal); - break; - case VT_I2: - CMP_INT_VALUE(iVal); - break; - case VT_UI2: - CMP_INT_VALUE(uiVal); - break; - case VT_I4: + INT_CASE(VT_I1, cVal, cac) + INT_CASE(VT_UI1, bVal, caub) + INT_CASE(VT_I2, iVal, cai) + INT_CASE(VT_UI2, uiVal, caui) + INT_CASE(VT_I4, lVal, cal) + INT_CASE(VT_UI4, ulVal, caul) + INT_CASE(VT_BOOL, boolVal, cabool) + INT_CASE(VT_R4, fltVal, caflt) + INT_CASE(VT_R8, dblVal, cadbl) + INT_CASE(VT_DATE, date, cadate) /* DATE == double */ + INT_CASE(VT_ERROR, scode, cascode) /* == ULONG */ + case VT_INT: /* does not appear as vector */ CMP_INT_VALUE(lVal); break; - case VT_UI4: - CMP_INT_VALUE(uiVal); + case VT_UINT: /* does not appear as vector */ + CMP_INT_VALUE(ulVal); break; case VT_I8: CMP_INT_VALUE(hVal.QuadPart); break; + case VT_VECTOR|VT_I8: + if (propvar1->u.cah.cElems > propvar2_converted->u.cah.cElems) + res = 1; + else if (propvar1->u.cah.cElems < propvar2_converted->u.cah.cElems) + res = -1; + else + { + ULONG i; + res = 0; + for (i = 0; i < propvar1->u.cah.cElems; ++i) + { + if (propvar1->u.cah.pElems[i].QuadPart > propvar2_converted->u.cah.pElems[i].QuadPart) + { + res = 1; + break; + } + else if (propvar1->u.cah.pElems[i].QuadPart < propvar2_converted->u.cah.pElems[i].QuadPart) + { + res = -1; + break; + } + } + } + break; case VT_UI8: CMP_INT_VALUE(uhVal.QuadPart); break; - case VT_BSTR: + case VT_VECTOR|VT_UI8: + if (propvar1->u.cauh.cElems > propvar2_converted->u.cauh.cElems) + res = 1; + else if (propvar1->u.cauh.cElems < propvar2_converted->u.cauh.cElems) + res = -1; + else + { + ULONG i; + res = 0; + for (i = 0; i < propvar1->u.cauh.cElems; ++i) + { + if (propvar1->u.cauh.pElems[i].QuadPart > propvar2_converted->u.cauh.pElems[i].QuadPart) + { + res = 1; + break; + } + else if (propvar1->u.cauh.pElems[i].QuadPart < propvar2_converted->u.cauh.pElems[i].QuadPart) + { + res = -1; + break; + } + } + } + break; + case VT_FILETIME: + { + CMP_INT_VALUE(filetime.dwHighDateTime); + if (res == 0) + CMP_INT_VALUE(filetime.dwLowDateTime); + + break; + } + case VT_VECTOR|VT_FILETIME: + { + ULONG i; + + CMP_INT_VALUE(cafiletime.cElems); + if (res) + break; + + for (i = 0; i < propvar1->u.cafiletime.cElems; ++i) + { + CMP_INT_VALUE(cafiletime.pElems[i].dwHighDateTime); + if (res) + break; + + CMP_INT_VALUE(cafiletime.pElems[i].dwLowDateTime); + if (res) + break; + } + break; + } + case VT_LPSTR: + res = lstrcmpA(propvar1->u.pszVal, propvar2_converted->u.pszVal); + break; + case VT_VECTOR|VT_LPSTR: + { + ULONG i; + + CMP_INT_VALUE(calpstr.cElems); + if (res) + break; + + for (i = 0; i < propvar1->u.calpstr.cElems; ++i) + { + /* FIXME: Use string flags. */ + res = lstrcmpA(propvar1->u.calpstr.pElems[i], propvar2_converted->u.calpstr.pElems[i]); + + if (res) + break; + } + break; + } + case VT_BSTR: /* BSTR and LPWSTR are NOT EQUAL in general, but here */ + case VT_LPWSTR: /* FIXME: Use string flags. */ - res = lstrcmpW(propvar1->u.bstrVal, propvar2->u.bstrVal); + res = lstrcmpW(propvar1->u.bstrVal, propvar2_converted->u.bstrVal); + break; + case VT_VECTOR|VT_BSTR: + case VT_VECTOR|VT_LPWSTR: + { + ULONG i; + + CMP_INT_VALUE(calpwstr.cElems); + if (res) + break; + + for (i = 0; i < propvar1->u.calpwstr.cElems; ++i) + { + /* FIXME: Use string flags. */ + res = lstrcmpW(propvar1->u.calpwstr.pElems[i], propvar2_converted->u.calpwstr.pElems[i]); + + if (res) + break; + } + break; + } + case VT_CLSID: + /* IsEqualUUID only compares for equality :( */ + CMP_INT_VALUE(puuid->Data1); + if (res) + break; + + CMP_INT_VALUE(puuid->Data2); + if (res) + break; + + CMP_INT_VALUE(puuid->Data3); + if (res) + break; + + res = memcmp(propvar1->u.puuid->Data4, propvar2_converted->u.puuid->Data4, 8); + break; + case VT_VECTOR|VT_CLSID: + { + ULONG i; + + CMP_INT_VALUE(cauuid.cElems); + if (res) + break; + + for (i = 0; i < propvar1->u.cauuid.cElems; ++i) + { + CMP_INT_VALUE(cauuid.pElems[i].Data1); + if (res) + break; + + CMP_INT_VALUE(cauuid.pElems[i].Data2); + if (res) + break; + + CMP_INT_VALUE(cauuid.pElems[i].Data3); + if (res) + break; + + res = memcmp(propvar1->u.cauuid.pElems[i].Data4, + propvar2_converted->u.cauuid.pElems[i].Data4, + 8); + + if (res) + break; + } + break; + } + case VT_CF: + CMP_INT_VALUE(pclipdata->cbSize); + if (res) + break; + + CMP_INT_VALUE(pclipdata->ulClipFmt); + if (res) + break; + + res = memcmp(propvar1->u.pclipdata->pClipData, + propvar2_converted->u.pclipdata->pClipData, + propvar1->u.pclipdata->cbSize - 4); + break; + case VT_VECTOR|VT_CF: + { + ULONG i; + + CMP_INT_VALUE(caclipdata.cElems); + if (res) + break; + + for (i = 0; i < propvar1->u.caclipdata.cElems; ++i) + { + CMP_INT_VALUE(caclipdata.pElems[i].cbSize); + if (res) + break; + + CMP_INT_VALUE(caclipdata.pElems[i].ulClipFmt); + if (res) + break; + + res = memcmp(propvar1->u.caclipdata.pElems[i].pClipData, + propvar2_converted->u.caclipdata.pElems[i].pClipData, + propvar1->u.caclipdata.pElems[i].cbSize - 4); + + if (res) + break; + } + break; + } + case VT_VECTOR|VT_VARIANT: + { + ULONG i; + + CMP_INT_VALUE(capropvar.cElems); + if (res) + break; + + for (i = 0; i < propvar1->u.capropvar.cElems; ++i) + { + res = PropVariantCompareEx(&propvar1->u.capropvar.pElems[i], + &propvar2_converted->u.capropvar.pElems[i], + unit, flags); + + if (res) + break; + } + break; + } + case VT_DECIMAL: + { + HRESULT hr; + DECIMAL dec1 = *(DECIMAL*)propvar1; + DECIMAL dec2 = *(DECIMAL*)propvar2_converted; + dec1.wReserved = 0; + dec2.wReserved = 0; + + hr = VarDecCmp(&dec1, &dec2); + if (hr == VARCMP_LT) + res = -1; + else if (hr == VARCMP_GT) + res = 1; + else if (hr == VARCMP_EQ) + res = 0; + else + { + WARN("Comparing DECIMALS: hr=%08x\n", (unsigned)hr); + res = -1; + } + break; + } + case VT_BLOB: + CMP_INT_VALUE(blob.cbSize); + if (res) + break; + + res = memcmp(propvar1->u.blob.pBlobData, + propvar2_converted->u.blob.pBlobData, + propvar1->u.blob.cbSize); break; default: FIXME("vartype %d not handled\n", propvar1->vt); @@ -597,3 +997,69 @@ INT WINAPI PropVariantCompareEx(REFPROPVARIANT propvar1, REFPROPVARIANT propvar2
return res; } + +HRESULT WINAPI StgDeserializePropVariant(const SERIALIZEDPROPERTYVALUE *pprop, + ULONG cbMax, + PROPVARIANT *pvar) +{ + HRESULT hr; + + TRACE("(%p, %lu, %p)\n", pprop, (unsigned long)cbMax, pvar); + + if (!pprop || !pvar) + return E_INVALIDARG; + + hr = wine_ReadProperty(pvar, (BYTE*)pprop, cbMax, NULL, CP_UNICODE, CP_ACP, NULL, NULL); + if (FAILED(hr)) + { + PropVariantClear(pvar); + return hr; + } + + return S_OK; +} + +HRESULT WINAPI StgSerializePropVariant(const PROPVARIANT *pVar, + SERIALIZEDPROPERTYVALUE **ppProp, + ULONG *pcb) +{ + HRESULT hr; + ULONG space; + ULONG space_used; + BYTE *buffer = NULL; + + TRACE("(%p, %p, %p)\n", pVar, ppProp, pcb); + + if (!ppProp || !pcb) + return E_INVALIDARG; + + *ppProp = NULL; + *pcb = 0; + + hr = wine_SerializedPropertySize(pVar, &space, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + return hr; + + buffer = CoTaskMemAlloc(space); + if (!buffer) + return E_OUTOFMEMORY; + + hr = wine_WritePropertyToBuffer(pVar, buffer, space, &space_used, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + { + WARN("Failed to serialize property of type %d: hr=%08x\n", pVar->vt, hr); + CoTaskMemFree(buffer); + + return hr; + } + + if (space - space_used) + { + WARN("Buffer size calculation was off by %u bytes\n", space - space_used); + } + + *ppProp = (SERIALIZEDPROPERTYVALUE*)buffer; + *pcb = space_used; + + return S_OK; +} diff --git a/dlls/propsys/tests/propsys.c b/dlls/propsys/tests/propsys.c index 99d1c46..311d610 100644 --- a/dlls/propsys/tests/propsys.c +++ b/dlls/propsys/tests/propsys.c @@ -866,6 +866,382 @@ static void test_intconversions(void) ok(llval == -7, "got wrong value %s\n", debugstr_longlong(llval)); }
+static char *buffer_printable(void *buffer, size_t buffer_size) +{ + char *heap_buffer, *p; + unsigned char *source_buffer; + + p = heap_buffer = HeapAlloc(GetProcessHeap(), 0, buffer_size * 4 + 5); + if (!heap_buffer) + return NULL; + + source_buffer = buffer; + *p++ = '"'; + + for (; buffer_size; --buffer_size) + { + if ((*source_buffer >= '0' && *source_buffer <= '9') + || (*source_buffer >= 'a' && *source_buffer <= 'z') + || (*source_buffer >= 'A' && *source_buffer <= 'Z') + || *source_buffer == ' ' || *source_buffer == ',') + { + *p++ = (char)*source_buffer++; + } + else + { + sprintf(p, "\%03o", (unsigned)*source_buffer++); + p += 4; + } + } + + *p++ = '"'; + *p++ = '\0'; + + return heap_buffer; +} + +static void ok_compare(const char *type, + BYTE *expected_buffer, + size_t expected_buffer_size, + BYTE *got_buffer, + size_t got_buffer_size) +{ + int same = got_buffer_size == expected_buffer_size && + !memcmp(expected_buffer, got_buffer, got_buffer_size); + + char *got = buffer_printable(got_buffer, got_buffer_size); + char *expected = buffer_printable(expected_buffer, expected_buffer_size); + + ok(same, "Comparing serialized %s: Expected %s:%lu but got %s:%lu\n", type, expected, (unsigned long)expected_buffer_size, got, (unsigned long)got_buffer_size); + + HeapFree(GetProcessHeap(), 0, got); + HeapFree(GetProcessHeap(), 0, expected); +} + +static void check_serialize_func(const char *type, const PROPVARIANT *prop, BYTE *expected, size_t expected_size) +{ + PROPVARIANT out; + HRESULT hr; + INT cmp; + SERIALIZEDPROPERTYVALUE *serialized = NULL; + ULONG serialized_size = 0; + + PropVariantInit(&out); + + hr = StgSerializePropVariant(prop, &serialized, &serialized_size); + ok(hr == S_OK, "Serializing %s: %08x\n", type, (unsigned)hr); + + ok_compare(type, expected, expected_size, (BYTE*)serialized, (size_t)serialized_size); + + hr = StgDeserializePropVariant(serialized, serialized_size, &out); + /* WTF: Win8 chokes on VT_DECIMAL, won't deserialize it but whatever it + deserializes compares equal to the original value */ + ok(hr == S_OK || broken(hr == E_FAIL && prop->vt == VT_DECIMAL), "Deserializing %s: %08x\n", type, (unsigned)hr); + + cmp = PropVariantCompare(prop, &out); + ok(cmp == 0, "Deserialized %s is different from original value!\n", type); + + PropVariantClear(&out); + CoTaskMemFree(serialized); +} + +#define CHECK_SERIALIZE(type, prop, buffer_str_literal) \ + do { \ + unsigned char _str[] = buffer_str_literal; \ + check_serialize_func(type, prop, _str, sizeof(_str) - 1); \ + } while (0) + +#define DO_INT_TEST(ctype, functype, single_buffer_str, vector_buffer_str, values...) \ + do { \ + PROPVARIANT v; \ + HRESULT hr; \ + ctype vbuffer[] = { values }; \ + \ + /* first try a single value */ \ + PropVariantInit(&v); \ + hr = InitPropVariantFrom##functype(vbuffer[0], &v); \ + ok(hr == S_OK, "Initializing PROPVARIANT from " #ctype ": %08x\n", (unsigned)hr); \ + \ + CHECK_SERIALIZE(#ctype, &v, single_buffer_str); \ + \ + PropVariantClear(&v); \ + \ + /* then try a vector */ \ + PropVariantInit(&v); \ + hr = InitPropVariantFrom##functype##Vector(vbuffer, sizeof(vbuffer)/sizeof(vbuffer[0]), &v); \ + ok(hr == S_OK, "Initializing PROPVARIANT from " #ctype " Vector: %08x\n", (unsigned)hr); \ + \ + CHECK_SERIALIZE(#ctype " Vector", &v, vector_buffer_str); \ + \ + PropVariantClear(&v); \ + } while(0) + +/* FIXME: In wine, this really tests wine_ReadProperty/wine_WritePropertyToBuffer + * in OLE32.dll, but propsys.dll provides the only non-braindamaged interface to + * property (de)serialization. Therefore, we test it here. + */ +static void test_serialization(void) +{ + HRESULT hr; + + DO_INT_TEST(SHORT, Int16, "\002\000\000\000\040\000\000\000", "\002\020\000\000\003\000\000\000\040\000\167\362\052\000\000\000", 32, -3465, 42); + DO_INT_TEST(USHORT, UInt16, "\022\000\000\000\040\000\000\000", "\022\020\000\000\003\000\000\000\040\000\376\377\052\000\000\000", 32, 65534, 42); + DO_INT_TEST(LONG, Int32, "\003\000\000\000\040\000\000\000", "\003\020\000\000\003\000\000\000\040\000\000\000\066\113\005\000\312\366\377\377", 32, 346934, -2358); + DO_INT_TEST(ULONG, UInt32, "\023\000\000\000\357\276\255\336", "\023\020\000\000\005\000\000\000\357\276\255\336\276\272\376\312\276\272\255\336\376\312\357\276\357\276\276\272", 0xDEADBEEF, 0xCAFEBABE, 0xDEADBABE, 0xBEEFCAFE, 0xBABEBEEF); + DO_INT_TEST(LONGLONG, Int64, "\024\000\000\000\065\065\043\316\114\000\000\000", "\024\020\000\000\003\000\000\000\065\065\043\316\114\000\000\000\341\030\011\376\377\377\377\377\343\226\003\000\000\000\000\000", 329875928373, -32958239, 235235); + DO_INT_TEST(ULONGLONG, UInt64, "\025\000\000\000\276\272\357\276\376\312\355\015", "\025\020\000\000\001\000\000\000\276\272\357\276\376\312\355\015", 0xDEDCAFEBEEFBABE); + DO_INT_TEST(BOOL, Boolean, "\013\000\000\000\377\377\000\000", "\013\020\000\000\005\000\000\000\377\377\000\000\377\377\000\000\000\000\000\000", TRUE, FALSE, TRUE, FALSE, FALSE); + + /* FileTime */ + { + PROPVARIANT vSingle; + PROPVARIANT vVector; + FILETIME ftVec[] = { { 0xDEADBEEF, 0xCAFEBABE }, { 0xDEADBABE, 0xCAFEBEEF }, { 239508, 2484 } }; + + PropVariantInit(&vSingle); PropVariantInit(&vVector); + + hr = InitPropVariantFromFileTime(&ftVec[0], &vSingle); + ok(hr == S_OK, "InitPropVariantFromFileTime: %08x\n", (unsigned)hr); + + hr = InitPropVariantFromFileTimeVector(ftVec, sizeof(ftVec)/sizeof(ftVec[0]), &vVector); + ok(hr == S_OK, "InitPropVariantFromFileTimeVector: %08x\n", (unsigned)hr); + + CHECK_SERIALIZE("FILETIME", &vSingle, "\100\000\000\000\357\276\255\336\276\272\376\312"); + CHECK_SERIALIZE("FILETIME Vector", &vVector, "\100\020\000\000\003\000\000\000\357\276\255\336\276\272\376\312\276\272\255\336\357\276\376\312\224\247\003\000\264\011\000\000"); + + PropVariantClear(&vSingle); PropVariantClear(&vVector); + } + + /* Int8 / Uint8 */ + { + PROPVARIANT vc; + PROPVARIANT vac; + PROPVARIANT vb; + PROPVARIANT vab; + + char cVec[] = "Hello World, How are You?"; + BYTE bVec[] = { 0xDE, 0xAD, 0xCA, 0xFE, 0xBA }; + + vc.vt = VT_I1; + vc.u.cVal = cVec[0]; + + vac.vt = VT_VECTOR|VT_I1; + vac.u.cac.cElems = sizeof(cVec)/sizeof(cVec[0]); + vac.u.cac.pElems = cVec; + + vb.vt = VT_UI1; + vb.u.bVal = bVec[0]; + + vab.vt = VT_VECTOR|VT_UI1; + vab.u.caub.cElems = sizeof(bVec)/sizeof(bVec[0]); + vab.u.caub.pElems = bVec; + + CHECK_SERIALIZE("char", &vc, "\020\000\000\000\110\000\000\000"); + CHECK_SERIALIZE("char vector", &vac, "\020\020\000\000\032\000\000\000\110\145\154\154\157\040\127\157\162\154\144\054\040\110\157\167\040\141\162\145\040\131\157\165\077\000\000\000"); + CHECK_SERIALIZE("byte", &vb, "\021\000\000\000\336\000\000\000"); + CHECK_SERIALIZE("byte vector", &vab, "\021\020\000\000\005\000\000\000\336\255\312\376\272\000\000\000"); + } + + /* Float */ + { + PROPVARIANT vSingle; + PROPVARIANT vVector; + + FLOAT fVec[] = { 3.14156778354f, 0.239852935758f, 128471284.354f, -523525.236f }; + + PropVariantInit(&vSingle); PropVariantInit(&vVector); + + vSingle.vt = VT_R4; + vSingle.u.fltVal = fVec[0]; + vVector.vt = VT_VECTOR|VT_R4; + vVector.u.caflt.cElems = sizeof(fVec)/sizeof(fVec[0]); + vVector.u.caflt.pElems = fVec; + + CHECK_SERIALIZE("float", &vSingle, "\004\000\000\000\162\017\111\100"); + CHECK_SERIALIZE("float vector", &vVector, "\004\020\000\000\004\000\000\000\162\017\111\100\002\234\165\076\037\012\365\114\250\240\377\310"); + } + + /* LPSTR */ + /* The serialization routine converts these to UTF-16 and back to CP_ACP + * on return */ + { + PROPVARIANT vSingle; + PROPVARIANT vVector; + + char str1[] = "HelloWorld"; + char str2[] = "aBc"; + char str3[] = "Blub"; + char *arr[] = { str1, str2, str3 }; + + vSingle.vt = VT_LPSTR; + vSingle.u.pszVal = str1; + + vVector.vt = VT_VECTOR|VT_LPSTR; + vVector.u.calpstr.cElems = sizeof(arr)/sizeof(arr[0]); + vVector.u.calpstr.pElems = arr; + + CHECK_SERIALIZE("lpstr", &vSingle, "\036\000\000\000\026\000\000\000H\000e\000l\000l\000o\000W\000o\000r\000l\000d\000\000\000\000\000"); + CHECK_SERIALIZE("lpstr vector", &vVector, "\036\020\000\000\003\000\000\000\026\000\000\000H\000e\000l\000l\000o\000W\000o\000r\000l\000d\000\000\000\000\000\010\000\000\000a\000B\000c\000\000\000\012\000\000\000B\000l\000u\000b\000\000\000\000\000"); + } + + /* LPWSTR */ + { + PROPVARIANT vSingle; + PROPVARIANT vVector; + + WCHAR str1[] = { 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', 0 }; + WCHAR str2[] = { 'a', 'B', 'c', 0 }; + WCHAR str3[] = { 'B', 'l', 'u', 'b', 0 }; + /* disclaimer: I don't speak chinese, I just want to test some Unicode characters */ + WCHAR str4[] = { 0x4E40, 0x4FB0, 0x4F47, 0x4EB9, 0 }; + const WCHAR *arr[] = { str1, str2, str3, str4 }; + + PropVariantInit(&vSingle); PropVariantInit(&vVector); + + hr = InitPropVariantFromString(str1, &vSingle); + ok(hr == S_OK, "InitPropVariantFromString: %08x\n", (unsigned)hr); + + hr = InitPropVariantFromStringVector(arr, sizeof(arr)/sizeof(arr[0]), &vVector); + + CHECK_SERIALIZE("lpwstr", &vSingle, "\037\000\000\000\013\000\000\000\110\000\145\000\154\000\154\000\157\000\127\000\157\000\162\000\154\000\144\000\000\000\000\000"); + CHECK_SERIALIZE("lpwstr vector", &vVector, "\037\020\000\000\004\000\000\000\013\000\000\000\110\000\145\000\154\000\154\000\157\000\127\000\157\000\162\000\154\000\144\000\000\000\000\000\004\000\000\000\141\000\102\000\143\000\000\000\005\000\000\000\102\000\154\000\165\000\142\000\000\000\000\000\005\000\000\000\100\116\260\117\107\117\271\116\000\000\000\000"); + + PropVariantClear(&vSingle); + PropVariantClear(&vVector); + } + + /* BSTR */ + { + PROPVARIANT vSingle; + PROPVARIANT vVector; + + WCHAR str1[] = { 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', 0 }; + WCHAR str2[] = { 'a', 'B', 'c', 0 }; + WCHAR str3[] = { 'B', 'l', 'u', 'b', 0 }; + /* disclaimer: I don't speak chinese, I just want to test some Unicode characters */ + WCHAR str4[] = { 0x4E40, 0x4FB0, 0x4F47, 0x4EB9, 0 }; + + BSTR arr[4]; + + arr[0] = SysAllocString(str1); /*FIXME: handle NULL*/ + arr[1] = SysAllocString(str2); + arr[2] = SysAllocString(str3); + arr[3] = SysAllocString(str4); + + vSingle.vt = VT_BSTR; + vSingle.u.bstrVal = arr[0]; + vVector.vt = VT_VECTOR|VT_BSTR; + vVector.u.cabstr.cElems = sizeof(arr)/sizeof(arr[0]); + vVector.u.cabstr.pElems = arr; + + CHECK_SERIALIZE("BSTR", &vSingle, "\010\000\000\000\026\000\000\000H\000e\000l\000l\000o\000W\000o\000r\000l\000d\000\000\000\000\000"); + CHECK_SERIALIZE("BSTR vector", &vVector, "\010\020\000\000\004\000\000\000\026\000\000\000H\000e\000l\000l\000o\000W\000o\000r\000l\000d\000\000\000\000\000\010\000\000\000a\000B\000c\000\000\000\012\000\000\000B\000l\000u\000b\000\000\000\000\000\012\000\000\000\100N\260OGO\271N\000\000\000\000"); + + SysFreeString(arr[0]); + SysFreeString(arr[1]); + SysFreeString(arr[2]); + SysFreeString(arr[3]); + } + + /* Clipdata */ + { + PROPVARIANT vSingle; + PROPVARIANT vVector; + + char data1[] = "Hello World, how are you today?"; + char data2[] = "Boo"; + + CLIPDATA cfArr[] = { + { + sizeof(data1) + 4, + 42, + (BYTE*)data1 + }, { + sizeof(data2) + 4, + 0xBABE, + (BYTE*)data2 + } + + }; + + vSingle.vt = VT_CF; + vSingle.u.pclipdata = &cfArr[0]; + + vVector.vt = VT_VECTOR|VT_CF; + vVector.u.caclipdata.cElems = sizeof(cfArr)/sizeof(cfArr[0]); + vVector.u.caclipdata.pElems = cfArr; + + CHECK_SERIALIZE("CF", &vSingle, "G\000\000\000\044\000\000\000\052\000\000\000Hello World, how are you today\077\000"); + CHECK_SERIALIZE("CF vector", &vVector, "G\020\000\000\002\000\000\000\044\000\000\000\052\000\000\000Hello World, how are you today\077\000\010\000\000\000\276\272\000\000Boo\000"); + } + + /* CLSID */ + { + PROPVARIANT vSingle; + PROPVARIANT vVector; + + CLSID arr[] = { + { 0x557cf406, 0x1a04, 0x11d3, { 0x9a,0x73,0x00,0x00,0xf8,0x1e,0xf3,0x2e } }, + { 0xDEADBEEF, 0xCAFE, 0xBABE, { 0xDE,0xAD,0xBA,0xBE,0xCA,0xFE,0xBE,0xEF } } + }; + + InitPropVariantFromCLSID(&arr[0], &vSingle); + + vVector.vt = VT_VECTOR|VT_CLSID; + vVector.u.cauuid.cElems = sizeof(arr)/sizeof(arr[0]); + vVector.u.cauuid.pElems = arr; + + CHECK_SERIALIZE("CLSID", &vSingle, "H\000\000\000\006\364\174U\004\032\323\021\232s\000\000\370\036\363\056"); + CHECK_SERIALIZE("CLSID", &vVector, "H\020\000\000\002\000\000\000\006\364\174U\004\032\323\021\232s\000\000\370\036\363\056\357\276\255\336\376\312\276\272\336\255\272\276\312\376\276\357"); + + PropVariantClear(&vSingle); + } + + /* DECIMAL */ + { + PROPVARIANT v; + DECIMAL *asDecimal = (DECIMAL*)&v; + + PropVariantInit(&v); + asDecimal->u.sign = DECIMAL_NEG; + asDecimal->u.scale = 5; + asDecimal->Hi32 = 0xCAFEBABE; + asDecimal->u1.Lo64 = 0xDEADBABECAFEBEEF; + v.vt = VT_DECIMAL; + + + CHECK_SERIALIZE("DECIMAL", &v, "\016\000\000\000\000\000\005\200\276\272\376\312\357\276\376\312\276\272\255\336"); + } + + /* VARIANT */ + /* Unfortunately, we may only apply one level of variants :( */ + { + PROPVARIANT vVec[2]; + PROPVARIANT v; + + InitPropVariantFromUInt32(0xDEADBABE, &vVec[0]); + InitPropVariantFromInt16(1342, &vVec[1]); + + v.vt = VT_VECTOR|VT_VARIANT; + v.u.capropvar.cElems = sizeof(vVec)/sizeof(vVec[0]); + v.u.capropvar.pElems = vVec; + + CHECK_SERIALIZE("VARIANT vector", &v, "\014\020\000\000\002\000\000\000\023\000\000\000\276\272\255\336\002\000\000\000\076\005\000\000"); + } + + /* BLOB */ + { + PROPVARIANT vBlob; + char buffer[] = "Hello, World"; + + vBlob.vt = VT_BLOB; + vBlob.u.blob.cbSize = sizeof(buffer); + vBlob.u.blob.pBlobData = (BYTE*)buffer; + + CHECK_SERIALIZE("BLOB", &vBlob, "A\000\000\000\015\000\000\000Hello, World\000\000\000\000"); + } +} + START_TEST(propsys) { test_PSStringFromPropertyKey(); @@ -876,4 +1252,5 @@ START_TEST(propsys) test_PropVariantToGUID(); test_PropVariantCompare(); test_intconversions(); + test_serialization(); } diff --git a/include/propidl.idl b/include/propidl.idl index fbe80d8..c4acb42 100644 --- a/include/propidl.idl +++ b/include/propidl.idl @@ -85,6 +85,7 @@ interface IPropertyStorage : IUnknown type *pElems; \ } name
+ TYPEDEF_CA(char, CAC); TYPEDEF_CA(unsigned char, CAUB); TYPEDEF_CA(short, CAI); TYPEDEF_CA(USHORT, CAUI); @@ -137,10 +138,13 @@ interface IPropertyStorage : IUnknown [case(VT_CF)] CLIPDATA *pclipdata; [case(VT_STREAM, VT_STREAMED_OBJECT)] IStream *pStream; [case(VT_STORAGE, VT_STORED_OBJECT)] IStorage *pStorage; + [case(VT_DISPATCH)] IDispatch *pdispVal; + [case(VT_UNKNOWN)] IUnknown *punkVal; [case(VT_BSTR)] BSTR bstrVal; [case(VT_BSTR_BLOB)] BSTRBLOB bstrblobVal; [case(VT_LPSTR)] LPSTR pszVal; [case(VT_LPWSTR)] LPWSTR pwszVal; + [case(VT_I1|VT_VECTOR)] CAC cac; [case(VT_UI1|VT_VECTOR)] CAUB caub; [case(VT_I2|VT_VECTOR)] CAI cai; [case(VT_UI2|VT_VECTOR)] CAUI caui; @@ -439,6 +443,12 @@ typedef struct SERIALIZEDPROPERTYVALUE { cpp_quote("HRESULT WINAPI FreePropVariantArray(ULONG,PROPVARIANT*);") cpp_quote("HRESULT WINAPI PropVariantClear(PROPVARIANT*);") cpp_quote("HRESULT WINAPI PropVariantCopy(PROPVARIANT*,const PROPVARIANT*);") +cpp_quote("SERIALIZEDPROPERTYVALUE* WINAPI StgConvertVariantToProperty(const PROPVARIANT *pvar, USHORT CodePage, SERIALIZEDPROPERTYVALUE *pprop, ULONG *pcb, PROPID pid, BOOLEAN fReserved, ULONG *pcIndirect);") +cpp_quote("BOOLEAN WINAPI StgConvertPropertyToVariant(const SERIALIZEDPROPERTYVALUE* prop, USHORT CodePage, PROPVARIANT* pvar, void* pma);") +cpp_quote("") +cpp_quote("HRESULT WINAPI wine_ReadProperty(PROPVARIANT *prop, const BYTE *data, ULONG cbData, ULONG *pcbDataLeft, UINT diskCodePage, UINT runningCodePage, void* (WINAPI *allocate)(void *this, ULONG size), void *allocate_data);") +cpp_quote("HRESULT WINAPI wine_SerializedPropertySize(const PROPVARIANT *in_prop, ULONG *pcbSize, UINT diskCodePage, UINT runningCodePage);") +cpp_quote("HRESULT WINAPI wine_WritePropertyToBuffer(const PROPVARIANT *in_prop, BYTE *buffer, ULONG cbBufSize, ULONG *pcbBufSizeUsed, UINT diskCodePage, UINT runningCodePage);") cpp_quote("") cpp_quote("#define _PROPVARIANT_INIT_DEFINED_") cpp_quote("#define PropVariantInit(p) memset((p), 0, sizeof(PROPVARIANT))") diff --git a/include/propvarutil.h b/include/propvarutil.h index 4791543..a2ec2af 100644 --- a/include/propvarutil.h +++ b/include/propvarutil.h @@ -21,6 +21,7 @@
#include <shtypes.h> #include <shlwapi.h> +#include <propidl.h>
enum tagPROPVAR_CHANGE_FLAGS { @@ -63,6 +64,7 @@ HRESULT WINAPI PropVariantChangeType(PROPVARIANT *ppropvarDest, REFPROPVARIANT p PROPVAR_CHANGE_FLAGS flags, VARTYPE vt); HRESULT WINAPI InitPropVariantFromGUIDAsString(REFGUID guid, PROPVARIANT *ppropvar); HRESULT WINAPI InitVariantFromGUIDAsString(REFGUID guid, VARIANT *pvar); +HRESULT WINAPI InitPropVariantFromCLSID(REFCLSID clsid, PROPVARIANT *ppropvar); HRESULT WINAPI InitPropVariantFromBuffer(const VOID *pv, UINT cb, PROPVARIANT *ppropvar); HRESULT WINAPI InitVariantFromBuffer(const VOID *pv, UINT cb, VARIANT *pvar); HRESULT WINAPI PropVariantToGUID(const PROPVARIANT *ppropvar, GUID *guid); @@ -77,42 +79,117 @@ HRESULT WINAPI PropVariantToUInt16(REFPROPVARIANT propvarIn, USHORT *ret); HRESULT WINAPI PropVariantToUInt32(REFPROPVARIANT propvarIn, ULONG *ret); HRESULT WINAPI PropVariantToUInt64(REFPROPVARIANT propvarIn, ULONGLONG *ret);
-#ifdef __cplusplus - +#ifdef NO_PROPVAR_INLINES HRESULT InitPropVariantFromBoolean(BOOL fVal, PROPVARIANT *ppropvar); HRESULT InitPropVariantFromString(PCWSTR psz, PROPVARIANT *ppropvar); HRESULT InitPropVariantFromInt64(LONGLONG llVal, PROPVARIANT *ppropvar); +#endif
+HRESULT WINAPI InitPropVariantFromInt16Vector(const SHORT *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromUInt16Vector(const USHORT *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromInt32Vector(const LONG *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromUInt32Vector(const ULONG *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromInt64Vector(const LONGLONG *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromUInt64Vector(const ULONGLONG *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromBooleanVector(const BOOL *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromFileTime(const FILETIME *pft, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromFileTimeVector(const FILETIME *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromStringVector(const WCHAR **pstr, ULONG c, PROPVARIANT *pprop); + +HRESULT WINAPI StgSerializePropVariant(const PROPVARIANT *ppropvar, + SERIALIZEDPROPERTYVALUE **ppProp, + ULONG *pcb); +HRESULT WINAPI StgDeserializePropVariant(const SERIALIZEDPROPERTYVALUE *pprop, + ULONG cbMax, + PROPVARIANT *ppropvar); + + +/* FIXME: Make this available only if the compiler supports the inline keyword */ #ifndef NO_PROPVAR_INLINES
-inline HRESULT InitPropVariantFromBoolean(BOOL fVal, PROPVARIANT *ppropvar) +#ifdef NONAMELESSUNION +# define PROPVARIANT_U(x) (x).u +#else +# define PROPVARIANT_U(x) (x) +#endif + +static inline HRESULT InitPropVariantFromInt16(SHORT val, PROPVARIANT *ppropvar) +{ + ppropvar->vt = VT_I2; + PROPVARIANT_U(*ppropvar).iVal = val; + return S_OK; +} + +static inline HRESULT InitPropVariantFromUInt16(USHORT val, PROPVARIANT *ppropvar) +{ + ppropvar->vt = VT_UI2; + PROPVARIANT_U(*ppropvar).uiVal = val; + return S_OK; +} + +static inline HRESULT InitPropVariantFromInt32(LONG val, PROPVARIANT *ppropvar) +{ + ppropvar->vt = VT_I4; + PROPVARIANT_U(*ppropvar).lVal = val; + return S_OK; +} + +static inline HRESULT InitPropVariantFromUInt32(ULONG val, PROPVARIANT *ppropvar) +{ + ppropvar->vt = VT_UI4; + PROPVARIANT_U(*ppropvar).ulVal = val; + return S_OK; +} + +static inline HRESULT InitPropVariantFromBoolean(BOOL fVal, PROPVARIANT *ppropvar) { ppropvar->vt = VT_BOOL; - ppropvar->boolVal = fVal ? VARIANT_TRUE : VARIANT_FALSE; + PROPVARIANT_U(*ppropvar).boolVal = fVal ? VARIANT_TRUE : VARIANT_FALSE; return S_OK; }
-inline HRESULT InitPropVariantFromString(PCWSTR psz, PROPVARIANT *ppropvar) +static inline HRESULT InitPropVariantFromString(PCWSTR psz, PROPVARIANT *ppropvar) { - HRESULT hres; + ULONG len;
- hres = SHStrDupW(psz, &ppropvar->pwszVal); - if(SUCCEEDED(hres)) + len = lstrlenW(psz) + 1; + PROPVARIANT_U(*ppropvar).pwszVal = CoTaskMemAlloc(len * sizeof(WCHAR)); + if (PROPVARIANT_U(*ppropvar).pwszVal) + { ppropvar->vt = VT_LPWSTR; + memcpy(PROPVARIANT_U(*ppropvar).pwszVal, psz, len * sizeof(WCHAR)); + + return S_OK; + } else + { PropVariantInit(ppropvar); - - return hres; + return E_OUTOFMEMORY; + } }
-inline HRESULT InitPropVariantFromInt64(LONGLONG llVal, PROPVARIANT *ppropvar) +static inline HRESULT InitPropVariantFromInt64(LONGLONG llVal, PROPVARIANT *ppropvar) { ppropvar->vt = VT_I8; - ppropvar->hVal.QuadPart = llVal; + PROPVARIANT_U(*ppropvar).hVal.QuadPart = llVal; return S_OK; }
-#endif + +static inline HRESULT InitPropVariantFromUInt64(ULONGLONG val, PROPVARIANT *ppropvar) +{ + ppropvar->vt = VT_UI8; + PROPVARIANT_U(*ppropvar).uhVal.QuadPart = val; + return S_OK; +} + +static inline INT PropVariantCompare(REFPROPVARIANT v1, REFPROPVARIANT v2) +{ + return PropVariantCompareEx(v1, v2, PVCU_DEFAULT, PVCF_DEFAULT); +} + +#undef PROPVARIANT_U + #endif
#endif /* __WINE_PROPVARUTIL_H */
The in-memory property store now implements the INamedPropertyStore, IPersistSerializedPropStorage(2), IPersistStream interfaces. --- dlls/propsys/propstore.c | 1088 +++++++++++++++++++++++++++++++++++++- dlls/propsys/propsys_classes.idl | 7 +- dlls/propsys/tests/Makefile.in | 2 +- dlls/propsys/tests/propstore.c | 400 +++++++++++++- dlls/propsys/tests/propsys.c | 3 +- 5 files changed, 1478 insertions(+), 22 deletions(-)
diff --git a/dlls/propsys/propstore.c b/dlls/propsys/propstore.c index 9c848fc..7dd36cb 100644 --- a/dlls/propsys/propstore.c +++ b/dlls/propsys/propstore.c @@ -2,6 +2,7 @@ * standard IPropertyStore implementation * * Copyright 2012 Vincent Povirk for CodeWeavers + * Copyright 2015 Jonas Kümmerlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -28,9 +29,11 @@ #include "objbase.h" #include "rpcproxy.h" #include "propsys.h" +#include "propvarutil.h" #include "wine/debug.h" #include "wine/unicode.h" #include "wine/list.h" +#include "mimeole.h"
#include "initguid.h" #include "propsys_private.h" @@ -53,11 +56,22 @@ typedef struct { DWORD count; } propstore_format;
+/* FIXME: We should really do something clever, like using a hashtable */ +typedef struct { + struct list entry; + PROPVARIANT propvar; + WCHAR name[1]; +} propstore_named_value; + typedef struct { IPropertyStoreCache IPropertyStoreCache_iface; + IPersistSerializedPropStorage2 IPersistSerializedPropStorage2_iface; + INamedPropertyStore INamedPropertyStore_iface; + IPersistStream IPersistStream_iface; LONG ref; CRITICAL_SECTION lock; struct list formats; /* list of struct propstore_format */ + struct list named; /* list of struct propstore_named_value */ } PropertyStore;
static inline PropertyStore *impl_from_IPropertyStoreCache(IPropertyStoreCache *iface) @@ -65,12 +79,23 @@ static inline PropertyStore *impl_from_IPropertyStoreCache(IPropertyStoreCache * return CONTAINING_RECORD(iface, PropertyStore, IPropertyStoreCache_iface); }
-static HRESULT WINAPI PropertyStore_QueryInterface(IPropertyStoreCache *iface, REFIID iid, - void **ppv) +static inline PropertyStore *impl_from_IPersistSerializedPropStorage2(IPersistSerializedPropStorage2 *iface) { - PropertyStore *This = impl_from_IPropertyStoreCache(iface); - TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv); + return CONTAINING_RECORD(iface, PropertyStore, IPersistSerializedPropStorage2_iface); +}
+static inline PropertyStore *impl_from_INamedPropertyStore(INamedPropertyStore *iface) +{ + return CONTAINING_RECORD(iface, PropertyStore, INamedPropertyStore_iface); +} + +static inline PropertyStore *impl_from_IPersistStream(IPersistStream *iface) +{ + return CONTAINING_RECORD(iface, PropertyStore, IPersistStream_iface); +} + +static HRESULT query_interface(PropertyStore *This, REFIID iid, void **ppv) +{ if (!ppv) return E_INVALIDARG;
if (IsEqualIID(&IID_IUnknown, iid) || IsEqualIID(&IID_IPropertyStore, iid) || @@ -78,6 +103,19 @@ static HRESULT WINAPI PropertyStore_QueryInterface(IPropertyStoreCache *iface, R { *ppv = &This->IPropertyStoreCache_iface; } + else if (IsEqualIID(&IID_IPersistSerializedPropStorage, iid) + || IsEqualIID(&IID_IPersistSerializedPropStorage2, iid)) + { + *ppv = &This->IPersistSerializedPropStorage2_iface; + } + else if (IsEqualIID(&IID_INamedPropertyStore, iid)) + { + *ppv = &This->INamedPropertyStore_iface; + } + else if (IsEqualIID(&IID_IPersistStream, iid) || IsEqualIID(&IID_IPersist, iid)) + { + *ppv = &This->IPersistStream_iface; + } else { FIXME("No interface for %s\n", debugstr_guid(iid)); @@ -89,10 +127,85 @@ static HRESULT WINAPI PropertyStore_QueryInterface(IPropertyStoreCache *iface, R return S_OK; }
-static ULONG WINAPI PropertyStore_AddRef(IPropertyStoreCache *iface) +static HRESULT WINAPI PropertyStore_IPropertyStoreCache_QueryInterface( + IPropertyStoreCache *iface, REFIID iid, void **ppv) +{ + PropertyStore *This = impl_from_IPropertyStoreCache(iface); + + TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv); + + return query_interface(This, iid, ppv); +} + +static HRESULT WINAPI PropertyStore_IPersistSerializedPropStorage_QueryInterface( + IPersistSerializedPropStorage2 *iface, REFIID iid, void **ppv) +{ + PropertyStore *This = impl_from_IPersistSerializedPropStorage2(iface); + + TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv); + + return query_interface(This, iid, ppv); +} + +static HRESULT WINAPI PropertyStore_INamedPropertyStore_QueryInterface( + INamedPropertyStore *iface, REFIID iid, void **ppv) +{ + PropertyStore *This = impl_from_INamedPropertyStore(iface); + + TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv); + + return query_interface(This, iid, ppv); +} + +static HRESULT WINAPI PropertyStore_IPersistStream_QueryInterface( + IPersistStream *iface, REFIID iid, void **ppv) +{ + PropertyStore *This = impl_from_IPersistStream(iface); + + TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv); + + return query_interface(This, iid, ppv); +} + +static ULONG add_ref(PropertyStore *This) +{ + return InterlockedIncrement(&This->ref); +} + +static ULONG WINAPI PropertyStore_IPropertyStoreCache_AddRef(IPropertyStoreCache *iface) { PropertyStore *This = impl_from_IPropertyStoreCache(iface); - ULONG ref = InterlockedIncrement(&This->ref); + ULONG ref = add_ref(This); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static ULONG WINAPI PropertyStore_IPersistSerializedPropStorage_AddRef(IPersistSerializedPropStorage2 *iface) +{ + PropertyStore *This = impl_from_IPersistSerializedPropStorage2(iface); + ULONG ref = add_ref(This); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static ULONG WINAPI PropertyStore_INamedPropertyStore_AddRef(INamedPropertyStore *iface) +{ + PropertyStore *This = impl_from_INamedPropertyStore(iface); + ULONG ref = add_ref(This); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static ULONG WINAPI PropertyStore_IPersistStream_AddRef(IPersistStream *iface) +{ + PropertyStore *This = impl_from_IPersistStream(iface); + ULONG ref = add_ref(This);
TRACE("(%p) refcount=%u\n", iface, ref);
@@ -110,26 +223,73 @@ static void destroy_format(propstore_format *format) HeapFree(GetProcessHeap(), 0, format); }
-static ULONG WINAPI PropertyStore_Release(IPropertyStoreCache *iface) +static void destroy_named(propstore_named_value *value) { - PropertyStore *This = impl_from_IPropertyStoreCache(iface); - ULONG ref = InterlockedDecrement(&This->ref); + PropVariantClear(&value->propvar); + HeapFree(GetProcessHeap(), 0, value); +}
- TRACE("(%p) refcount=%u\n", iface, ref); +static ULONG WINAPI release(PropertyStore *This) +{ + ULONG ref = InterlockedDecrement(&This->ref);
if (ref == 0) { propstore_format *cursor, *cursor2; + propstore_named_value *cnamed, *cnamed2; + This->lock.DebugInfo->Spare[0] = 0; DeleteCriticalSection(&This->lock); LIST_FOR_EACH_ENTRY_SAFE(cursor, cursor2, &This->formats, propstore_format, entry) destroy_format(cursor); + LIST_FOR_EACH_ENTRY_SAFE(cnamed, cnamed2, &This->named, propstore_named_value, entry) + destroy_named(cnamed); HeapFree(GetProcessHeap(), 0, This); }
return ref; }
+static ULONG WINAPI PropertyStore_IPropertyStoreCache_Release(IPropertyStoreCache *iface) +{ + PropertyStore *This = impl_from_IPropertyStoreCache(iface); + ULONG ref = release(This); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static ULONG WINAPI PropertyStore_IPersistSerializedPropStorage_Release(IPersistSerializedPropStorage2 *iface) +{ + PropertyStore *This = impl_from_IPersistSerializedPropStorage2(iface); + ULONG ref = release(This); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static ULONG WINAPI PropertyStore_INamedPropertyStore_Release(INamedPropertyStore *iface) +{ + PropertyStore *This = impl_from_INamedPropertyStore(iface); + ULONG ref = release(This); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static ULONG WINAPI PropertyStore_IPersistStream_Release(IPersistStream *iface) +{ + PropertyStore *This = impl_from_IPersistStream(iface); + ULONG ref = release(This); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + static HRESULT WINAPI PropertyStore_GetCount(IPropertyStoreCache *iface, DWORD *cProps) { @@ -153,6 +313,29 @@ static HRESULT WINAPI PropertyStore_GetCount(IPropertyStoreCache *iface, return S_OK; }
+static HRESULT WINAPI PropertyStore_GetNameCount(INamedPropertyStore *iface, + DWORD *cProps) +{ + PropertyStore *This = impl_from_INamedPropertyStore(iface); + propstore_named_value *value; + + TRACE("%p,%p\n", iface, cProps); + + if (!cProps) + return E_POINTER; + + *cProps = 0; + + EnterCriticalSection(&This->lock); + + LIST_FOR_EACH_ENTRY(value, &This->named, propstore_named_value, entry) + *cProps += 1; + + LeaveCriticalSection(&This->lock); + + return S_OK; +} + static HRESULT WINAPI PropertyStore_GetAt(IPropertyStoreCache *iface, DWORD iProp, PROPERTYKEY *pkey) { @@ -203,6 +386,47 @@ static HRESULT WINAPI PropertyStore_GetAt(IPropertyStoreCache *iface, return hr; }
+static HRESULT WINAPI PropertyStore_GetNameAt(INamedPropertyStore *iface, + DWORD iProp, BSTR *pname) +{ + PropertyStore *This = impl_from_INamedPropertyStore(iface); + propstore_named_value *cursor, *value = NULL; + HRESULT hr; + + TRACE("%p,%d,%p\n", iface, iProp, pname); + + if (!pname) + return E_POINTER; + + EnterCriticalSection(&This->lock); + + LIST_FOR_EACH_ENTRY(cursor, &This->named, propstore_named_value, entry) + { + if (iProp == 0) + { + value = cursor; + break; + } + + iProp -= 1; + } + + if (value) + { + *pname = SysAllocString(value->name); + if (*pname) + hr = S_OK; + else + hr = E_OUTOFMEMORY; + } + else + hr = E_INVALIDARG; + + LeaveCriticalSection(&This->lock); + + return hr; +} + static HRESULT PropertyStore_LookupValue(PropertyStore *This, REFPROPERTYKEY key, BOOL insert, propstore_value **result) { @@ -297,6 +521,42 @@ static HRESULT WINAPI PropertyStore_GetValue(IPropertyStoreCache *iface, return hr; }
+static HRESULT WINAPI PropertyStore_GetNamedValue(INamedPropertyStore *iface, + const WCHAR *name, PROPVARIANT *pv) +{ + PropertyStore *This = impl_from_INamedPropertyStore(iface); + propstore_named_value *cursor, *value = NULL; + + TRACE("%p,%p,%p\n", iface, name, pv); + + if (!name || !pv) + return E_POINTER; + + EnterCriticalSection(&This->lock); + + LIST_FOR_EACH_ENTRY(cursor, &This->named, propstore_named_value, entry) + { + if (0 == lstrcmpW(cursor->name, name)) + { + value = cursor; + break; + } + } + + if (value) + { + PropVariantCopy(pv, &value->propvar); + } + else + { + PropVariantInit(pv); + } + + LeaveCriticalSection(&This->lock); + + return S_OK; +} + static HRESULT WINAPI PropertyStore_SetValue(IPropertyStoreCache *iface, REFPROPERTYKEY key, REFPROPVARIANT propvar) { @@ -325,6 +585,59 @@ static HRESULT WINAPI PropertyStore_SetValue(IPropertyStoreCache *iface, return hr; }
+static HRESULT WINAPI PropertyStore_SetNamedValue(INamedPropertyStore *iface, + const WCHAR *name, REFPROPVARIANT propvar) +{ + PropertyStore *This = impl_from_INamedPropertyStore(iface); + propstore_named_value *cursor, *value = NULL; + HRESULT hr = S_OK; + PROPVARIANT temp; + + TRACE("%p,%p,%p\n", iface, name, propvar); + + hr = PropVariantCopy(&temp, propvar); + if (FAILED(hr)) + return hr; + + EnterCriticalSection(&This->lock); + + LIST_FOR_EACH_ENTRY(cursor, &This->named, propstore_named_value, entry) + { + if (0 == lstrcmpW(name, cursor->name)) + { + value = cursor; + break; + } + } + + if (value) + { + /* replace old value */ + PropVariantClear(&value->propvar); + value->propvar = temp; + } + else + { + /* add new value */ + ULONG namesize = (lstrlenW(name) + 1) * sizeof(WCHAR); + + value = HeapAlloc(GetProcessHeap(), 0, sizeof(propstore_named_value) + namesize); + if (value) + { + value->propvar = temp; + memcpy(value->name, name, namesize); + + list_add_head(&This->named, &value->entry); + } + else + hr = E_OUTOFMEMORY; + } + + LeaveCriticalSection(&This->lock); + + return hr; +} + static HRESULT WINAPI PropertyStore_Commit(IPropertyStoreCache *iface) { FIXME("%p: stub\n", iface); @@ -435,10 +748,719 @@ static HRESULT WINAPI PropertyStore_SetValueAndState(IPropertyStoreCache *iface, return hr; }
-static const IPropertyStoreCacheVtbl PropertyStore_Vtbl = { - PropertyStore_QueryInterface, - PropertyStore_AddRef, - PropertyStore_Release, +static HRESULT WINAPI PropertyStore_SetFlags(IPersistSerializedPropStorage2 *iface, + PERSIST_SPROPSTORE_FLAGS flags) +{ + TRACE("%p,%d: stub\n", iface, flags); + + return S_OK; +} + +static HRESULT WINAPI PropertyStore_GetPropertyStorageSize( + IPersistSerializedPropStorage2 *iface, DWORD *pcb) +{ + DWORD count; + DWORD size = 0; + HRESULT hr = S_OK; + PropertyStore *This = impl_from_IPersistSerializedPropStorage2(iface); + propstore_format *format; + propstore_value *value; + propstore_named_value *named_value; + + TRACE("%p,%p\n", iface, pcb); + + if (!pcb) + return E_POINTER; + + EnterCriticalSection(&This->lock); + + /* As a special case, an empty propstore has size 0 */ + hr = PropertyStore_GetCount(&This->IPropertyStoreCache_iface, &count); + if (FAILED(hr) || !count) + goto out; + + LIST_FOR_EACH_ENTRY(format, &This->formats, propstore_format, entry) + { + size += 4; /* DWORD Storage size */ + size += 4; /* DWORD Storage Version */ + size += 16; /* GUID fmtid */ + + LIST_FOR_EACH_ENTRY(value, &format->values, propstore_value, entry) + { + DWORD propsize; + + size += 4; /* DWORD Size */ + size += 4; /* DWORD pid */ + size += 1; /* Reserved. */ + + hr = wine_SerializedPropertySize(&value->propvar, &propsize, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + { + WARN("Property of type %d is not serializable\n", value->propvar.vt); + size = 0; + goto out; + } + + size += propsize; + } + + size += 4; /* Terminating Element */ + } + + if (!list_empty(&This->named)) + { + size += 4; /* DWORD Storage size */ + size += 4; /* DWORD Storage Version */ + size += 16; /* GUID fmtid */ + + LIST_FOR_EACH_ENTRY(named_value, &This->named, propstore_named_value, entry) + { + DWORD propsize; + DWORD namesize; + + size += 4; /* DWORD Size */ + size += 4; /* DWORD String size */ + size += 1; /* Reserved */ + + namesize = (lstrlenW(named_value->name) + 1) * sizeof(WCHAR); + + hr = wine_SerializedPropertySize(&named_value->propvar, &propsize, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + { + WARN("Property of type %d is not serializable\n", named_value->propvar.vt); + size = 0; + goto out; + } + + size += namesize + propsize; + } + + size += 4; /* Terminating element */ + } + + size += 4; /* Terminating Storage */ + +out: + LeaveCriticalSection(&This->lock); + + *pcb = size; + return hr; +} + +static inline void write_dword(BYTE *buffer, DWORD dw) +{ + buffer[0] = (dw & 0x000000FF); + buffer[1] = (dw & 0x0000FF00) >> 8; + buffer[2] = (dw & 0x00FF0000) >> 16; + buffer[3] = (dw & 0xFF000000) >> 24; +} + +static inline void write_word(BYTE *buffer, WORD w) +{ + buffer[0] = (w & 0x00FF); + buffer[1] = (w & 0xFF00) >> 8; +} + +static inline void write_clsid(BYTE *buffer, const CLSID *id) +{ + write_dword(buffer, id->Data1); + write_word(buffer + 4, id->Data2); + write_word(buffer + 6, id->Data3); + memcpy(buffer + 8, id->Data4, 8); +} + +static inline void write_wstring(BYTE *buffer, const WCHAR *str, ULONG cChar) +{ + for (; cChar; --cChar, buffer += 2, ++str) + { + write_word(buffer, (WORD)*str); + } +} + +static HRESULT WINAPI PropertyStore_GetPropertyStorageBuffer( + IPersistSerializedPropStorage2 *iface, SERIALIZEDPROPSTORAGE *psps, DWORD cb, DWORD *cbUsed) +{ + HRESULT hr = S_OK; + PropertyStore *This = impl_from_IPersistSerializedPropStorage2(iface); + propstore_format *format; + propstore_value *value; + propstore_named_value *named_value; + DWORD cb_Start = cb; + BYTE *buffer = (BYTE*)psps; + +#define REQUIRE_SPACE(cBytes) \ + do { \ + if (cBytes > cb) \ + { \ + hr = E_INVALIDARG; \ + goto out; \ + } \ + } while(0) +#define MOVE(cBytes) \ + do { \ + buffer += cBytes; \ + cb -= cBytes; \ + } while(0) + + TRACE("%p,%p,%d,%p\n", iface, psps, cb, cbUsed); + + if (!psps) + return E_POINTER; + + EnterCriticalSection(&This->lock); + + LIST_FOR_EACH_ENTRY(format, &This->formats, propstore_format, entry) + { + BYTE *pStorageSizeBuf = buffer; /* we set it afterwards */ + DWORD storageSize = 24; /* storage size, storage version, fmtid */ + + REQUIRE_SPACE(24); /* Storage size, storage version, fmtid */ + + write_dword(buffer + 4, 0x53505331); /* DWORD Version */ + write_clsid(buffer + 8, &format->fmtid); + + MOVE(24); + + LIST_FOR_EACH_ENTRY(value, &format->values, propstore_value, entry) + { + DWORD propsize; + DWORD propsizeUsed; + + hr = wine_SerializedPropertySize(&value->propvar, &propsize, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + { + WARN("Property of type %d is not serializable\n", value->propvar.vt); + goto out; + } + + REQUIRE_SPACE(9 + propsize); /* Size, pid, reserved, content */ + + write_dword(buffer, propsize + 9); /* size */ + write_dword(buffer + 4, value->pid); /* pid */ + buffer[8] = 0; /* reserved byte to make sure everything is misaligned */ + + MOVE(9); + + hr = wine_WritePropertyToBuffer(&value->propvar, buffer, + propsize, &propsizeUsed, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + { + WARN("Couldn't serialize property of type %d, hr=%08x\n", + value->propvar.vt, (unsigned)hr); + goto out; + } + + if (propsizeUsed != propsize) + { + WARN("Serialized property size calculation was off by %u bytes\n", + propsize - propsizeUsed); + } + + MOVE(propsize); + storageSize += propsize + 9; + } + + /* Terminating Element */ + REQUIRE_SPACE(4); + ZeroMemory(buffer, 4); + MOVE(4); + storageSize += 4; + + write_dword(pStorageSizeBuf, storageSize); + } + + if (!list_empty(&This->named)) + { + BYTE *pStorageSizeBuf = buffer; /* we set it afterwards */ + DWORD storageSize = 24; /* storage size, storage version, fmtid */ + + REQUIRE_SPACE(24); /* Storage size, storage version, fmtid */ + + write_dword(buffer + 4, 0x53505331); /* DWORD Version */ + write_clsid(buffer + 8, &FMTID_NamedProperties); + + MOVE(24); + + LIST_FOR_EACH_ENTRY(named_value, &This->named, propstore_named_value, entry) + { + DWORD propsize; + DWORD propsizeUsed; + DWORD namesize; + + namesize = (lstrlenW(named_value->name) + 1) * sizeof(WCHAR); + TRACE("Writing named property with name %s:%u\n", wine_dbgstr_w(named_value->name), namesize); + + hr = wine_SerializedPropertySize(&named_value->propvar, &propsize, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + { + WARN("Property of type %d is not serializable\n", named_value->propvar.vt); + goto out; + } + + REQUIRE_SPACE(9 + namesize + propsize); + + write_dword(buffer, 9 + namesize + propsize); + write_dword(buffer + 4, namesize); + buffer[8] = 0; + + MOVE(9); + + write_wstring(buffer, named_value->name, namesize/2); + MOVE(namesize); + + hr = wine_WritePropertyToBuffer(&named_value->propvar, buffer, + propsize, &propsizeUsed, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + { + WARN("Couldn't serialize property of type %d, hr=%08x\n", + named_value->propvar.vt, (unsigned)hr); + goto out; + } + + if (propsizeUsed != propsize) + { + WARN("Serialized property size calculation was off by %u bytes\n", + propsize - propsizeUsed); + } + + MOVE(propsize); + storageSize += namesize + propsize + 9; + } + + /* Terminating Element */ + REQUIRE_SPACE(4); + ZeroMemory(buffer, 4); + MOVE(4); + storageSize += 4; + + write_dword(pStorageSizeBuf, storageSize); + } + + /* Terminating Storage */ + REQUIRE_SPACE(4); + ZeroMemory(buffer, 4); + MOVE(4); + +#undef REQUIRE_SPACE +#undef MOVE + +out: + LeaveCriticalSection(&This->lock); + + if (cbUsed) + *cbUsed = SUCCEEDED(hr) ? cb_Start - cb : 0; + + return hr; +} + +static HRESULT WINAPI PropertyStore_GetPropertyStorage( + IPersistSerializedPropStorage2 *iface, SERIALIZEDPROPSTORAGE **ppsps, DWORD *pcb) +{ + HRESULT hr; + DWORD size; + DWORD usedSize; + PropertyStore *This = impl_from_IPersistSerializedPropStorage2(iface); + + TRACE("%p,%p,%p\n", iface, ppsps, pcb); + + if (!ppsps || !pcb) + return E_POINTER; + + EnterCriticalSection(&This->lock); + + hr = PropertyStore_GetPropertyStorageSize(iface, &size); + + if (SUCCEEDED(hr)) + { + if (size == 0) + { + usedSize = 0; + *ppsps = NULL; + } + else if ((*ppsps = CoTaskMemAlloc(size))) + { + hr = PropertyStore_GetPropertyStorageBuffer(iface, *ppsps, size, &usedSize); + if (FAILED(hr)) + { + CoTaskMemFree(*ppsps); + } + + if (size != usedSize) + { + WARN("Buffer calculation was off by %u bytes\n", size - usedSize); + } + } + else + { + hr = E_OUTOFMEMORY; + } + } + + LeaveCriticalSection(&This->lock); + + if (SUCCEEDED(hr)) + { + *pcb = usedSize; + } + else + { + *ppsps = NULL; + *pcb = 0; + } + + return hr; +} + +static inline void read_dword(const BYTE *buffer, DWORD *dw) +{ + *dw = buffer[0] + | (buffer[1] << 8) + | (buffer[2] << 16) + | (buffer[3] << 24); +} + +static inline void read_word(const BYTE *buffer, WORD *w) +{ + *w = buffer[0] | (buffer[1] << 8); +} + +static inline void read_clsid(const BYTE *buffer, CLSID *id) +{ + read_dword(buffer, &id->Data1); + read_word(buffer + 4, &id->Data2); + read_word(buffer + 6, &id->Data3); + memcpy(id->Data4, buffer + 8, 8); +} + +static inline void read_wstring(const BYTE *buffer, WCHAR *str, ULONG cChar) +{ + for (; cChar; --cChar, ++str, buffer += 2) + { + read_word(buffer, (WORD*)str); + } +} + +static HRESULT WINAPI PropertyStore_SetPropertyStorage( + IPersistSerializedPropStorage2 *iface, const SERIALIZEDPROPSTORAGE *psps, DWORD cb) +{ + HRESULT hr = S_OK; + PropertyStore *This = impl_from_IPersistSerializedPropStorage2(iface); + const BYTE *buffer = (const BYTE*)psps; + +#define REQUIRE_SPACE(cBytes) \ + do { \ + if (cBytes > cb) \ + { \ + WARN("Trying to read %u bytes at offset %u where only %u are available\n", \ + cBytes, (unsigned)(buffer - (const BYTE*)psps), cb); \ + hr = E_INVALIDARG; \ + goto out; \ + } \ + } while(0) +#define MOVE(cBytes) \ + do { \ + buffer += cBytes; \ + cb -= cBytes; \ + } while(0) + + TRACE("%p,%p,%d\n", iface, psps, cb); + + /*FIXME: Should we clear existing properties in the store? */ + + if (!psps && !cb) /* special case: empty storage */ + return S_OK; + + if (!psps) + return E_POINTER; + + EnterCriticalSection(&This->lock); + + for (;;) + { + DWORD storageSize; + DWORD version; + CLSID fmtid; + + REQUIRE_SPACE(4); + + /* Read size field */ + read_dword(buffer, &storageSize); + + if (storageSize == 0) /* final element */ + { + MOVE(4); + break; + } + + REQUIRE_SPACE(24); + read_dword(buffer + 4, &version); + read_clsid(buffer + 8, &fmtid); + + if (version != 0x53505331) + { + WARN("Found invalid version 0x%X\n", version); + hr = E_INVALIDARG; + goto out; + } + + MOVE(24); + + if (IsEqualGUID(&fmtid, &FMTID_NamedProperties)) + { + for (;;) + { + DWORD propsize; + DWORD namesize; + PROPVARIANT prop; + WCHAR *name; + + REQUIRE_SPACE(4); + read_dword(buffer, &propsize); + + if (propsize == 0) /* final element */ + { + MOVE(4); + break; + } + + REQUIRE_SPACE(9); + read_dword(buffer + 4, &namesize); + + if (namesize % 2 || namesize > (propsize - 9 - 4)) + { + WARN("unicode string has invalid number of bytes\n"); + hr = E_INVALIDARG; + goto out; + } + + if (buffer[8]) + WARN("reserved byte should be zero, but is 0x%X\n", (unsigned)buffer[4]); + + REQUIRE_SPACE(propsize); /* includes header */ + MOVE(9); + + /* read the name */ + name = CoTaskMemAlloc(namesize + sizeof(WCHAR)); + if (!name) + { + WARN("not enough memory for reading name\n"); + hr = E_OUTOFMEMORY; + goto out; + } + + read_wstring(buffer, name, namesize/2); + name[namesize/2] = 0; /* just to be safe */ + MOVE(namesize); + + /* read the property */ + hr = StgDeserializePropVariant( + (const SERIALIZEDPROPERTYVALUE *)buffer, propsize - 9, &prop); + if (FAILED(hr)) + { + WARN("Couldn't deserialize property, hr=%08x\n", (unsigned)hr); + CoTaskMemFree(name); + goto out; + } + + /* add it to the store */ + hr = PropertyStore_SetNamedValue(&This->INamedPropertyStore_iface, name, &prop); + PropVariantClear(&prop); + CoTaskMemFree(name); + + if (FAILED(hr)) + { + WARN("Couldn't save deserialized property in store, hr=%08x\n", (unsigned)hr); + goto out; + } + + MOVE(propsize - namesize - 9); + } + } + else + { + for (;;) + { + DWORD propsize; + DWORD pid; + PROPVARIANT prop; + PROPERTYKEY key; + + REQUIRE_SPACE(4); + read_dword(buffer, &propsize); + + if (propsize == 0) /* final element */ + { + MOVE(4); + break; + } + + REQUIRE_SPACE(9); + read_dword(buffer + 4, &pid); + + if (buffer[8]) + WARN("reserved byte should be zero, but is 0x%X\n", (unsigned)buffer[4]); + + REQUIRE_SPACE(propsize); /* includes header */ + MOVE(9); + + /* read the property */ + hr = StgDeserializePropVariant( + (const SERIALIZEDPROPERTYVALUE *)buffer, propsize - 9, &prop); + if (FAILED(hr)) + { + WARN("Couldn't deserialize property, hr=%08x\n", (unsigned)hr); + goto out; + } + + /* add it to the store */ + key.fmtid = fmtid; + key.pid = pid; + + hr = PropertyStore_SetValue(&This->IPropertyStoreCache_iface, &key, &prop); + PropVariantClear(&prop); + + if (FAILED(hr)) + { + WARN("Couldn't save deserialized property in store, hr=%08x\n", (unsigned)hr); + goto out; + } + + MOVE(propsize - 9); + } + } + } + +#undef REQUIRE_SPACE +#undef MOVE + +out: + LeaveCriticalSection(&This->lock); + + return hr; +} + +static HRESULT WINAPI PropertyStore_GetClassID(IPersistStream *iface, CLSID *pclsid) +{ + TRACE("%p,%p\n", iface, pclsid); + + if (!pclsid) + return E_POINTER; + + *pclsid = CLSID_InMemoryPropertyStore; + return S_OK; +} + +static HRESULT WINAPI PropertyStore_IsDirty(IPersistStream *iface) +{ + TRACE("%p: stub\n", iface); + + return S_FALSE; +} + +static HRESULT WINAPI PropertyStore_Load(IPersistStream *iface, IStream *stream) +{ + PropertyStore *This = impl_from_IPersistStream(iface); + HRESULT hr = S_OK; + DWORD count; + BYTE sizeBuffer[4]; + BYTE *buffer; + + TRACE("%p,%p\n", iface, stream); + + if (!stream) + return E_POINTER; + + hr = IStream_Read(stream, sizeBuffer, 4, &count); + if (hr != S_OK) + return hr; + + read_dword(sizeBuffer, &count); + buffer = CoTaskMemAlloc(count); + if (!buffer) + return E_OUTOFMEMORY; + + hr = IStream_Read(stream, buffer, count, &count); + if (hr == S_OK) + { + hr = PropertyStore_SetPropertyStorage(&This->IPersistSerializedPropStorage2_iface, + (const SERIALIZEDPROPSTORAGE *)buffer, + count); + } + + CoTaskMemFree(buffer); + + /* MSDN mandates generic error codes */ + if (hr != S_OK && hr != E_OUTOFMEMORY) + hr = E_FAIL; + + return hr; +} + +static HRESULT WINAPI PropertyStore_Save(IPersistStream *iface, + IStream *stream, + BOOL clearDirty) +{ + PropertyStore *This = impl_from_IPersistStream(iface); + HRESULT hr = S_OK; + DWORD count; + DWORD written; + BYTE sizeBuffer[4]; + SERIALIZEDPROPSTORAGE *buffer; + + TRACE("%p %p %d\n", iface, stream, (int)clearDirty); + + if (!stream) + return E_POINTER; + + hr = PropertyStore_GetPropertyStorage(&This->IPersistSerializedPropStorage2_iface, + &buffer, &count); + if (FAILED(hr)) + goto out; + + write_dword(sizeBuffer, count); + + hr = IStream_Write(stream, sizeBuffer, 4, &written); + if (hr != S_OK) + goto out; + + hr = IStream_Write(stream, buffer, count, &written); + + CoTaskMemFree(buffer); + +out: + /* MSDN mandates generic error codes */ + if (hr != S_OK && hr != STG_E_MEDIUMFULL) + hr = STG_E_CANTSAVE; + + return hr; +} + +static HRESULT WINAPI PropertyStore_GetSizeMax(IPersistStream *iface, ULARGE_INTEGER *pcbSize) +{ + PropertyStore *This = impl_from_IPersistStream(iface); + HRESULT hr = S_OK; + DWORD size; + + TRACE("%p,%p", iface, pcbSize); + + if (!pcbSize) + return E_POINTER; + + hr = PropertyStore_GetPropertyStorageSize(&This->IPersistSerializedPropStorage2_iface, + &size); + if (SUCCEEDED(hr)) + { + pcbSize->QuadPart = size; + pcbSize->QuadPart += 4; + } + + return hr; +} + +static const IPropertyStoreCacheVtbl PropertyStore_IPropertyStoreCache_Vtbl = { + PropertyStore_IPropertyStoreCache_QueryInterface, + PropertyStore_IPropertyStoreCache_AddRef, + PropertyStore_IPropertyStoreCache_Release, PropertyStore_GetCount, PropertyStore_GetAt, PropertyStore_GetValue, @@ -450,6 +1472,38 @@ static const IPropertyStoreCacheVtbl PropertyStore_Vtbl = { PropertyStore_SetValueAndState };
+static const IPersistSerializedPropStorage2Vtbl PropertyStore_IPersistSerializedPropStorage_Vtbl = { + PropertyStore_IPersistSerializedPropStorage_QueryInterface, + PropertyStore_IPersistSerializedPropStorage_AddRef, + PropertyStore_IPersistSerializedPropStorage_Release, + PropertyStore_SetFlags, + PropertyStore_SetPropertyStorage, + PropertyStore_GetPropertyStorage, + PropertyStore_GetPropertyStorageSize, + PropertyStore_GetPropertyStorageBuffer +}; + +static const INamedPropertyStoreVtbl PropertyStore_INamedPropertyStore_Vtbl = { + PropertyStore_INamedPropertyStore_QueryInterface, + PropertyStore_INamedPropertyStore_AddRef, + PropertyStore_INamedPropertyStore_Release, + PropertyStore_GetNamedValue, + PropertyStore_SetNamedValue, + PropertyStore_GetNameCount, + PropertyStore_GetNameAt +}; + +static const IPersistStreamVtbl PropertyStore_IPersistStream_Vtbl = { + PropertyStore_IPersistStream_QueryInterface, + PropertyStore_IPersistStream_AddRef, + PropertyStore_IPersistStream_Release, + PropertyStore_GetClassID, + PropertyStore_IsDirty, + PropertyStore_Load, + PropertyStore_Save, + PropertyStore_GetSizeMax +}; + HRESULT PropertyStore_CreateInstance(IUnknown *pUnkOuter, REFIID iid, void** ppv) { PropertyStore *This; @@ -464,11 +1518,15 @@ HRESULT PropertyStore_CreateInstance(IUnknown *pUnkOuter, REFIID iid, void** ppv This = HeapAlloc(GetProcessHeap(), 0, sizeof(PropertyStore)); if (!This) return E_OUTOFMEMORY;
- This->IPropertyStoreCache_iface.lpVtbl = &PropertyStore_Vtbl; + This->IPropertyStoreCache_iface.lpVtbl = &PropertyStore_IPropertyStoreCache_Vtbl; + This->IPersistSerializedPropStorage2_iface.lpVtbl = &PropertyStore_IPersistSerializedPropStorage_Vtbl; + This->INamedPropertyStore_iface.lpVtbl = &PropertyStore_INamedPropertyStore_Vtbl; + This->IPersistStream_iface.lpVtbl = &PropertyStore_IPersistStream_Vtbl; This->ref = 1; InitializeCriticalSection(&This->lock); This->lock.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": PropertyStore.lock"); list_init(&This->formats); + list_init(&This->named);
ret = IPropertyStoreCache_QueryInterface(&This->IPropertyStoreCache_iface, iid, ppv); IPropertyStoreCache_Release(&This->IPropertyStoreCache_iface); diff --git a/dlls/propsys/propsys_classes.idl b/dlls/propsys/propsys_classes.idl index 02555a3..0d5589b 100644 --- a/dlls/propsys/propsys_classes.idl +++ b/dlls/propsys/propsys_classes.idl @@ -25,4 +25,9 @@ threading(both), uuid(9a02e012-6303-4e1e-b9a1-630f802592c5) ] -coclass InMemoryPropertyStore { interface IPropertyStoreCache; } +coclass InMemoryPropertyStore { + interface IPropertyStoreCache; + interface IPersistSerializedPropStorage2; + interface INamedPropertyStore; + interface IPersistStream; +} diff --git a/dlls/propsys/tests/Makefile.in b/dlls/propsys/tests/Makefile.in index d07d675..2913673 100644 --- a/dlls/propsys/tests/Makefile.in +++ b/dlls/propsys/tests/Makefile.in @@ -1,5 +1,5 @@ TESTDLL = propsys.dll -IMPORTS = propsys ole32 oleaut32 +IMPORTS = propsys ole32 oleaut32 uuid
C_SRCS = \ propstore.c \ diff --git a/dlls/propsys/tests/propstore.c b/dlls/propsys/tests/propstore.c index 01500dd..e69ce73 100644 --- a/dlls/propsys/tests/propstore.c +++ b/dlls/propsys/tests/propstore.c @@ -2,6 +2,7 @@ * Unit tests for IPropertyStore and related interfaces * * Copyright 2012 Vincent Povirk + * Copyright 2015 Jonas Kümmerlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -30,6 +31,7 @@ #include "objbase.h" #include "propsys.h" #include "wine/test.h" +#include "propvarutil.h"
#include "initguid.h"
@@ -196,21 +198,194 @@ static void test_inmemorystore(void) IPropertyStoreCache_Release(propcache); }
-static void test_persistserialized(void) +static void test_namedpropertystore(void) { IPropertyStore *propstore; - IPersistSerializedPropStorage *serialized; + INamedPropertyStore *named; + HRESULT hr; + PROPVARIANT propvar; + const WCHAR wcsJava[] = { 'J', 'a', 'v', 'a', 0 }; + const WCHAR wcsBlub[] = { 'B', 'l', 'u', 'b', 0 }; + DWORD count; + BSTR name; + + hr = CoCreateInstance(&CLSID_InMemoryPropertyStore, NULL, CLSCTX_INPROC_SERVER, + &IID_IPropertyStore, (void**)&propstore); + ok(hr == S_OK, "CoCreateInstance failed, hr=%x\n", hr); + + hr = IPropertyStore_QueryInterface(propstore, &IID_INamedPropertyStore, + (void**)&named); + ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr); + + if (FAILED(hr)) + { + skip("IPersistSerializedPropStorage not supported\n"); + return; + } + + InitPropVariantFromUInt32(0xcafebabe, &propvar); + + hr = INamedPropertyStore_SetNamedValue(named, wcsJava, &propvar); + ok(hr == S_OK, "SetNamedValue failed, hr=%x\n", hr); + + hr = INamedPropertyStore_GetNameCount(named, &count); + ok(hr == S_OK, "GetNameCount failed, hr=%x\n", hr); + ok(count == 1, "Expected 1 element, got %u\n", count); + + hr = INamedPropertyStore_GetNameAt(named, 0, &name); + ok(hr == S_OK, "GetNameAt failed, hr=%x\n", hr); + ok(0 == lstrcmpW(name, wcsJava), "Unexpected name %s\n", wine_dbgstr_w(name)); + + SysFreeString(name); + + PropVariantInit(&propvar); + hr = INamedPropertyStore_GetNamedValue(named, wcsJava, &propvar); + ok(hr == S_OK, "GetNamedValue failed, hr=%x\n", hr); + ok(propvar.vt == VT_UI4, "Unexpected vt %d\n", propvar.vt); + ok(U(propvar).ulVal == 0xcafebabe, "Unexpected value %x\n", U(propvar).ulVal); + + InitPropVariantFromInt16(2523, &propvar); + hr = INamedPropertyStore_SetNamedValue(named, wcsBlub, &propvar); + ok(hr == S_OK, "SetNamedValue failed, hr=%x\n", hr); + + hr = INamedPropertyStore_GetNameCount(named, &count); + ok(hr == S_OK, "GetNameCount failed, hr=%x\n", hr); + ok(count == 2, "Expected 2 elements, got %u\n", count); + + InitPropVariantFromUInt32(0xdeadbeef, &propvar); + hr = INamedPropertyStore_SetNamedValue(named, wcsJava, &propvar); + ok(hr == S_OK, "SetNameValue failed, hr=%x\n", hr); + + hr = INamedPropertyStore_GetNameCount(named, &count); + ok(hr == S_OK, "GetNameCount failed, hr=%x\n", hr); + ok(count == 2, "Expected 2 elements, got %u\n", count); + + PropVariantInit(&propvar); + hr = INamedPropertyStore_GetNamedValue(named, wcsJava, &propvar); + ok(hr == S_OK, "GetNamedValue failed, hr=%x\n", hr); + ok(propvar.vt == VT_UI4, "Unexpected vt %d\n", propvar.vt); + ok(U(propvar).ulVal == 0xdeadbeef, "Unexpected value %x\n", U(propvar).ulVal); +} + +static void test_persistserialized(void) +{ + IPropertyStore *propstore = NULL; + INamedPropertyStore *named = NULL; + IPersistSerializedPropStorage *serialized = NULL; + IPersistStream *persiststream = NULL; HRESULT hr; SERIALIZEDPROPSTORAGE *result; DWORD result_size; + HGLOBAL hSerialized; + + WCHAR hello[] = { 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', 0 }; + WCHAR wcsJava[] = { 'J', 'a', 'v', 'a', 0 }; + + /* The spec doesn't impose any order. + * Since we have two properties, there are two valid serializations */ + BYTE expected_result1[] = { + /* WTF: Contrary to what the spec says, the store size field is missing! */ + + 0x45, 0x00, 0x00, 0x00, /* 1st Storage size (69 bytes) */ + + 0x31, 0x53, 0x50, 0x53, /* Version (DWORD) 0x53505331 */ + + /* fmtid GUID 0x7b317433, 0xdfa3, 0x4c44, 0xad, 0x3e, 0x2f, 0x80, 0x4b, 0x90, 0xdb, 0xf4 */ + 0x33, 0x74, 0x31, 0x7b, 0xa3, 0xdf, 0x44, 0x4c, + 0xad, 0x3e, 0x2f, 0x80, 0x4b, 0x90, 0xdb, 0xf4, + + 0x29, 0x00, 0x00, 0x00, /* Value size */ + 0x02, 0x00, 0x00, 0x00, /* PID */ + 0x00, /* random reserved byte to make everything misaligned */ + + 0x1f, 0x00, 0x00, 0x00, /* Variant Type */ + 0x0b, 0x00, 0x00, 0x00, /* String length */ + + /* UTF-16 string: "HelloWorld", padded to 4 bytes */ + 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, + 0x6f, 0x00, 0x57, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x6c, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, /* Final value with size 0 */ + + 0x37, 0x00, 0x00, 0x00, /* 2nd Storage size */ + 0x31, 0x53, 0x50, 0x53, /* Version */ + + /* fmtid named properties */ + 0x05, 0xd5, 0xcd, 0xd5, 0x9c, 0x2e, 0x1b, 0x10, + 0x93, 0x97, 0x08, 0x00, 0x2b, 0x2c, 0xf9, 0xae, + + 0x1b, 0x00, 0x00, 0x00, /* Value size */ + 0x0a, 0x00, 0x00, 0x00, /* Name size */ + 0x00, /* random reserved byte to ensure misalignment */ + + /* UTF-16 string "Java" */ + 0x4a, 0x00, 0x61, 0x00, 0x76, 0x00, 0x61, 0x00, 0x00, 0x00, + + 0x13, 0x00, 0x00, 0x00, /* Variant Type */ + 0xbe, 0xba, 0xfe, 0xca, /* Variant Value */ + + 0x00, 0x00, 0x00, 0x00, /* Final value with size 0 */ + 0x00, 0x00, 0x00, 0x00 /* Final storage with size 0 */ + }; + BYTE expected_result2[] = { + /* WTF: Contrary to what the spec says, the store size field is missing! */ + + 0x37, 0x00, 0x00, 0x00, /* 2nd Storage size */ + 0x31, 0x53, 0x50, 0x53, /* Version */ + + /* fmtid named properties */ + 0x05, 0xd5, 0xcd, 0xd5, 0x9c, 0x2e, 0x1b, 0x10, + 0x93, 0x97, 0x08, 0x00, 0x2b, 0x2c, 0xf9, 0xae, + + 0x1b, 0x00, 0x00, 0x00, /* Value size */ + 0x0a, 0x00, 0x00, 0x00, /* Name size */ + 0x00, /* random reserved byte to ensure misalignment */ + + /* UTF-16 string "Java" */ + 0x4a, 0x00, 0x61, 0x00, 0x76, 0x00, 0x61, 0x00, 0x00, 0x00, + + 0x13, 0x00, 0x00, 0x00, /* Variant Type */ + 0xbe, 0xba, 0xfe, 0xca, /* Variant Value */ + + 0x00, 0x00, 0x00, 0x00, /* Final value with size 0 */ + + 0x45, 0x00, 0x00, 0x00, /* 1st Storage size (69 bytes) */ + + 0x31, 0x53, 0x50, 0x53, /* Version (DWORD) 0x53505331 */ + + /* fmtid GUID 0x7b317433, 0xdfa3, 0x4c44, 0xad, 0x3e, 0x2f, 0x80, 0x4b, 0x90, 0xdb, 0xf4 */ + 0x33, 0x74, 0x31, 0x7b, 0xa3, 0xdf, 0x44, 0x4c, + 0xad, 0x3e, 0x2f, 0x80, 0x4b, 0x90, 0xdb, 0xf4, + + 0x29, 0x00, 0x00, 0x00, /* Value size */ + 0x02, 0x00, 0x00, 0x00, /* PID */ + 0x00, /* random reserved byte to make everything misaligned */ + + 0x1f, 0x00, 0x00, 0x00, /* Variant Type */ + 0x0b, 0x00, 0x00, 0x00, /* String length */ + + /* UTF-16 string: "HelloWorld", padded to 4 bytes */ + 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, + 0x6f, 0x00, 0x57, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x6c, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, /* Final value with size 0 */ + + 0x00, 0x00, 0x00, 0x00 /* Final storage with size 0 */ + };
hr = CoCreateInstance(&CLSID_InMemoryPropertyStore, NULL, CLSCTX_INPROC_SERVER, &IID_IPropertyStore, (void**)&propstore); ok(hr == S_OK, "CoCreateInstance failed, hr=%x\n", hr);
+ hr = IPropertyStore_QueryInterface(propstore, &IID_INamedPropertyStore, + (void**)&named); + ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr); + hr = IPropertyStore_QueryInterface(propstore, &IID_IPersistSerializedPropStorage, (void**)&serialized); - todo_wine ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr); + ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr);
if (FAILED(hr)) { @@ -244,7 +419,225 @@ static void test_persistserialized(void) ok(hr == S_OK, "GetCount failed, hr=%x\n", hr); ok(result_size == 0, "expecting 0, got %d\n", result_size);
+ { + PROPVARIANT vHello; + PROPVARIANT vUlong; + PROPERTYKEY kHello = { PKEY_WineTest, PID_FIRST_USABLE }; + + vHello.vt = VT_LPWSTR; + vHello.u.pwszVal = hello; + vUlong.vt = VT_UI4; + vUlong.u.ulVal = 0xCAFEBABE; + + hr = INamedPropertyStore_SetNamedValue(named, wcsJava, &vUlong); + ok(hr == S_OK, "SetValue failed, hr=%08x\n", hr); + + hr = IPropertyStore_SetValue(propstore, &kHello, &vHello); + ok(hr == S_OK, "SetValue failed, hr=%08x\n", hr); + } + + hr = IPersistSerializedPropStorage_GetPropertyStorage(serialized, &result, &result_size); + ok(hr == S_OK, "GetPropertyStorage failed, hr=%x\n", hr); + ok(result != NULL, "GetPropertyStorage returned NULL where it shouldn't\n"); + + /* compare the result */ + if (result) + { + BOOL same; + + same = result_size == sizeof(expected_result1) + && (!memcmp(expected_result1, result, result_size) + || !memcmp(expected_result2, result, result_size)); + ok(same, "GetPropertyStorage returned unexpected result of size %u (%u expected)\n", + result_size, (unsigned)sizeof(expected_result1)); + + if (!same) + { + size_t i; + + printf("Got result: {"); + for (i = 0; i < result_size; ++i) + { + printf("0x%02x, ", (unsigned)((BYTE*)result)[i]); + } + printf("}\n"); + } + } + + /* Load it again into a new property store */ + if (result) + { + IPropertyStore *propstore2 = NULL; + IPersistSerializedPropStorage *serialized2 = NULL; + INamedPropertyStore *named2 = NULL; + PROPVARIANT vHello; + PROPVARIANT vJava; + PROPERTYKEY key; + + hr = CoCreateInstance(&CLSID_InMemoryPropertyStore, NULL, CLSCTX_INPROC_SERVER, + &IID_IPropertyStore, (void**)&propstore2); + ok(hr == S_OK, "CoCreateInstance failed, hr=%x\n", hr); + + hr = IPropertyStore_QueryInterface(propstore2, &IID_IPersistSerializedPropStorage, + (void**)&serialized2); + ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr); + + hr = IPropertyStore_QueryInterface(propstore2, &IID_INamedPropertyStore, + (void**)&named2); + ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr); + + hr = IPersistSerializedPropStorage_SetPropertyStorage(serialized2, result, result_size); + ok(hr == S_OK, "SetPropertyStorage failed, hr=%x\n", hr); + + hr = IPropertyStore_GetCount(propstore2, &result_size); + ok(hr == S_OK, "GetCount failed, hr=%x\n", hr); + ok(result_size == 1, "expecting 1, got %d\n", result_size); + + hr = INamedPropertyStore_GetNameCount(named2, &result_size); + ok(hr == S_OK, "GetNameCount failed, hr=%x\n", hr); + ok(result_size == 1, "expecting 1, got %d\n", result_size); + + key.fmtid = PKEY_WineTest; + key.pid = PID_FIRST_USABLE; + + hr = IPropertyStore_GetValue(propstore2, &key, &vHello); + ok(hr == S_OK, "GetValue failed, hr=%x\n", hr); + + ok(vHello.vt == VT_LPWSTR, "Variant has wrong type %d\n", vHello.vt); + ok(lstrcmpW(vHello.u.pwszVal, hello) == 0, + "Variant has wrong value %s\n", wine_dbgstr_w(vHello.u.pwszVal)); + + hr = INamedPropertyStore_GetNamedValue(named2, wcsJava, &vJava); + ok(hr == S_OK, "GetNamedValue failed, hr=%x\n", hr); + + ok(vJava.vt == VT_UI4, "Variant has wrong type %d\n", vJava.vt); + ok(vJava.u.ulVal == 0xCAFEBABE, "Variant has wrong value %X\n", vJava.u.ulVal); + + PropVariantClear(&vHello); + PropVariantClear(&vJava); + + IPropertyStore_Release(propstore2); + INamedPropertyStore_Release(named2); + IPersistSerializedPropStorage_Release(serialized2); + + CoTaskMemFree(result); + } + + /* Serialize using IPersistStream */ + hr = IPropertyStore_QueryInterface(propstore, &IID_IPersistStream, (void**)&persiststream); + ok(hr == S_OK, "QueryInterface(IPersistStream) failed, hr=%x\n", hr); + + if (persiststream) + { + IStream *stream; + CLSID clazz; + BYTE *mem; + DWORD size; + + hr = CreateStreamOnHGlobal(NULL, FALSE, &stream); + ok(hr == S_OK, "Failed to create stream on HGLOBAL, hr=%x\n", hr); + + /* check the CLSID */ + hr = IPersistStream_GetClassID(persiststream, &clazz); + ok(hr == S_OK, "Failed to retrieve CLSID, hr=%x\n", hr); + ok(IsEqualGUID(&clazz, &CLSID_InMemoryPropertyStore), + "Wrong CLSID %s returned\n", wine_dbgstr_guid(&clazz)); + + hr = IPersistStream_Save(persiststream, stream, TRUE); + ok(hr == S_OK, "IPersistStream::Save failed, hr=%x\n", hr); + + /* The HGLOBAL should now contain one of the possible serializations, + * prefixed with the length of the following serialized storage. */ + hr = GetHGlobalFromStream(stream, &hSerialized); + ok(hr == S_OK, "WTF: Can't retrieve HGLOBAL from stream, hr=%x\n", hr); + + ok(GlobalSize(hSerialized)-4 == sizeof(expected_result1), + "Serialized result has invalid size %lu, expected %lu\n", + (unsigned long)GlobalSize(hSerialized), (unsigned long)sizeof(expected_result1) + 4); + + mem = GlobalLock(hSerialized); + ok(mem != NULL, "WTF: Can't lock HGLOBAL"); + + size = mem[0] | (mem[1] << 8) | (mem[2] << 16) | (mem[3] << 24); + + ok(size == sizeof(expected_result1), + "Serialized result encodes invalid size %lu, expected %lu\n", + (unsigned long)size, (unsigned long)sizeof(expected_result1)); + ok(memcmp(mem+4, expected_result1, sizeof(expected_result1)) == 0 + || memcmp(mem+4, expected_result2, sizeof(expected_result2)) == 0, + "Serialized result differs from expected result\n"); + GlobalUnlock(hSerialized); + + IPersistStream_Release(persiststream); + IStream_Release(stream); + } + + /* Deserialize using IPersistStream */ + if (hSerialized) + { + IStream *stream; + IPropertyStore *propstore2 = NULL; + IPersistStream *persiststream2 = NULL; + INamedPropertyStore *named2 = NULL; + PROPVARIANT vHello; + PROPVARIANT vJava; + PROPERTYKEY key; + + hr = CreateStreamOnHGlobal(hSerialized, FALSE, &stream); + ok(hr == S_OK, "Failed to create stream on HGLOBAL, hr=%x\n", hr); + + hr = CoCreateInstance(&CLSID_InMemoryPropertyStore, NULL, CLSCTX_INPROC_SERVER, + &IID_IPropertyStore, (void**)&propstore2); + ok(hr == S_OK, "CoCreateInstance failed, hr=%x\n", hr); + + hr = IPropertyStore_QueryInterface(propstore2, &IID_IPersistStream, + (void**)&persiststream2); + ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr); + + hr = IPropertyStore_QueryInterface(propstore2, &IID_INamedPropertyStore, + (void**)&named2); + ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr); + + hr = IPersistStream_Load(persiststream2, stream); + ok(hr == S_OK, "IPersistStream::Load failed, hr=%x\n", hr); + + hr = IPropertyStore_GetCount(propstore2, &result_size); + ok(hr == S_OK, "GetCount failed, hr=%x\n", hr); + ok(result_size == 1, "expecting 1, got %d\n", result_size); + + hr = INamedPropertyStore_GetNameCount(named2, &result_size); + ok(hr == S_OK, "GetNameCount failed, hr=%x\n", hr); + ok(result_size == 1, "expecting 1, got %d\n", result_size); + + key.fmtid = PKEY_WineTest; + key.pid = PID_FIRST_USABLE; + + hr = IPropertyStore_GetValue(propstore2, &key, &vHello); + ok(hr == S_OK, "GetValue failed, hr=%x\n", hr); + + ok(vHello.vt == VT_LPWSTR, "Variant has wrong type %d\n", vHello.vt); + ok(lstrcmpW(vHello.u.pwszVal, hello) == 0, + "Variant has wrong value %s\n", wine_dbgstr_w(vHello.u.pwszVal)); + + hr = INamedPropertyStore_GetNamedValue(named2, wcsJava, &vJava); + ok(hr == S_OK, "GetNamedValue failed, hr=%x\n", hr); + + ok(vJava.vt == VT_UI4, "Variant has wrong type %d\n", vJava.vt); + ok(vJava.u.ulVal == 0xCAFEBABE, "Variant has wrong value %X\n", vJava.u.ulVal); + + PropVariantClear(&vHello); + PropVariantClear(&vJava); + + IPropertyStore_Release(propstore2); + INamedPropertyStore_Release(named2); + IPersistStream_Release(persiststream2); + IStream_Release(stream); + + GlobalFree(hSerialized); + } + IPropertyStore_Release(propstore); + INamedPropertyStore_Release(named); IPersistSerializedPropStorage_Release(serialized); }
@@ -253,6 +646,7 @@ START_TEST(propstore) CoInitialize(NULL);
test_inmemorystore(); + test_namedpropertystore(); test_persistserialized();
CoUninitialize(); diff --git a/dlls/propsys/tests/propsys.c b/dlls/propsys/tests/propsys.c index 311d610..acc35fa 100644 --- a/dlls/propsys/tests/propsys.c +++ b/dlls/propsys/tests/propsys.c @@ -29,12 +29,11 @@ #include "windef.h" #include "winbase.h" #include "objbase.h" -#include "initguid.h" #include "propsys.h" #include "propvarutil.h" #include "wine/test.h"
-DEFINE_GUID(GUID_NULL,0,0,0,0,0,0,0,0,0,0,0); +#include "initguid.h" DEFINE_GUID(dummy_guid, 0xdeadbeef, 0xdead, 0xbeef, 0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe); DEFINE_GUID(expect_guid, 0x12345678, 0x1234, 0x1234, 0x12, 0x34, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12);
This implements PropVariantToString, PropVariantToStringAlloc, PropVariantToBSTR and PropVariantChangeType for stringifying numbers and converting between different types of strings.
MSDN specifies a lot more conversion, so there's plenty of room for future improvements. --- dlls/propsys/propsys.spec | 6 +- dlls/propsys/propvar.c | 319 +++++++++++++++++++++++++++++++++++++++++++ dlls/propsys/tests/propsys.c | 80 ++++++++++- include/propvarutil.h | 3 + 4 files changed, 404 insertions(+), 4 deletions(-)
diff --git a/dlls/propsys/propsys.spec b/dlls/propsys/propsys.spec index 50bdf6e..970b5c0 100644 --- a/dlls/propsys/propsys.spec +++ b/dlls/propsys/propsys.spec @@ -106,7 +106,7 @@ @ stub PropVariantGetUInt16Elem @ stub PropVariantGetUInt32Elem @ stub PropVariantGetUInt64Elem -@ stub PropVariantToBSTR +@ stdcall PropVariantToBSTR(ptr ptr) @ stub PropVariantToBoolean @ stub PropVariantToBooleanVector @ stub PropVariantToBooleanVectorAlloc @@ -133,8 +133,8 @@ @ stub PropVariantToInt64VectorAlloc @ stub PropVariantToInt64WithDefault @ stub PropVariantToStrRet -@ stub PropVariantToString -@ stub PropVariantToStringAlloc +@ stdcall PropVariantToString(ptr ptr long) +@ stdcall PropVariantToStringAlloc(ptr ptr) @ stub PropVariantToStringVector @ stub PropVariantToStringVectorAlloc @ stub PropVariantToStringWithDefault diff --git a/dlls/propsys/propvar.c b/dlls/propsys/propvar.c index e7ba3f3..df195aa 100644 --- a/dlls/propsys/propvar.c +++ b/dlls/propsys/propvar.c @@ -32,6 +32,7 @@ #include "propvarutil.h" #include "mimeole.h" #include "oleauto.h" +#include "strsafe.h"
#include "wine/debug.h" #include "wine/unicode.h" @@ -211,6 +212,299 @@ HRESULT WINAPI PropVariantToUInt64(REFPROPVARIANT propvarIn, ULONGLONG *ret) return hr; }
+static HRESULT PropVariantToString_Size(REFPROPVARIANT propvarIn, UINT *pcch) +{ + HRESULT hr = S_OK; + + TRACE("%p %p\n", propvarIn, pcch); + + *pcch = 0; + + switch (propvarIn->vt) + { + case VT_EMPTY: + *pcch = 0; + break; + case VT_I1: + case VT_I2: + case VT_I4: + case VT_I8: + case VT_INT: + case VT_UI1: + case VT_UI2: + case VT_UI4: + case VT_UI8: + case VT_UINT: + *pcch = 21; /* enough for all 64bit integers */ + break; + case VT_LPWSTR: + *pcch = lstrlenW(propvarIn->u.pwszVal); + break; + case VT_LPSTR: + *pcch = MultiByteToWideChar(CP_ACP, 0, propvarIn->u.pszVal, -1, NULL, 0); + break; + case VT_BSTR: + *pcch = SysStringLen(propvarIn->u.bstrVal); + break; + default: + FIXME("Unsupported variant type %d\n", propvarIn->vt); + hr = E_NOTIMPL; + } + + return hr; +} + +/* FIXME: It is 2015, why do I have to write this? */ +static UINT i64_to_wstr(LONGLONG llVal, WCHAR *buf) +{ + UINT size = 0; + LONGLONG i; + + /* special case 0 */ + if (llVal == 0) + { + buf[0] = '0'; + buf[1] = 0; + return 1; + } + + /* calculate size needed */ + if (llVal < 0) + size += 1; + + for (i = llVal; i != 0; i /= 10) + size++; + + TRACE("Required size: %u chars\n", size); + + /* offset the buffer */ + buf += size; + + /* insert terminating null */ + *buf-- = 0; + + /* begin writing */ + for (i = llVal; i != 0; i /= 10) + { + *buf-- = '0' + abs(i % 10); + } + + if (llVal < 0) + *buf = '-'; + + return size; +} + +static UINT u64_to_wstr(ULONGLONG ullVal, WCHAR *buf) +{ + UINT size = 0; + ULONGLONG i; + + /* special case 0 */ + if (ullVal == 0) + { + buf[0] = '0'; + buf[1] = 0; + return 1; + } + + /* calculate size needed */ + for (i = ullVal; i > 0; i /= 10) + size++; + + /* offset the buffer */ + buf += size; + + /* insert terminating null */ + *buf-- = 0; + + /* begin writing */ + for (i = ullVal; i > 0; i /= 10) + { + *buf-- = '0' + (i % 10); + } + + return size; +} + +HRESULT WINAPI PropVariantToString(REFPROPVARIANT propvar, WCHAR *buf, UINT cch) +{ + HRESULT hr = S_OK; + WCHAR numBuffer[22]; + + TRACE("%p %p %u\n", propvar, buf, cch); + + if (cch < 1) + return E_FAIL; + + switch (propvar->vt) + { + case VT_EMPTY: + *buf = 0; + break; + case VT_I1: + case VT_I2: + case VT_I4: + case VT_I8: + case VT_INT: + { + LONGLONG ll; + UINT numLen; + + hr = PropVariantToInt64(propvar, &ll); + if (FAILED(hr)) + break; + + numLen = i64_to_wstr(ll, numBuffer); + + if (numLen < cch) + { + memcpy(buf, numBuffer, (numLen+1)*sizeof(WCHAR)); + } + else + { + memcpy(buf, numBuffer, (cch-1)*sizeof(WCHAR)); + buf[cch - 1] = 0; + hr = STRSAFE_E_INSUFFICIENT_BUFFER; + } + + break; + } + case VT_UI1: + case VT_UI2: + case VT_UI4: + case VT_UI8: + case VT_UINT: + { + ULONGLONG ll; + UINT numLen; + + hr = PropVariantToUInt64(propvar, &ll); + if (FAILED(hr)) + break; + + numLen = u64_to_wstr(ll, numBuffer); + + if (numLen < cch) + { + memcpy(buf, numBuffer, (numLen+1)*sizeof(WCHAR)); + } + else + { + memcpy(buf, numBuffer, (cch-1)*sizeof(WCHAR)); + buf[cch - 1] = 0; + hr = STRSAFE_E_INSUFFICIENT_BUFFER; + } + + break; + } + case VT_LPWSTR: + { + UINT len = lstrlenW(propvar->u.pwszVal); + + if (len < cch) + { + memcpy(buf, propvar->u.pwszVal, (len+1)*sizeof(WCHAR)); + } + else + { + memcpy(buf, propvar->u.pwszVal, (cch-1)*sizeof(WCHAR)); + buf[cch-1] = 0; + hr = STRSAFE_E_INSUFFICIENT_BUFFER; + } + break; + } + case VT_LPSTR: + { + UINT len = MultiByteToWideChar(CP_ACP, 0, propvar->u.pszVal, -1, NULL, 0); + if (len == 0) + { + hr = E_FAIL; + break; + } + + if (len < cch) + { + MultiByteToWideChar(CP_ACP, 0, propvar->u.pszVal, -1, buf, len+1); + } + else + { + /* TODO: allocate large buffer, copy truncated result */ + FIXME("Truncating narrow string not implemented\n"); + hr = E_NOTIMPL; + } + break; + } + case VT_BSTR: + { + UINT len = SysStringLen(propvar->u.bstrVal); + + if (len < cch) + { + memcpy(buf, propvar->u.bstrVal, len*sizeof(WCHAR)); + buf[len] = 0; + } + else + { + memcpy(buf, propvar->u.bstrVal, (cch-1)*sizeof(WCHAR)); + buf[cch-1] = 0; + hr = STRSAFE_E_INSUFFICIENT_BUFFER; + } + break; + } + default: + FIXME("Unsupported variant type %d\n", propvar->vt); + hr = E_NOTIMPL; + } + + return hr; +} + +HRESULT WINAPI PropVariantToStringAlloc(REFPROPVARIANT prop, WCHAR **pbuf) +{ + HRESULT hr; + UINT size; + + TRACE("%p %p\n", prop, pbuf); + + hr = PropVariantToString_Size(prop, &size); + if (FAILED(hr)) + return hr; + + *pbuf = CoTaskMemAlloc((size+1) * sizeof(WCHAR)); + if (!*pbuf) + return E_OUTOFMEMORY; + + hr = PropVariantToString(prop, *pbuf, size+1); + if (FAILED(hr)) + { + CoTaskMemFree(*pbuf); + *pbuf = NULL; + } + + return hr; +} + +HRESULT WINAPI PropVariantToBSTR(REFPROPVARIANT prop, BSTR *pbstr) +{ + HRESULT hr; + WCHAR *buf; + + TRACE("%p %p\n", prop, pbstr); + + hr = PropVariantToStringAlloc(prop, &buf); + if (FAILED(hr)) + return hr; + + *pbstr = SysAllocString(buf); + if (!*pbstr) + hr = E_OUTOFMEMORY; + + CoTaskMemFree(buf); + + return hr; +} + /****************************************************************** * PropVariantChangeType (PROPSYS.@) */ @@ -290,6 +584,31 @@ HRESULT WINAPI PropVariantChangeType(PROPVARIANT *ppropvarDest, REFPROPVARIANT p } return hr; } + case VT_LPWSTR: + { + WCHAR *str; + + hr = PropVariantToStringAlloc(propvarSrc, &str); + if (SUCCEEDED(hr)) + { + ppropvarDest->vt = VT_LPWSTR; + ppropvarDest->u.pwszVal = str; + } + return hr; + } + case VT_BSTR: + { + BSTR str; + + hr = PropVariantToBSTR(propvarSrc, &str); + if (SUCCEEDED(hr)) + { + ppropvarDest->vt = VT_BSTR; + ppropvarDest->u.bstrVal = str; + } + + return hr; + } }
switch (propvarSrc->vt) diff --git a/dlls/propsys/tests/propsys.c b/dlls/propsys/tests/propsys.c index acc35fa..347df4f 100644 --- a/dlls/propsys/tests/propsys.c +++ b/dlls/propsys/tests/propsys.c @@ -31,6 +31,7 @@ #include "objbase.h" #include "propsys.h" #include "propvarutil.h" +#include "strsafe.h" #include "wine/test.h"
#include "initguid.h" @@ -713,7 +714,7 @@ static void test_PropVariantCompare(void) todo_wine ok(res == 0, "res=%i\n", res);
res = PropVariantCompareEx(&str_2, &i2_2, 0, 0); - todo_wine ok(res == 0, "res=%i\n", res); + ok(res == 0, "res=%i\n", res);
res = PropVariantCompareEx(&str_02, &i2_2, 0, 0); ok(res == -1, "res=%i\n", res); @@ -865,6 +866,82 @@ static void test_intconversions(void) ok(llval == -7, "got wrong value %s\n", debugstr_longlong(llval)); }
+static void test_stringify(void) +{ + PROPVARIANT propvar; + SHORT sVal = -235; + WCHAR sValStr[] = { '-', '2', '3', '5', 0 }; + LONG lVal = 0; + WCHAR lValStr[] = { '0', 0 }; + ULONGLONG ullVal = 0xDEAFBABECAFEBEEF; + WCHAR ullValStr[] = { '1','6','0','4','6','2','4','9','3','2','5','9','5','6','6','1','1','8','2','3',0 }; + WCHAR wszVal[] = { 'H','e','l','l','o','W','o','r','l','d',0 }; + char szVal[] = "HelloWorld"; + WCHAR buffer[22]; + WCHAR smallBuffer[10]; + HRESULT hr; + + ZeroMemory(buffer, sizeof(buffer)); + ZeroMemory(smallBuffer, sizeof(smallBuffer)); + + /* Warm up using a small integer */ + propvar.vt = VT_I2; + propvar.u.iVal = sVal; + + hr = PropVariantToString(&propvar, buffer, sizeof(buffer)/sizeof(buffer[0])); + ok(hr == S_OK, "PropVariantToString(VT_I2) failed, hr=%08x\n", hr); + ok(!lstrcmpW(buffer, sValStr), "Unexpected result %s\n", wine_dbgstr_w(buffer)); + + /* Test zero */ + propvar.vt = VT_I4; + propvar.u.lVal = lVal; + + hr = PropVariantToString(&propvar, buffer, sizeof(buffer)/sizeof(buffer[0])); + ok(hr == S_OK, "PropVariantToString(VT_I2) failed, hr=%08x\n", hr); + ok(!lstrcmpW(buffer, lValStr), "Unexpected result %s\n", wine_dbgstr_w(buffer)); + + /* Test bigger integers */ + propvar.vt = VT_UI8; + propvar.u.uhVal.QuadPart = ullVal; + hr = PropVariantToString(&propvar, buffer, sizeof(buffer)/sizeof(buffer[0])); + ok(hr == S_OK, "PropVariantToString(VT_UI8) failed, hr=%08x\n", hr); + ok(!lstrcmpW(buffer, ullValStr), "Unexpected result %s\n", wine_dbgstr_w(buffer)); + + /* Test truncation */ + hr = PropVariantToString(&propvar, smallBuffer, sizeof(smallBuffer)/sizeof(buffer[0])); + ok(hr == STRSAFE_E_INSUFFICIENT_BUFFER, "Unexpected hr=%08x\n", hr); + ok(!memcmp(ullValStr, smallBuffer, lstrlenW(smallBuffer)*sizeof(WCHAR)), + "Unexpected result %s\n", wine_dbgstr_w(smallBuffer)); + + /* Test narrow string conversion */ + propvar.vt = VT_LPSTR; + propvar.u.pszVal = szVal; + + hr = PropVariantToString(&propvar, buffer, sizeof(buffer)/sizeof(buffer[0])); + ok(hr == S_OK, "PropVariantToString(VT_LPSTR) failed, hr=%08x\n", hr); + ok(!lstrcmpW(buffer, wszVal), "Unexpected result %s\n", wine_dbgstr_w(buffer)); + + /* Test wide string copy */ + propvar.vt = VT_LPWSTR; + propvar.u.pwszVal = wszVal; + + hr = PropVariantToString(&propvar, buffer, sizeof(buffer)/sizeof(buffer[0])); + ok(hr == S_OK, "PropVariantToString(VT_LPWSTR) failed, hr=%08x\n", hr); + ok(!lstrcmpW(buffer, wszVal), "Unexpected result %s\n", wine_dbgstr_w(buffer)); + + /* Test bstr */ + propvar.vt = VT_BSTR; + propvar.u.bstrVal = SysAllocString(wszVal); + + hr = PropVariantToString(&propvar, buffer, sizeof(buffer)/sizeof(buffer[0])); + ok(hr == S_OK, "PropVariantToString(VT_BSTR) failed, hr=%08x\n", hr); + ok(!lstrcmpW(buffer, wszVal), "Unexpected result %s\n", wine_dbgstr_w(buffer)); + + SysFreeString(propvar.u.bstrVal); + + /* TODO test all other funny strigifications */ +} + static char *buffer_printable(void *buffer, size_t buffer_size) { char *heap_buffer, *p; @@ -1250,6 +1327,7 @@ START_TEST(propsys) test_InitPropVariantFromBuffer(); test_PropVariantToGUID(); test_PropVariantCompare(); + test_stringify(); test_intconversions(); test_serialization(); } diff --git a/include/propvarutil.h b/include/propvarutil.h index a2ec2af..571588a 100644 --- a/include/propvarutil.h +++ b/include/propvarutil.h @@ -78,6 +78,9 @@ HRESULT WINAPI PropVariantToInt64(REFPROPVARIANT propvarIn, LONGLONG *ret); HRESULT WINAPI PropVariantToUInt16(REFPROPVARIANT propvarIn, USHORT *ret); HRESULT WINAPI PropVariantToUInt32(REFPROPVARIANT propvarIn, ULONG *ret); HRESULT WINAPI PropVariantToUInt64(REFPROPVARIANT propvarIn, ULONGLONG *ret); +HRESULT WINAPI PropVariantToString(REFPROPVARIANT propvarIn, WCHAR *psz, UINT cch); +HRESULT WINAPI PropVariantToBSTR(REFPROPVARIANT propvarIn, BSTR *pbstr); +HRESULT WINAPI PropVariantToStringAlloc(REFPROPVARIANT propvarIn, WCHAR **ppszOut);
#ifdef NO_PROPVAR_INLINES HRESULT InitPropVariantFromBoolean(BOOL fVal, PROPVARIANT *ppropvar);
On 14.07.2015 18:07, Jonas Kümmerlin wrote:
Hi, Jonas, and welcome.
I don't know much about AppUserModelID, but still some comments on your patches:
- case VT_LPSTR:
*pcch = MultiByteToWideChar(CP_ACP, 0, propvarIn->u.pszVal, -1, NULL, 0);
break;
You sure it's meant to be WCHAR even for VT_LPSTR type? This looks wrong to me.
- case VT_LPSTR:
- {
UINT len = MultiByteToWideChar(CP_ACP, 0, propvar->u.pszVal, -1, NULL, 0);
if (len == 0)
{
hr = E_FAIL;
break;
}
if (len < cch)
{
MultiByteToWideChar(CP_ACP, 0, propvar->u.pszVal, -1, buf, len+1);
}
else
{
/* TODO: allocate large buffer, copy truncated result */
FIXME("Truncating narrow string not implemented\n");
hr = E_NOTIMPL;
}
break;
- }
Same.
+/* FIXME: It is 2015, why do I have to write this? */ +static UINT i64_to_wstr(LONGLONG llVal, WCHAR *buf)
Maybe you could reuse VarBstrFromUI8() from oleaut32?
if (len < cch)
{
memcpy(buf, propvar->u.bstrVal, len*sizeof(WCHAR));
buf[len] = 0;
}
Isn't it what strcpyW does? This pattern appears in several places.
- hr = PropVariantToString_Size(prop, &size);
- if (FAILED(hr))
return hr;
- *pbuf = CoTaskMemAlloc((size+1) * sizeof(WCHAR));
- if (!*pbuf)
return E_OUTOFMEMORY;
- hr = PropVariantToString(prop, *pbuf, size+1);
- if (FAILED(hr))
- {
CoTaskMemFree(*pbuf);
*pbuf = NULL;
- }
- return hr;
It's the only place where PropVariantToString_Size is used, so you could as well return full length from it.
- ULONGLONG ullVal = 0xDEAFBABECAFEBEEF;
I think we prefer lower case hex literals these days.
Nikolay Sivov wrote:
- case VT_LPSTR:
*pcch = MultiByteToWideChar(CP_ACP, 0, propvarIn
->u.pszVal, -1, NULL, 0);
break;
You sure it's meant to be WCHAR even for VT_LPSTR type? This looks wrong to me.
Yes, windows does convert between narrow and wide strings as requested. If you call PropVariantToString, you'll get a unicode string no matter what (see tests/propsys.c:921).
+/* FIXME: It is 2015, why do I have to write this? */ +static UINT i64_to_wstr(LONGLONG llVal, WCHAR *buf)
Maybe you could reuse VarBstrFromUI8() from oleaut32?
VarBstrFromUI8() allocates a new BSTR, I'd prefer something like snprintfW(), but according to libs/wine/string.c:478 it won't support 64bit integers on 32bit machines (and C89 doesn't even specify "%lld"). Maybe vsnprintfW() should be fixed then.
if (len < cch)
{
memcpy(buf, propvar->u.bstrVal, len*sizeof(WCHAR));
buf[len] = 0;
}
Isn't it what strcpyW does? This pattern appears in several places.
Actually, I'd rather prefer to replace the whole block with StringCchCopyW. Which will most likely happen.
--- dlls/shell32/shelllink.c | 218 ++++++++++++++++++++++++++++---------- dlls/shell32/tests/shell32_test.h | 9 ++ dlls/shell32/tests/shelllink.c | 123 +++++++++++++++++++++ 3 files changed, 292 insertions(+), 58 deletions(-)
diff --git a/dlls/shell32/shelllink.c b/dlls/shell32/shelllink.c index bd3507e..71960a9 100644 --- a/dlls/shell32/shelllink.c +++ b/dlls/shell32/shelllink.c @@ -155,6 +155,8 @@ typedef struct IUnknown *site;
LPOLESTR filepath; /* file path returned by IPersistFile::GetCurFile */ + + IPropertyStore *propStorage; /* In-memory property backing store */ } IShellLinkImpl;
static inline IShellLinkImpl *impl_from_IShellLinkA(IShellLinkA *iface) @@ -577,6 +579,9 @@ static HRESULT Stream_ReadChunk( IStream* stm, LPVOID *data ) if( FAILED( r ) || count != sizeof(size) ) return E_FAIL;
+ if( size == 0 ) + return S_FALSE; + chunk = HeapAlloc( GetProcessHeap(), 0, size ); if( !chunk ) return E_OUTOFMEMORY; @@ -643,7 +648,7 @@ static HRESULT Stream_LoadLocation( IStream *stm, DWORD n;
r = Stream_ReadChunk( stm, (LPVOID*) &p ); - if( FAILED(r) ) + if( FAILED(r) || r == S_FALSE ) return r;
loc = (LOCATION_INFO*) p; @@ -689,45 +694,30 @@ static HRESULT Stream_LoadLocation( IStream *stm, * In the original Win32 implementation the buffers are not initialized * to zero, so data trailing the string is random garbage. */ -static HRESULT Stream_LoadAdvertiseInfo( IStream* stm, LPWSTR *str ) +static HRESULT Stream_LoadAdvertiseInfo( EXP_DARWIN_LINK *chunk, LPWSTR *str ) { - DWORD size; - ULONG count; - HRESULT r; - EXP_DARWIN_LINK buffer; - - TRACE("%p\n",stm); - - r = IStream_Read( stm, &buffer.dbh.cbSize, sizeof (DWORD), &count ); - if( FAILED( r ) ) - return r; + TRACE("%p %p\n",chunk,str);
- /* make sure that we read the size of the structure even on error */ - size = sizeof buffer - sizeof (DWORD); - if( buffer.dbh.cbSize != sizeof buffer ) + if( chunk->dbh.cbSize != sizeof *chunk ) { ERR("Ooops. This structure is not as expected...\n"); return E_FAIL; }
- r = IStream_Read( stm, &buffer.dbh.dwSignature, size, &count ); - if( FAILED( r ) ) - return r; + /* ensure null termination */ + chunk->szwDarwinID[MAX_PATH-1] = 0;
- if( count != size ) - return E_FAIL; + TRACE("magic %08x string = %s\n", chunk->dbh.dwSignature, debugstr_w(chunk->szwDarwinID));
- TRACE("magic %08x string = %s\n", buffer.dbh.dwSignature, debugstr_w(buffer.szwDarwinID)); - - if( (buffer.dbh.dwSignature&0xffff0000) != 0xa0000000 ) + if( (chunk->dbh.dwSignature&0xffff0000) != 0xa0000000 ) { - ERR("Unknown magic number %08x in advertised shortcut\n", buffer.dbh.dwSignature); + ERR("Unknown magic number %08x in advertised shortcut\n", chunk->dbh.dwSignature); return E_FAIL; }
*str = HeapAlloc( GetProcessHeap(), 0, - (lstrlenW(buffer.szwDarwinID)+1) * sizeof(WCHAR) ); - lstrcpyW( *str, buffer.szwDarwinID ); + (lstrlenW(chunk->szwDarwinID)+1) * sizeof(WCHAR) ); + lstrcpyW( *str, chunk->szwDarwinID );
return S_OK; } @@ -743,7 +733,7 @@ static HRESULT WINAPI IPersistStream_fnLoad( ULONG dwBytesRead; BOOL unicode; HRESULT r; - DWORD zero; + DATABLOCK_HEADER *extra = NULL;
IShellLinkImpl *This = impl_from_IPersistStream(iface);
@@ -860,33 +850,88 @@ static HRESULT WINAPI IPersistStream_fnLoad( if( FAILED( r ) ) goto end;
- if( hdr.dwFlags & SLDF_HAS_LOGO3ID ) + /* Extra data chunks can appear in any order, and there may be + * any number of them. We can make use of some of them, and will + * ignore the others + */ + for (;;) { - r = Stream_LoadAdvertiseInfo( stm, &This->sProduct ); - TRACE("Product -> %s\n",debugstr_w(This->sProduct)); - } - if( FAILED( r ) ) - goto end; + if ( extra ) + HeapFree( GetProcessHeap(), 0, extra );
- if( hdr.dwFlags & SLDF_HAS_DARWINID ) - { - r = Stream_LoadAdvertiseInfo( stm, &This->sComponent ); - TRACE("Component -> %s\n",debugstr_w(This->sComponent)); - } - if( FAILED( r ) ) - goto end; + r = Stream_ReadChunk(stm, (void**)&extra); + if ( r == S_FALSE ) /* size zero chunk -> end of extra data */ + break;
- r = IStream_Read(stm, &zero, sizeof zero, &dwBytesRead); - if( FAILED( r ) || zero || dwBytesRead != sizeof zero ) - { - /* Some lnk files have extra data blocks starting with a - * DATABLOCK_HEADER. For instance EXP_SPECIAL_FOLDER and an unknown - * one with a 0xa0000003 signature. However these don't seem to matter - * too much. - */ - WARN("Last word was not zero\n"); + if ( FAILED( r ) ) + break; + + if ( extra->cbSize <= sizeof(*extra) ) + { + WARN("Extra chunk has invalid size\n"); + continue; + } + + if ( extra->dwSignature == 0xA0000006 ) + { + /* Darwin data block */ + if ( !( hdr.dwFlags & SLDF_HAS_DARWINID ) ) + { + WARN("Found unexpected DarwinDataBlock, parsing anyway\n"); + } + + if ( This->sComponent ) + { + WARN("Found doubled DarwinDataBlock, ignoring\n"); + } + else + { + r = Stream_LoadAdvertiseInfo( (EXP_DARWIN_LINK*)extra, &This->sComponent ); + TRACE("Component -> %s\n",debugstr_w(This->sComponent)); + } + + if ( FAILED( r ) ) + break; + } + else if ( extra->dwSignature == 0xA0000009 ) + { + /* Property store data block */ + IPersistSerializedPropStorage *serialized = NULL; + + r = IPropertyStore_QueryInterface(This->propStorage, + &IID_IPersistSerializedPropStorage, + (void**)&serialized); + if ( FAILED( r ) ) + { + WARN("QueryInterface(IPersistSerializedPropStorage): %08x\n", r); + break; + } + + r = IPersistSerializedPropStorage_SetPropertyStorage( + serialized, + (PCUSERIALIZEDPROPSTORAGE)((EXP_PROPERTYSTORAGE*)extra)->abPropertyStorage, + extra->cbSize - 8); + + IPersistSerializedPropStorage_Release(serialized); + + if ( FAILED( r ) ) + { + WARN("IPersistSerializedPropStorage::SetPropertyStorage: %08x\n", r); + break; + } + } + else + { + WARN("Extra data chunk with unknown signature 0x%08x, ignoring\n", extra->dwSignature); + } }
+ if ( extra ) + HeapFree( GetProcessHeap(), 0, extra); + + if ( FAILED( r ) ) + goto end; + TRACE("OK\n");
pdump (This->pPidl); @@ -1011,6 +1056,44 @@ static HRESULT Stream_WriteAdvertiseInfo( IStream* stm, LPCWSTR string, DWORD ma return IStream_Write( stm, buffer, buffer->dbh.cbSize, &count ); }
+static HRESULT Stream_WritePropertyStore( IStream *stm, IPropertyStore *store ) +{ + DWORD size; + DWORD count; + SERIALIZEDPROPSTORAGE *storage = NULL; + DATABLOCK_HEADER header; + HRESULT r = S_OK; + IPersistSerializedPropStorage *serialized = NULL; + + TRACE("%p %p\n", stm, store); + + r = IPropertyStore_QueryInterface( store, &IID_IPersistSerializedPropStorage, (void**)&serialized ); + if ( FAILED( r ) ) + return r; + + r = IPersistSerializedPropStorage_GetPropertyStorage(serialized, &storage, &size); + if ( FAILED( r ) ) + goto end; + + header.cbSize = size + sizeof(header); + header.dwSignature = 0xA0000009; + + r = IStream_Write( stm, &header, sizeof(header), &count ); + if ( FAILED( r ) || count != sizeof(header) ) + goto end; + + r = IStream_Write( stm, storage, size, &count ); + +end: + if ( storage ) + CoTaskMemFree( storage ); + + if ( serialized ) + IPersistSerializedPropStorage_Release( serialized ); + + return r; +} + /************************************************************************ * IPersistStream_Save (IPersistStream) * @@ -1104,6 +1187,8 @@ static HRESULT WINAPI IPersistStream_fnSave( if( This->sComponent ) r = Stream_WriteAdvertiseInfo( stm, This->sComponent, EXP_DARWIN_ID_SIG );
+ r = Stream_WritePropertyStore( stm, This->propStorage ); + /* the last field is a single zero dword */ zero = 0; r = IStream_Write( stm, &zero, sizeof zero, &count ); @@ -1641,6 +1726,8 @@ static ULONG WINAPI IShellLinkW_fnRelease(IShellLinkW * iface) if (This->pPidl) ILFree(This->pPidl);
+ IPropertyStore_Release(This->propStorage); + LocalFree(This);
return 0; @@ -2581,36 +2668,44 @@ static ULONG WINAPI propertystore_Release(IPropertyStore *iface) static HRESULT WINAPI propertystore_GetCount(IPropertyStore *iface, DWORD *props) { IShellLinkImpl *This = impl_from_IPropertyStore(iface); - FIXME("(%p)->(%p): stub\n", This, props); - return E_NOTIMPL; + + return IPropertyStore_GetCount(This->propStorage, props); }
static HRESULT WINAPI propertystore_GetAt(IPropertyStore *iface, DWORD propid, PROPERTYKEY *key) { IShellLinkImpl *This = impl_from_IPropertyStore(iface); - FIXME("(%p)->(%d %p): stub\n", This, propid, key); - return E_NOTIMPL; + + return IPropertyStore_GetAt(This->propStorage, propid, key); }
static HRESULT WINAPI propertystore_GetValue(IPropertyStore *iface, REFPROPERTYKEY key, PROPVARIANT *value) { IShellLinkImpl *This = impl_from_IPropertyStore(iface); - FIXME("(%p)->(%p %p): stub\n", This, key, value); - return E_NOTIMPL; + + return IPropertyStore_GetValue(This->propStorage, key, value); }
static HRESULT WINAPI propertystore_SetValue(IPropertyStore *iface, REFPROPERTYKEY key, REFPROPVARIANT value) { IShellLinkImpl *This = impl_from_IPropertyStore(iface); - FIXME("(%p)->(%p %p): stub\n", This, key, value); - return E_NOTIMPL; + HRESULT r; + + r = IPropertyStore_SetValue(This->propStorage, key, value); + if (SUCCEEDED(r)) + This->bDirty = TRUE; + + return r; }
static HRESULT WINAPI propertystore_Commit(IPropertyStore *iface) { IShellLinkImpl *This = impl_from_IPropertyStore(iface); + + /* The in-memory property store doesn't need it, we don't care, so just fake it */ + /* or, preferably, research the correct semantics for the commit call */ FIXME("(%p): stub\n", This); - return E_NOTIMPL; + return S_OK; }
static const IPropertyStoreVtbl propertystorevtbl = { @@ -2627,6 +2722,7 @@ static const IPropertyStoreVtbl propertystorevtbl = { HRESULT WINAPI IShellLink_Constructor(IUnknown *outer, REFIID riid, void **obj) { IShellLinkImpl * sl; + IPropertyStore * propStorage; HRESULT r;
TRACE("outer=%p riid=%s\n", outer, debugstr_guid(riid)); @@ -2636,6 +2732,11 @@ HRESULT WINAPI IShellLink_Constructor(IUnknown *outer, REFIID riid, void **obj) if (outer) return CLASS_E_NOAGGREGATION;
+ r = CoCreateInstance(&CLSID_InMemoryPropertyStore, NULL, CLSCTX_INPROC_SERVER, + &IID_IPropertyStore, (void**)&propStorage); + if (FAILED(r)) + return E_FAIL; + sl = LocalAlloc(LMEM_ZEROINIT,sizeof(IShellLinkImpl)); if (!sl) return E_OUTOFMEMORY; @@ -2655,6 +2756,7 @@ HRESULT WINAPI IShellLink_Constructor(IUnknown *outer, REFIID riid, void **obj) sl->iIdOpen = -1; sl->site = NULL; sl->filepath = NULL; + sl->propStorage = propStorage;
TRACE("(%p)\n", sl);
diff --git a/dlls/shell32/tests/shell32_test.h b/dlls/shell32/tests/shell32_test.h index 42a74fb..d3ade95 100644 --- a/dlls/shell32/tests/shell32_test.h +++ b/dlls/shell32/tests/shell32_test.h @@ -18,10 +18,17 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include "propsys.h"
/* Helper function for creating .lnk files */ typedef struct { + PROPERTYKEY key; + PROPVARIANT value; +} lnk_prop_t; + +typedef struct +{ const char* description; const char* workdir; const char* path; @@ -31,6 +38,8 @@ typedef struct const char* icon; int icon_id; WORD hotkey; + ULONG c_props; + lnk_prop_t *props; } lnk_desc_t;
#define create_lnk(a,b,c) create_lnk_(__LINE__, (a), (b), (c)) diff --git a/dlls/shell32/tests/shelllink.c b/dlls/shell32/tests/shelllink.c index b258052..21513e0 100644 --- a/dlls/shell32/tests/shelllink.c +++ b/dlls/shell32/tests/shelllink.c @@ -35,6 +35,11 @@ # define SLDF_HAS_LOGO3ID 0x00000800 /* not available in the Vista SDK */ #endif
+#include "initguid.h" +#include "propkey.h" + +DEFINE_PROPERTYKEY(PKEY_WineTest, 0x7b317433, 0xdfa3, 0x4c44, 0xad, 0x3e, 0x2f, 0x80, 0x4b, 0x90, 0xdb, 0xf4, 2); + static void (WINAPI *pILFree)(LPITEMIDLIST); static BOOL (WINAPI *pILIsEqual)(LPCITEMIDLIST, LPCITEMIDLIST); static HRESULT (WINAPI *pSHILCreateFromPath)(LPCWSTR, LPITEMIDLIST *,DWORD*); @@ -100,6 +105,7 @@ static void test_get_set(void) HRESULT r; IShellLinkA *sl; IShellLinkW *slW = NULL; + IPropertyStore *ps = NULL; char mypath[MAX_PATH]; char buffer[INFOTIPSIZE]; LPITEMIDLIST pidl, tmp_pidl; @@ -342,6 +348,36 @@ static void test_get_set(void) ok(r == S_OK, "GetHotkey failed (0x%08x)\n", r); ok(w==0x5678, "GetHotkey returned %d'\n", w);
+ /* Test the IPropertyStore interface provided since 7 */ + r = IShellLinkA_QueryInterface(sl, &IID_IPropertyStore, (void**)&ps); + if (FAILED(r)) + { + win_skip("IPropertyStore not available for shell links\n"); + } + else + { + PROPVARIANT var; + WCHAR id[] = { 'W','i','n','e','.','T','e','s','t','.','H','e','l','l','o',0 }; + + ok(ps != NULL, "WTF: Got NULL property store\n"); + + /* write a simple and common property */ + var.vt = VT_LPWSTR; + U(var).pwszVal = id; + r = IPropertyStore_SetValue(ps, &PKEY_AppUserModel_ID, &var); + ok(r == S_OK, "IPropertyStore::SetValue failed (0x%08x)\n", r); + + /* and immediately read it back */ + r = IPropertyStore_GetValue(ps, &PKEY_AppUserModel_ID, &var); + ok(r == S_OK, "IPropertyStore::GetValue failed(0x%08x)\n", r); + ok(var.vt == VT_LPWSTR, "Wrong variant type %d has been returned\n", (int)var.vt); + ok(!lstrcmpW(U(var).pwszVal, id), "Wrong result %s returned\n", wine_dbgstr_w(U(var).pwszVal)); + + PropVariantClear(&var); + + IPropertyStore_Release(ps); + } + IShellLinkA_Release(sl); }
@@ -412,6 +448,34 @@ void create_lnk_(int line, const WCHAR* path, lnk_desc_t* desc, int save_fails) lok(r == S_OK, "SetHotkey failed (0x%08x)\n", r); }
+ if (desc->c_props) + { + IPropertyStore *ps = NULL; + r = IShellLinkA_QueryInterface(sl, &IID_IPropertyStore, (void**)&ps); + if (FAILED(r)) + { + win_skip("IPropertyStore not availble for shell links\n"); + } + else + { + ULONG i; + for (i = 0; i < desc->c_props; ++i) + { + r = IPropertyStore_SetValue(ps, &desc->props[i].key, &desc->props[i].value); + lok(r == S_OK, "IPropertyStore::SetValue failed (0x%08x)\n", r); + } + + /* On windows, this is sometimes necessary so that all properties are + * saved in IPersistFile::Save. But sometimes, it works well without + * committing. It remains a mystery Wine will most likely not replicate. + */ + r = IPropertyStore_Commit(ps); + lok(r == S_OK, "IPropertyStore::Commit failed(0x%08x)\n", r); + + IPropertyStore_Release(ps); + } + } + r = IShellLinkA_QueryInterface(sl, &IID_IPersistFile, (void**)&pf); lok(r == S_OK, "no IID_IPersistFile (0x%08x)\n", r); if (r == S_OK) @@ -594,6 +658,52 @@ static void check_lnk_(int line, const WCHAR* path, lnk_desc_t* desc, int todo) "GetHotkey returned 0x%04x instead of 0x%04x\n", i, desc->hotkey); } + if (desc->c_props) + { + IPropertyStore *ps = NULL; + r = IShellLinkA_QueryInterface(sl, &IID_IPropertyStore, (void**)&ps); + if (FAILED(r)) + { + win_skip("IPropertyStore not availble for shell links\n"); + } + else + { + ULONG i; + PROPVARIANT v; + HMODULE hPropsys = NULL; + INT WINAPI (*pPropVariantCompareEx)( + REFPROPVARIANT propvar1, REFPROPVARIANT propvar2, + int uint, int flags) = NULL; + + hPropsys = LoadLibraryA("propsys.dll"); + if (hPropsys) + { + pPropVariantCompareEx = (void*)GetProcAddress(hPropsys, "PropVariantCompareEx"); + } + + for (i = 0; i < desc->c_props; ++i) + { + r = IPropertyStore_GetValue(ps, &desc->props[i].key, &v); + ok(r == S_OK, "IPropertyStore::GetValue failed (0x%08x)\n", r); + + if (!pPropVariantCompareEx) + { + win_skip("PropVariantCompareEx not available, can't compare property\n"); + } + else + { + lok(!pPropVariantCompareEx(&v, &desc->props[i].value, 0, 0), + "Property differs from expected one, found type %d, expected %d\n", + v.vt, desc->props[i].value.vt); + } + + PropVariantClear(&v); + } + + FreeLibrary(hPropsys); + IPropertyStore_Release(ps); + } + }
IShellLinkA_Release(sl); } @@ -603,6 +713,9 @@ static void test_load_save(void) WCHAR lnkfile[MAX_PATH]; char lnkfileA[MAX_PATH]; static const char lnkfileA_name[] = "\test.lnk"; + static WCHAR aum_id[] = { 'W','i','n','e','.','T','e','s','t',0 }; + + lnk_prop_t props[2];
lnk_desc_t desc; char mypath[MAX_PATH]; @@ -635,6 +748,16 @@ static void test_load_save(void) desc.icon=""; check_lnk(lnkfile, &desc, 0x0);
+ /* Spice up future tests by introducing properties */ + props[0].key = PKEY_AppUserModel_ID; + props[0].value.vt = VT_LPWSTR; + U(props[0].value).pwszVal = aum_id; + props[1].key = PKEY_WineTest; + props[1].value.vt = VT_UI4; + U(props[1].value).ulVal = 0xCAFEBABE; + desc.props = props; + desc.c_props = 2; + /* Point a .lnk file to nonexistent files */ desc.description=""; desc.workdir="c:\Nonexitent\work\directory";
Hi,
Thanks for working on this. Getting this properly implemented should make Wine integrate much better with Unity and Gnome Shell environments.
Actually, in most cases just setting StartupWMClass would work, but I would want us using the full path to the executable first.
I did some work on this a while back, but I only ever got as far as putting in some of the propsys bits.
It seems like patches 3 and 4 didn't get through to the mailing list.
OLE32.dll now exports a few internal functions for propsys.dll to use. The newly implemented functions handle way more cases than the original implementations, properly protect themselves against buffer overflows and are more thoroughly tested.
This allows to provide a high-quality implementation of StgDeserializePropVariant and StgSerializePropVariant.
As a byproduct of implementing the tests, numerous InitPropVariant* routines have been implemented in propsys.dll, and PropVariantCompareEx has been extended to support all serializable properties. --- dlls/ole32/ole32.spec | 3 + dlls/ole32/stg_prop.c | 1711 ++++++++++++++++++++++++++++++++++------ dlls/ole32/tests/propvariant.c | 55 +- dlls/propsys/propsys.spec | 28 +- dlls/propsys/propvar.c | 502 +++++++++++- dlls/propsys/tests/propsys.c | 377 +++++++++ include/propidl.idl | 10 + include/propvarutil.h | 103 ++- 8 files changed, 2521 insertions(+), 268 deletions(-)
diff --git a/dlls/ole32/ole32.spec b/dlls/ole32/ole32.spec index b36e683..8ec9c83 100644 --- a/dlls/ole32/ole32.spec +++ b/dlls/ole32/ole32.spec @@ -287,3 +287,6 @@ @ stdcall WriteFmtUserTypeStg(ptr long ptr) @ stub WriteOleStg @ stub WriteStringStream +@ stdcall wine_WritePropertyToBuffer(ptr ptr long ptr long long) +@ stdcall wine_SerializedPropertySize(ptr ptr long long) +@ stdcall wine_ReadProperty(ptr ptr long ptr long long ptr ptr) diff --git a/dlls/ole32/stg_prop.c b/dlls/ole32/stg_prop.c index da361ce..6cf6c62 100644 --- a/dlls/ole32/stg_prop.c +++ b/dlls/ole32/stg_prop.c @@ -1038,183 +1038,1491 @@ static void* WINAPI Allocate_CoTaskMemAlloc(void *this, ULONG size) return CoTaskMemAlloc(size); }
-/* FIXME: there isn't any checking whether the read property extends past the - * end of the buffer. +static HRESULT ConvertCodepageSize(const char *str, DWORD cb, DWORD *cbSize, + UINT codepageFrom, UINT codepageTo, BOOL wcharByteSwap) +{ + int unicodeCharCount; + int narrowCharCount; + WCHAR *unicodeString; + + *cbSize = 0; + + if (codepageFrom == CP_UNICODE && cb % 2) + { + WARN("Unicode string has odd number of bytes\n"); + return STG_E_INVALIDPARAMETER; + } + + /* look for a shortcut */ + if (codepageFrom == codepageTo) + { + *cbSize = cb; + return S_OK; + } + + /* look for another shortcut */ + if (codepageTo == CP_UNICODE) + { + unicodeCharCount = MultiByteToWideChar(codepageFrom, + 0, + str, + cb, + NULL, + 0); + if (unicodeCharCount < 1) + return STG_E_INVALIDPARAMETER; + + *cbSize = unicodeCharCount * sizeof(WCHAR); + return S_OK; + } + + /* convert to unicode */ + if (codepageFrom == CP_UNICODE) + { + unicodeString = CoTaskMemAlloc(cb); + if (!unicodeString) + return STG_E_INSUFFICIENTMEMORY; + + memcpy(unicodeString, str, cb); + + if (wcharByteSwap) + { + PropertyStorage_ByteSwapString(unicodeString, cb/2); + } + + unicodeCharCount = (int)cb / 2; + } + else + { + unicodeCharCount = MultiByteToWideChar(codepageFrom, + 0, + str, + cb, + NULL, + 0); + if (unicodeCharCount < 1) + return STG_E_INVALIDPARAMETER; + + unicodeString = CoTaskMemAlloc(unicodeCharCount * sizeof(WCHAR)); + if (!unicodeString) + return STG_E_INSUFFICIENTMEMORY; + + MultiByteToWideChar(codepageFrom, + 0, + str, + cb, + unicodeString, + unicodeCharCount); + } + + /* attempt conversion to the target codepage */ + narrowCharCount = WideCharToMultiByte(codepageTo, + 0, + unicodeString, + unicodeCharCount, + NULL, + 0, + NULL, + NULL); + + CoTaskMemFree(unicodeString); + if (narrowCharCount < 1) + return STG_E_INVALIDPARAMETER; + + *cbSize = narrowCharCount; + return S_OK; +} + +static HRESULT ConvertCodepage(const char *str, DWORD cbStr, + char *strOut, DWORD cbBuf, + UINT codepageFrom, UINT codepageTo, BOOL wcharByteSwap) +{ + int unicodeCharCount; + int narrowCharCount; + WCHAR *unicodeString; + + TRACE("(%p %u %p %u %u %u %d)\n", str, cbStr, strOut, cbBuf, codepageFrom, codepageTo, (int)wcharByteSwap); + + if (codepageFrom == CP_UNICODE && cbStr % 2) + { + WARN("Unicode string has odd number of bytes\n"); + return STG_E_INVALIDPARAMETER; + } + + /* look for a shortcut */ + if (codepageFrom == codepageTo) + { + if (cbBuf < cbStr) + return STG_E_INSUFFICIENTMEMORY; + + memcpy(strOut, str, cbStr); + + if (codepageTo == CP_UNICODE && wcharByteSwap) + { + PropertyStorage_ByteSwapString(strOut, cbStr/2); + } + + return S_OK; + } + + /* look for another shortcut */ + if (codepageTo == CP_UNICODE) + { + unicodeCharCount = MultiByteToWideChar(codepageFrom, + 0, + str, + cbStr, + NULL, + 0); + + if (unicodeCharCount < 1 || unicodeCharCount > cbBuf) + return STG_E_INVALIDPARAMETER; + + MultiByteToWideChar(codepageFrom, + 0, + str, + cbStr, + (LPWSTR)strOut, + unicodeCharCount); + return S_OK; + } + + /* convert to unicode */ + if (codepageFrom == CP_UNICODE) + { + unicodeString = CoTaskMemAlloc(cbStr); + if (!unicodeString) + return STG_E_INSUFFICIENTMEMORY; + + memcpy(unicodeString, str, cbStr); + unicodeCharCount = (int)cbStr / 2; + + if (wcharByteSwap) + { + PropertyStorage_ByteSwapString(unicodeString, unicodeCharCount); + } + } + else + { + unicodeCharCount = MultiByteToWideChar(codepageFrom, + 0, + str, + cbStr, + NULL, + 0); + if (unicodeCharCount < 1) + return STG_E_INVALIDPARAMETER; + + unicodeString = CoTaskMemAlloc(unicodeCharCount * sizeof(WCHAR)); + if (!unicodeString) + return STG_E_INSUFFICIENTMEMORY; + + MultiByteToWideChar(codepageFrom, + 0, + str, + cbStr, + unicodeString, + unicodeCharCount); + } + + /* attempt conversion to the target codepage */ + narrowCharCount = WideCharToMultiByte(codepageTo, + 0, + unicodeString, + unicodeCharCount, + NULL, + 0, + NULL, + NULL); + + if (narrowCharCount < 1 || narrowCharCount > cbBuf) + { + CoTaskMemFree(unicodeString); + return STG_E_INVALIDPARAMETER; + } + + WideCharToMultiByte(codepageTo, + 0, + unicodeString, + unicodeCharCount, + strOut, + narrowCharCount, + NULL, + NULL); + + return S_OK; +} + +/* HACK to hide (ULONG)(something) < 0 from the analyzer */ +static inline int gt(ULONG a, ULONG b) { return a > b; } + +/* wine_ReadProperty [OLE32.@] + * + * Base implementation for deserializing properties. + * Used by: + * - IPropertyStorage + * - StgConvertPropertyToVariant + * - StgDeserializeProperty [propsys.dll] + * + * This handles all straightforward cases, only crazy stuff like indirect + * properties have to be handled by the caller itself. + * + * The only sane allocator you could pass is NULL, in which case CoTaskMemAlloc + * is used. The allocator parameter just there to make the brain-damaged + * interface of StgConvertPropertyToVariant work. + * + * NOTICE: In case the reading fails, a partially initialized PROPVARIANT may be + * returned that has to be freed (using PropVariantClear, unless you + * used a custom allocator, in which case you basically have to + * reimplement PropVariantClear). + * TODO: Array handling + */ +HRESULT WINAPI wine_ReadProperty(PROPVARIANT *prop, const BYTE *data, + ULONG cbData, ULONG *pcbDataUsed, UINT diskCodepage, UINT runningCodepage, + void* (WINAPI *allocate)(void *this, ULONG size), void *allocate_data) +{ + HRESULT hr = S_OK; + ULONG cbData_Start; + + cbData_Start = cbData; + if (pcbDataUsed) + *pcbDataUsed = 0; + +#define FAIL_IF_EOF(cbytes) \ + do { \ + if ((ULONG)cbytes > cbData) \ + return STG_E_INVALIDPARAMETER; \ + } while (0) +#define MOVE_FORWARD(bytes) \ + do { \ + FAIL_IF_EOF(bytes); \ + data += bytes; \ + cbData -= bytes; \ + } while (0) +#define READ_WORD_AND_MOVE(location) \ + do { \ + FAIL_IF_EOF(2); \ + StorageUtl_ReadWord(data, 0, (location)); \ + MOVE_FORWARD(2); \ + } while (0) +#define READ_DWORD_AND_MOVE(location) \ + do { \ + FAIL_IF_EOF(4); \ + StorageUtl_ReadDWord(data, 0, (location)); \ + MOVE_FORWARD(4); \ + } while (0) +#define ALLOCATE_OR_FAIL(pptr, cbEl, nEl) \ + do { \ + *(pptr) = allocate(allocate_data, (cbEl) * (nEl)); \ + if (!*(pptr)) \ + { \ + return STG_E_INSUFFICIENTMEMORY; \ + } \ + } while(0) + +#define FOR_POSSIBLE_VECTOR(ctype, singleMember, vectorMember, cbBlock) \ + ctype *vec; \ + DWORD vecsize; \ + if (!(prop->vt & VT_VECTOR)) \ + { \ + vecsize = 1; \ + vec = &prop->u.singleMember; \ + } \ + else \ + { \ + READ_DWORD_AND_MOVE(&vecsize); \ + prop->u.vectorMember.cElems = vecsize; \ + ALLOCATE_OR_FAIL(&prop->u.vectorMember.pElems, sizeof(ctype), vecsize); \ + vec = prop->u.vectorMember.pElems; \ + ZeroMemory(vec, sizeof(ctype) * vecsize); \ + } \ + for (; vecsize; --vecsize, ++vec, data += (cbBlock), cbData -= (cbBlock)) \ + if (gt((ULONG)(cbBlock), cbData)) \ + return STG_E_INVALIDPARAMETER; \ + else +#define FOR_POSSIBLE_VECTOR_OR_SINGLEP(ctype, singleMemberP, vectorMember, cbBlock) \ + ctype *vec; \ + DWORD vecsize; \ + if (!(prop->vt & VT_VECTOR)) \ + { \ + vecsize = 1; \ + ALLOCATE_OR_FAIL(&prop->u.singleMemberP, sizeof(ctype), 1); \ + vec = prop->u.singleMemberP; \ + } \ + else \ + { \ + READ_DWORD_AND_MOVE(&vecsize); \ + prop->u.vectorMember.cElems = vecsize; \ + ALLOCATE_OR_FAIL(&prop->u.vectorMember.pElems, sizeof(ctype), vecsize); \ + vec = prop->u.vectorMember.pElems; \ + ZeroMemory(vec, sizeof(ctype) * vecsize); \ + } \ + for (; vecsize; --vecsize, ++vec, data += (cbBlock), cbData -= (cbBlock)) \ + if (gt((ULONG)(cbBlock), cbData)) \ + return STG_E_INVALIDPARAMETER; \ + else +#define CASE_OR_VECTOR(vtype) \ + case vtype: \ + case VT_VECTOR|vtype: + + if (!allocate) + allocate = &Allocate_CoTaskMemAlloc; + + assert(prop); + assert(data); + + ZeroMemory(prop, sizeof(*prop)); + + READ_DWORD_AND_MOVE((DWORD *)&prop->vt); + + switch (prop->vt) + { + case VT_EMPTY: + case VT_NULL: + break; + CASE_OR_VECTOR(VT_I1) + { + FOR_POSSIBLE_VECTOR(char, cVal, cac, 1) + { + *vec = *(const char *)data; + TRACE("Read char 0x%x ('%c')\n", vec[0], vec[0]); + } + break; + } + CASE_OR_VECTOR(VT_UI1) + { + FOR_POSSIBLE_VECTOR(BYTE, bVal, caub, 1) + { + *vec = *data; + TRACE("Read byte 0x%x\n", *vec); + } + break; + } + CASE_OR_VECTOR(VT_I2) + { + FOR_POSSIBLE_VECTOR(SHORT, iVal, cai, 2) + { + StorageUtl_ReadWord(data, 0, (WORD*)vec); + TRACE("Read short %d\n", (int)*vec); + } + break; + } + CASE_OR_VECTOR(VT_UI2) + CASE_OR_VECTOR(VT_BOOL) /* VARIANT_BOOL == USHORT */ + { + FOR_POSSIBLE_VECTOR(USHORT, uiVal, caui, 2) + { + StorageUtl_ReadWord(data, 0, vec); + TRACE("Read ushort %d\n", (int)*vec); + } + break; + } + CASE_OR_VECTOR(VT_INT) + CASE_OR_VECTOR(VT_I4) + CASE_OR_VECTOR(VT_ERROR) /* SCODE == LONG */ + { + FOR_POSSIBLE_VECTOR(LONG, lVal, cal, 4) + { + StorageUtl_ReadDWord(data, 0, (DWORD*)vec); + TRACE("Read long %u\n", (unsigned)*vec); + } + break; + } + CASE_OR_VECTOR(VT_UINT) + CASE_OR_VECTOR(VT_UI4) + { + FOR_POSSIBLE_VECTOR(ULONG, ulVal, caul, 4) + { + StorageUtl_ReadDWord(data, 0, vec); + TRACE("Read ulong %d\n", (int)*vec); + } + break; + } + CASE_OR_VECTOR(VT_I8) + { + FOR_POSSIBLE_VECTOR(LARGE_INTEGER, hVal, cah, 8) + { + StorageUtl_ReadULargeInteger(data, 0, (ULARGE_INTEGER*)vec); + TRACE("Read longlong 0x%08x%08x\n", (unsigned)vec->HighPart, (unsigned)vec->LowPart); + } + break; + } + CASE_OR_VECTOR(VT_UI8) + { + FOR_POSSIBLE_VECTOR(ULARGE_INTEGER, uhVal, cauh, 8) + { + StorageUtl_ReadULargeInteger(data, 0, vec); + TRACE("Read ulonglong 0x%08x%08x\n", (unsigned)vec->HighPart, (unsigned)vec->LowPart); + } + break; + } + CASE_OR_VECTOR(VT_LPSTR) + { + FOR_POSSIBLE_VECTOR(LPSTR, pszVal, calpstr, 0) + { + DWORD count; + DWORD convertedCount; + + READ_DWORD_AND_MOVE(&count); + if (diskCodepage == CP_UNICODE && count % 2) + { + WARN("Unicode string has odd number of bytes\n"); + return STG_E_INVALIDHEADER; + } + + FAIL_IF_EOF(count); + + hr = ConvertCodepageSize((const char*)data, + count, + &convertedCount, + diskCodepage, + runningCodepage, + TRUE); + if (FAILED(hr)) + return hr; + + ALLOCATE_OR_FAIL(vec, 1, convertedCount); + hr = ConvertCodepage((const char *)data, count, + *vec, convertedCount, + diskCodepage, + runningCodepage, + TRUE); + + MOVE_FORWARD(count); + MOVE_FORWARD((4 - (count%4)) % 4); /* padding */ + + /* Ensure NULL termination */ + if (runningCodepage == CP_UNICODE) + { + ((WCHAR*)*vec)[convertedCount / sizeof(WCHAR) - 1] = '\0'; + TRACE("Read string value %s\n", + debugstr_w((PCWSTR)*vec)); + } + else + { + (*vec)[convertedCount - 1] = '\0'; + TRACE("Read string value %s\n", debugstr_a(*vec)); + } + } + break; + } + CASE_OR_VECTOR(VT_BSTR) + { + FOR_POSSIBLE_VECTOR(BSTR, bstrVal, cabstr, 0) + { + DWORD count, wcount; + + READ_DWORD_AND_MOVE(&count); + if (diskCodepage == CP_UNICODE && count % 2) + { + WARN("Unicode string has odd number of bytes\n"); + return STG_E_INVALIDHEADER; + } + else + { + FAIL_IF_EOF(count); + + if (diskCodepage == CP_UNICODE) + wcount = count / 2; + else + wcount = MultiByteToWideChar(diskCodepage, 0, (LPCSTR)data, count, NULL, 0); + + *vec = SysAllocStringLen(NULL, wcount); /* FIXME: use allocator? */ + + if (*vec) + { + if (diskCodepage == CP_UNICODE) + { + memcpy(*vec, data, count); + PropertyStorage_ByteSwapString(*vec, wcount); + } + else + MultiByteToWideChar(diskCodepage, 0, (LPCSTR)data, count, *vec, wcount); + + (*vec)[wcount - 1] = '\0'; + TRACE("Read string value %s\n", debugstr_w(*vec)); + } + else + { + return STG_E_INSUFFICIENTMEMORY; + } + } + + MOVE_FORWARD(count); + MOVE_FORWARD((4 - (count%4)) % 4); /* padding */ + } + break; + } + case VT_BLOB: + { + DWORD count; + + READ_DWORD_AND_MOVE(&count); + FAIL_IF_EOF(count); + prop->u.blob.cbSize = count; + prop->u.blob.pBlobData = allocate(allocate_data, count); + if (prop->u.blob.pBlobData) + { + memcpy(prop->u.blob.pBlobData, data, count); + TRACE("Read blob value of size %d\n", count); + + MOVE_FORWARD(count); + MOVE_FORWARD((4 - (count % 4)) % 4); /* padding to 4 bytes */ + } + else + return STG_E_INSUFFICIENTMEMORY; + break; + } + CASE_OR_VECTOR(VT_LPWSTR) + { + FOR_POSSIBLE_VECTOR(LPWSTR, pwszVal, calpwstr, 0) + { + DWORD count; + + READ_DWORD_AND_MOVE(&count); + FAIL_IF_EOF(count * sizeof(WCHAR)); + ALLOCATE_OR_FAIL(vec, sizeof(WCHAR), count); + + memcpy(*vec, data, count * sizeof(WCHAR)); + + /* make sure string is NULL-terminated */ + (*vec)[count - 1] = '\0'; + PropertyStorage_ByteSwapString(*vec, count); + TRACE("Read string value %s\n", debugstr_w(*vec)); + + MOVE_FORWARD(count * sizeof(WCHAR)); + if (count % 2) + MOVE_FORWARD(sizeof(WCHAR)); /* padding to 4 bytes */ + } + break; + } + CASE_OR_VECTOR(VT_FILETIME) + { + ULARGE_INTEGER tmp; + FOR_POSSIBLE_VECTOR(FILETIME, filetime, cafiletime, 8) + { + StorageUtl_ReadULargeInteger(data, 0, &tmp); + + vec->dwLowDateTime = tmp.LowPart; + vec->dwHighDateTime = tmp.HighPart; + } + break; + } + CASE_OR_VECTOR(VT_CY) + { + ULARGE_INTEGER tmp; + FOR_POSSIBLE_VECTOR(CY, cyVal, cacy, 8) + { + StorageUtl_ReadULargeInteger(data, 0, &tmp); + vec->int64 = (LONGLONG)tmp.QuadPart; + } + break; + } + CASE_OR_VECTOR(VT_CF) + { + FOR_POSSIBLE_VECTOR_OR_SINGLEP(CLIPDATA, pclipdata, caclipdata, 0) + { + DWORD len = 0, tag = 0; + + READ_DWORD_AND_MOVE(&len); + FAIL_IF_EOF(len); + READ_DWORD_AND_MOVE(&tag); + if (len > 4) + { + vec->cbSize = len; + vec->ulClipFmt = tag; + + ALLOCATE_OR_FAIL(&vec->pClipData, 1, len - 4); + + memcpy(vec->pClipData, data, len - 4); + MOVE_FORWARD(len - 4); + MOVE_FORWARD((4 - (len % 4)) % 4); /* padding */ + } + else + return STG_E_INVALIDPARAMETER; + } + break; + } + CASE_OR_VECTOR(VT_DATE) + CASE_OR_VECTOR(VT_R8) + { + FOR_POSSIBLE_VECTOR(double, dblVal, cadbl, sizeof(double)) + { + memcpy(vec, data, sizeof(double)); + } + break; + } + CASE_OR_VECTOR(VT_R4) + { + FOR_POSSIBLE_VECTOR(float, fltVal, caflt, sizeof(float)) + { + memcpy(vec, data, sizeof(double)); + } + break; + } + case VT_DECIMAL: + { + DECIMAL *dec; + + FAIL_IF_EOF(16); + + dec = (DECIMAL*)prop; + dec->u.scale = data[2]; + dec->u.sign = data[3]; + StorageUtl_ReadDWord(data, 4, &dec->Hi32); + StorageUtl_ReadULargeInteger(data, 8, (ULARGE_INTEGER*)&dec->u1); + + MOVE_FORWARD(16); + break; + } + CASE_OR_VECTOR(VT_CLSID) + { + FOR_POSSIBLE_VECTOR_OR_SINGLEP(GUID, puuid, cauuid, 16) + { + StorageUtl_ReadGUID(data, 0, vec); + } + break; + } + case VT_VECTOR|VT_VARIANT: + { + FOR_POSSIBLE_VECTOR_OR_SINGLEP(PROPVARIANT, /* dummy */ pvarVal, capropvar, 0) + { + ULONG cbDataUsed; + hr = wine_ReadProperty(vec, data, cbData, &cbDataUsed, + diskCodepage, runningCodepage, allocate, allocate_data); + if (FAILED(hr)) + { + return hr; + } + + data += cbDataUsed; + cbData -= cbDataUsed; + } + break; + } + default: + FIXME("unsupported type %d\n", prop->vt); + return STG_E_INVALIDPARAMETER; + } + + /* Padding to 4 bytes, even for arrays of smaller integers */ + /* This is important for handling VT_VECTOR|VT_VARIANT */ + if (pcbDataUsed) { + ULONG paddingMissing; + + paddingMissing = (4 - ((cbData_Start - cbData) % 4)) % 4; + + if (cbData >= paddingMissing) + cbData -= paddingMissing; + else + cbData = 0; + + *pcbDataUsed = cbData_Start - cbData; + } + +#undef FAIL_IF_EOF +#undef READ_DWORD_AND_MOVE +#undef READ_WORD_AND_MOVE +#undef MOVE_FORWARD +#undef FOR_POSSIBLE_VECTOR +#undef FOR_POSSIBLE_VECTOR_OR_SINGLEP +#undef ALLOCATE_OR_FAIL +#undef CASE_OR_VECTOR + + return hr; +} + +/* + * Converts VT_BYREF properties to their non-ref counterparts + * + * NOTICE: This does not copy arrays, vectors etc., these are shared + * by the input and output properties. Therefore, only one may be + * eventually freed. + */ +static HRESULT ScrapeByRefOfProperty(const PROPVARIANT *in, PROPVARIANT *out) +{ + if (!(in->vt & VT_BYREF)) + { + /* no ref, no problem */ + memcpy(out, in, sizeof(*in)); + return S_OK; + } + + out->vt = in->vt & ~VT_BYREF; + + /* BYREF properties all carry a pointer, which must not be NULL here */ + /* It doesn't matter which one we check */ + if (!in->u.pcVal) + return STG_E_INVALIDPARAMETER; + + if (out->vt & VT_ARRAY) + { + out->u.parray = *in->u.pparray; + return S_OK; + } + + switch (out->vt) + { + case VT_I1: + out->u.cVal = *in->u.pcVal; + break; + case VT_UI1: + out->u.bVal = *in->u.pbVal; + break; + case VT_I2: + out->u.iVal = *in->u.piVal; + break; + case VT_UI2: + out->u.uiVal = *in->u.puiVal; + break; + case VT_I4: + case VT_INT: + out->u.lVal = *in->u.plVal; + break; + case VT_UI4: + case VT_UINT: + out->u.ulVal = *in->u.pulVal; + break; + case VT_R4: + out->u.fltVal = *in->u.pfltVal; + break; + case VT_R8: + out->u.dblVal = *in->u.pdblVal; + break; + case VT_BOOL: + out->u.boolVal = *in->u.pboolVal; + break; + case VT_DECIMAL: + memcpy(out, in->u.pdecVal, sizeof(DECIMAL)); + out->vt = VT_DECIMAL; + break; + case VT_ERROR: + out->u.scode = *in->u.pscode; + break; + case VT_CY: + memcpy(&out->u.cyVal, in->u.pcyVal, sizeof(CY)); + break; + case VT_DATE: + memcpy(&out->u.date, in->u.pdate, sizeof(DATE)); + break; + case VT_BSTR: + out->u.bstrVal = *in->u.pbstrVal; + break; + case VT_UNKNOWN: + out->u.punkVal = *in->u.ppunkVal; + break; + case VT_DISPATCH: + out->u.pdispVal = *in->u.ppdispVal; + break; + default: + return STG_E_INVALIDPARAMETER; + } + + return S_OK; +} + +/* wine_SerializedPropertySize [OLE32.@] + * + * Returns the buffer size needed to serialize the given property. + * + * This handles all straightforward cases, only crazy stuff like indirect + * properties have to be handled by the caller itself. + * + * TODO: Array handling + */ +HRESULT WINAPI wine_SerializedPropertySize(const PROPVARIANT *in_prop, + ULONG *pcbSize, UINT diskCodepage, UINT runningCodepage) +{ + ULONG size = 4; /* the header */ + HRESULT hr; + PROPVARIANT prop; + + TRACE("(%p %p %u %u)\n", in_prop, pcbSize, diskCodepage, runningCodepage); + + if (!in_prop) + return E_INVALIDARG; + if (!pcbSize) + return E_INVALIDARG; + + *pcbSize = 0; + + hr = ScrapeByRefOfProperty(in_prop, &prop); + if (FAILED(hr)) + return hr; + + switch (prop.vt) + { + case VT_EMPTY: + case VT_NULL: + break; + case VT_I1: + case VT_UI1: + size += 1; + break; + case VT_VECTOR|VT_I1: + case VT_VECTOR|VT_UI1: + size += 4; /* Vector Header */ + size += prop.u.cac.cElems * 1; + break; + case VT_I2: + case VT_UI2: + case VT_BOOL: + size += 2; + break; + case VT_VECTOR|VT_I2: + case VT_VECTOR|VT_UI2: + case VT_VECTOR|VT_BOOL: + size += 4; /* Vector Header */ + size += prop.u.cai.cElems * 2; + break; + case VT_UI4: + case VT_I4: + case VT_INT: + case VT_UINT: + case VT_ERROR: + size += 4; + break; + case VT_VECTOR|VT_UI4: + case VT_VECTOR|VT_I4: + case VT_VECTOR|VT_INT: + case VT_VECTOR|VT_UINT: + case VT_VECTOR|VT_ERROR: + size += 4; /* Vector Header */ + size += prop.u.cal.cElems * 4; + break; + case VT_I8: + case VT_UI8: + size += 8; + break; + case VT_VECTOR|VT_I8: + case VT_VECTOR|VT_UI8: + size += 4; /* Vector header */ + size += prop.u.cah.cElems * 8; + break; + case VT_LPSTR: + { + ULONG len; + ULONG convertedLen; + + size += 4; /* DWORD byte count */ + + if (runningCodepage == CP_UNICODE) + len = lstrlenW(prop.u.pwszVal) * sizeof(WCHAR) + 2; + else + len = lstrlenA(prop.u.pszVal) + 1; + + hr = ConvertCodepageSize(prop.u.pszVal, + len, + &convertedLen, + runningCodepage, + diskCodepage, FALSE); + if (FAILED(hr)) + return hr; + + size += convertedLen; + + break; + } + case VT_VECTOR|VT_LPSTR: + { + ULONG i; + + size += 4; /* Vector Header */ + for (i = 0; i < prop.u.calpstr.cElems; ++i) + { + ULONG len; + ULONG convertedLen; + + size += 4; /* DWORD byte count */ + + if (runningCodepage == CP_UNICODE) + len = lstrlenW((LPCWSTR)prop.u.calpstr.pElems[i]) * sizeof(WCHAR) + sizeof(WCHAR); + else + len = lstrlenA(prop.u.calpstr.pElems[i]) + 1; + + hr = ConvertCodepageSize(prop.u.calpstr.pElems[i], + len, + &convertedLen, + runningCodepage, + diskCodepage, FALSE); + if (FAILED(hr)) + return hr; + + size += convertedLen; + + size += (4 - (len % 4)) % 4; /* padding */ + } + break; + } + case VT_BSTR: + { + /* bstrs are possibly saved as ansi string */ + if (diskCodepage == CP_UNICODE) + { + ULONG len = 4; /* DWORD byte count */ + len += SysStringLen(prop.u.bstrVal) * sizeof(WCHAR) + sizeof(WCHAR); + size += len; + } + else + { + UINT cw = SysStringLen(prop.u.bstrVal); + + size += 4; /* DWORD byte count */ + size += WideCharToMultiByte(diskCodepage, 0, prop.u.bstrVal, cw, NULL, 0, NULL, NULL); + size += 1; /* terminating null */ + } + + break; + } + case VT_VECTOR|VT_BSTR: + { + ULONG i; + size += 4; /* Vector Header */ + for (i = 0; i < prop.u.cabstr.cElems; ++i) + { + ULONG len = 4; /* DWORD byte count */ + + /* bstrs are possibly saved as ansi string */ + if (diskCodepage == CP_UNICODE) + { + len += SysStringLen(prop.u.cabstr.pElems[i]) * sizeof(WCHAR) + sizeof(WCHAR); + } + else + { + UINT cw = SysStringLen(prop.u.cabstr.pElems[i]); + + len += WideCharToMultiByte(diskCodepage, 0, + prop.u.cabstr.pElems[i], cw, NULL, 0, NULL, NULL); + len += 1; /* terminating null */ + } + + len += (4 - (len % 4)) % 4; /* padding */ + size += len; + } + break; + } + case VT_BLOB: + size += 4; /* DWORD byte count */ + size += prop.u.blob.cbSize; + break; + case VT_LPWSTR: + size += 4; /* DWORD char count */ + size += (lstrlenW(prop.u.pwszVal) + 1) * sizeof(WCHAR); + break; + case VT_VECTOR|VT_LPWSTR: + { + ULONG i; + size += 4; /* Vector header */ + for (i = 0; i < prop.u.calpwstr.cElems; ++i) + { + ULONG len = 4; /* DWORD char count */ + len += (lstrlenW(prop.u.calpwstr.pElems[i]) + 1) * sizeof(WCHAR); + len += (4 - (len % 4)) % 4; /* padding */ + + size += len; + } + break; + } + case VT_FILETIME: + case VT_CY: + size += 8; /* 64bit integer */ + break; + case VT_VECTOR|VT_FILETIME: + case VT_VECTOR|VT_CY: + size += 4; /* Vector header */ + size += prop.u.cafiletime.cElems * 8; + break; + case VT_DATE: + case VT_R8: + size += 8; /* double */ + break; + case VT_VECTOR|VT_DATE: + case VT_VECTOR|VT_R8: + size += 4; /* Vector header */ + size += prop.u.cadbl.cElems * 8; + break; + case VT_R4: + size += 4; /* float */ + break; + case VT_VECTOR|VT_R4: + size += 4; /* Vector header */ + size += prop.u.caflt.cElems * 4; + break; + case VT_DECIMAL: + size += 16; + break; + case VT_CLSID: + size += sizeof(GUID); + break; + case VT_VECTOR|VT_CLSID: + size += 4; /* Vector header */ + size += prop.u.cauuid.cElems * sizeof(GUID); + break; + case VT_CF: + size += 4; /* size field */ + size += prop.u.pclipdata->cbSize; /* includes tag field */ + break; + case VT_VECTOR|VT_CF: + { + ULONG i; + + size += 4; /* Vector header */ + for (i = 0; i < prop.u.caclipdata.cElems; ++i) + { + ULONG len = 4; /* DWORD size field */ + len += prop.u.caclipdata.pElems[i].cbSize; /* includes tag field */ + len += (4 - (len % 4)) % 4; /* padding */ + + size += len; + } + break; + } + case VT_VECTOR|VT_VARIANT: + { + HRESULT hr; + ULONG vsize; + ULONG i; + + size += 4; /* Vector header */ + for (i = 0; i < prop.u.capropvar.cElems; ++i) + { + hr = wine_SerializedPropertySize(&prop.u.capropvar.pElems[i], + &vsize, + diskCodepage, runningCodepage); + if (FAILED(hr)) + return hr; + + size += vsize; + size += (4 - (vsize % 4)) % 4; + } + break; + } + default: + FIXME("unsupported type %d\n", prop.vt); + return STG_E_INVALIDPARAMETER; + } + + /* always pad to 4 bytes */ + size += (4 - (size % 4)) % 4; + *pcbSize = size; + + return S_OK; +} + +/* wine_WritePropertyToBuffer [OLE32.@] + * + * Base implementation for serializing properties. + * Used by: + * - IPropertyStorage + * - StgConvertVariantToProperty + * - StgSerializeProperty [propsys.dll] + * + * This handles all straightforward cases, only crazy stuff like indirect + * properties have to be handled by the caller itself. + * + * TODO: Array handling */ -static HRESULT PropertyStorage_ReadProperty(PROPVARIANT *prop, const BYTE *data, - UINT codepage, void* (WINAPI *allocate)(void *this, ULONG size), void *allocate_data) +HRESULT WINAPI wine_WritePropertyToBuffer(const PROPVARIANT *in_prop, + BYTE *buffer, + ULONG cbBufSize, + ULONG *pcbBufSizeUsed, + UINT diskCodepage, + UINT runningCodepage) { HRESULT hr = S_OK; + ULONG cbBufSize_Start; + PROPVARIANT prop;
- assert(prop); - assert(data); - StorageUtl_ReadDWord(data, 0, (DWORD *)&prop->vt); - data += sizeof(DWORD); - switch (prop->vt) + TRACE("(%p %p %u %p %u %u)\n", in_prop, buffer, cbBufSize, + pcbBufSizeUsed, diskCodepage, runningCodepage); + + cbBufSize_Start = cbBufSize; + + if (pcbBufSizeUsed) + *pcbBufSizeUsed = 0; + + hr = ScrapeByRefOfProperty(in_prop, &prop); + if (FAILED(hr)) + return hr; + +#define FAIL_IF_EOF(cbytes) \ + do { \ + if ((ULONG)cbytes > cbBufSize) \ + return STG_E_INVALIDPARAMETER; \ + } while (0) +#define MOVE_FORWARD(bytes) \ + do { \ + FAIL_IF_EOF(bytes); \ + buffer += bytes; \ + cbBufSize -= bytes; \ + } while (0) +#define WRITE_WORD_AND_MOVE(value) \ + do { \ + FAIL_IF_EOF(2); \ + StorageUtl_WriteWord(buffer, 0, (value)); \ + MOVE_FORWARD(2); \ + } while (0) +#define WRITE_DWORD_AND_MOVE(value) \ + do { \ + FAIL_IF_EOF(4); \ + StorageUtl_WriteDWord(buffer, 0, (value)); \ + MOVE_FORWARD(4); \ + } while (0) +#define PAD_TO_MUL_AND_MOVE(cbLen, cbMul) \ + do { \ + FAIL_IF_EOF(((cbMul) - ((cbLen) % (cbMul))) % (cbMul)); \ + ZeroMemory(buffer, ((cbMul) - ((cbLen) % (cbMul))) % (cbMul)); \ + MOVE_FORWARD(((cbMul) - ((cbLen) % (cbMul))) % (cbMul)); \ + } while(0) +#define FOR_POSSIBLE_VECTOR(ctype, singleMember, vectorMember, cbBlock) \ + const ctype *vec; \ + DWORD vecsize; \ + if (!(prop.vt & VT_VECTOR)) \ + { \ + vecsize = 1; \ + vec = &prop.u.singleMember; \ + } \ + else \ + { \ + vecsize = prop.u.vectorMember.cElems; \ + WRITE_DWORD_AND_MOVE(vecsize); \ + vec = prop.u.vectorMember.pElems; \ + } \ + for (; vecsize; --vecsize, ++vec, buffer += (cbBlock), cbBufSize -= (cbBlock)) \ + if (gt((ULONG)(cbBlock), cbBufSize)) \ + return STG_E_INVALIDPARAMETER; \ + else + +#define FOR_POSSIBLE_VECTOR_OR_SINGLEP(ctype, singleMemberP, vectorMember, cbBlock) \ + const ctype *vec; \ + DWORD vecsize; \ + if (!(prop.vt & VT_VECTOR)) \ + { \ + vecsize = 1; \ + vec = prop.u.singleMemberP; \ + } \ + else \ + { \ + vecsize = prop.u.vectorMember.cElems; \ + WRITE_DWORD_AND_MOVE(vecsize); \ + vec = prop.u.vectorMember.pElems; \ + } \ + for (; vecsize; --vecsize, ++vec, buffer += (cbBlock), cbBufSize -= (cbBlock)) \ + if (gt((ULONG)(cbBlock), cbBufSize)) \ + return STG_E_INVALIDPARAMETER; \ + else + +#define CASE_OR_VECTOR(vtype) \ + case vtype: \ + case VT_VECTOR|vtype: + + assert(buffer); + + TRACE("Serializing variant of type %d to buffer size %lu\n", prop.vt, (unsigned long)cbBufSize); + + /* variant type */ + WRITE_DWORD_AND_MOVE((DWORD)prop.vt); + + switch (prop.vt) { case VT_EMPTY: case VT_NULL: break; - case VT_I1: - prop->u.cVal = *(const char *)data; - TRACE("Read char 0x%x ('%c')\n", prop->u.cVal, prop->u.cVal); - break; - case VT_UI1: - prop->u.bVal = *data; - TRACE("Read byte 0x%x\n", prop->u.bVal); + CASE_OR_VECTOR(VT_I1) + { + FOR_POSSIBLE_VECTOR(char, cVal, cac, 1) + { + *(char *)buffer = *vec; + TRACE("Write char 0x%x ('%c')\n", vec[0], vec[0]); + } break; - case VT_I2: - StorageUtl_ReadWord(data, 0, (WORD*)&prop->u.iVal); - TRACE("Read short %d\n", prop->u.iVal); + } + CASE_OR_VECTOR(VT_UI1) + { + FOR_POSSIBLE_VECTOR(BYTE, bVal, caub, 1) + { + *buffer = *vec; + TRACE("Write byte 0x%x\n", *vec); + } break; - case VT_UI2: - StorageUtl_ReadWord(data, 0, &prop->u.uiVal); - TRACE("Read ushort %d\n", prop->u.uiVal); + } + CASE_OR_VECTOR(VT_I2) + { + FOR_POSSIBLE_VECTOR(SHORT, iVal, cai, 2) + { + StorageUtl_WriteWord(buffer, 0, *(WORD*)vec); + TRACE("Write short %d\n", (int)*vec); + } break; - case VT_INT: - case VT_I4: - StorageUtl_ReadDWord(data, 0, (DWORD*)&prop->u.lVal); - TRACE("Read long %d\n", prop->u.lVal); + } + CASE_OR_VECTOR(VT_UI2) + CASE_OR_VECTOR(VT_BOOL) /* VARIANT_BOOL == USHORT */ + { + FOR_POSSIBLE_VECTOR(USHORT, uiVal, caui, 2) + { + StorageUtl_WriteWord(buffer, 0, *vec); + TRACE("Write ushort %d\n", (int)*vec); + } break; - case VT_UINT: - case VT_UI4: - StorageUtl_ReadDWord(data, 0, &prop->u.ulVal); - TRACE("Read ulong %d\n", prop->u.ulVal); + } + CASE_OR_VECTOR(VT_INT) + CASE_OR_VECTOR(VT_I4) + CASE_OR_VECTOR(VT_ERROR) /* SCODE == LONG */ + { + FOR_POSSIBLE_VECTOR(LONG, lVal, cal, 4) + { + StorageUtl_WriteDWord(buffer, 0, *(DWORD*)vec); + TRACE("Write long %d\n", *vec); + } break; - case VT_LPSTR: + } + CASE_OR_VECTOR(VT_UINT) + CASE_OR_VECTOR(VT_UI4) { - DWORD count; - - StorageUtl_ReadDWord(data, 0, &count); - if (codepage == CP_UNICODE && count % 2) + FOR_POSSIBLE_VECTOR(ULONG, ulVal, caul, 4) { - WARN("Unicode string has odd number of bytes\n"); - hr = STG_E_INVALIDHEADER; + StorageUtl_WriteDWord(buffer, 0, *vec); + TRACE("Write ulong %u\n", *vec); } - else + break; + } + CASE_OR_VECTOR(VT_I8) + { + FOR_POSSIBLE_VECTOR(LARGE_INTEGER, hVal, cah, 8) { - prop->u.pszVal = allocate(allocate_data, count); - if (prop->u.pszVal) - { - memcpy(prop->u.pszVal, data + sizeof(DWORD), count); - /* This is stored in the code page specified in codepage. - * Don't convert it, the caller will just store it as-is. - */ - if (codepage == CP_UNICODE) - { - /* Make sure it's NULL-terminated */ - prop->u.pszVal[count / sizeof(WCHAR) - 1] = '\0'; - TRACE("Read string value %s\n", - debugstr_w(prop->u.pwszVal)); - } - else - { - /* Make sure it's NULL-terminated */ - prop->u.pszVal[count - 1] = '\0'; - TRACE("Read string value %s\n", debugstr_a(prop->u.pszVal)); - } - } - else - hr = STG_E_INSUFFICIENTMEMORY; + StorageUtl_WriteULargeInteger(buffer, 0, (ULARGE_INTEGER*)vec); + TRACE("Write longlong 0x%lx%08lx\n", (unsigned long)vec->HighPart, (unsigned long)vec->LowPart); } break; } - case VT_BSTR: + CASE_OR_VECTOR(VT_UI8) { - DWORD count, wcount; - - StorageUtl_ReadDWord(data, 0, &count); - if (codepage == CP_UNICODE && count % 2) + FOR_POSSIBLE_VECTOR(ULARGE_INTEGER, uhVal, cauh, 8) { - WARN("Unicode string has odd number of bytes\n"); - hr = STG_E_INVALIDHEADER; + StorageUtl_WriteULargeInteger(buffer, 0, vec); + TRACE("Write ulonglong 0x%lx%08lx\n", (unsigned long)vec->HighPart, (unsigned long)vec->LowPart); } - else + break; + } + CASE_OR_VECTOR(VT_LPSTR) + { + FOR_POSSIBLE_VECTOR(LPSTR, pszVal, calpstr, 0) { - if (codepage == CP_UNICODE) - wcount = count / 2; + DWORD len; + DWORD convertedLen; + + if (runningCodepage == CP_UNICODE) + len = lstrlenW((LPCWSTR)*vec) * sizeof(WCHAR) + 2; else - wcount = MultiByteToWideChar(codepage, 0, (LPCSTR)(data + sizeof(DWORD)), count, NULL, 0); + len = lstrlenA(*vec) + 1; + + hr = ConvertCodepageSize(*vec, len, &convertedLen, + runningCodepage, diskCodepage, FALSE); + if (FAILED(hr)) + return hr; + + WRITE_DWORD_AND_MOVE(convertedLen); + FAIL_IF_EOF(convertedLen);
- prop->u.bstrVal = SysAllocStringLen(NULL, wcount); /* FIXME: use allocator? */ + hr = ConvertCodepage(*vec, len, + (char*)buffer, convertedLen, + runningCodepage, diskCodepage, FALSE); + if (FAILED(hr)) + return hr;
- if (prop->u.bstrVal) + if (diskCodepage == CP_UNICODE) { - if (codepage == CP_UNICODE) - memcpy(prop->u.bstrVal, data + sizeof(DWORD), count); - else - MultiByteToWideChar(codepage, 0, (LPCSTR)(data + sizeof(DWORD)), count, prop->u.bstrVal, wcount); + PropertyStorage_ByteSwapString(buffer, convertedLen/2); + TRACE("Write lpstr %s\n", debugstr_w((LPCWSTR)buffer)); + } + else + { + TRACE("Write lpstr %s\n", debugstr_a((char*)buffer)); + } + + MOVE_FORWARD(convertedLen); + PAD_TO_MUL_AND_MOVE(convertedLen, 4); + } + break; + } + CASE_OR_VECTOR(VT_BSTR) + { + FOR_POSSIBLE_VECTOR(BSTR, bstrVal, cabstr, 0) + { + /* BSTRs are saved in the codepage */ + DWORD len = SysStringLen(*vec) + 1; + + if (diskCodepage == CP_UNICODE) + { + WRITE_DWORD_AND_MOVE(len * sizeof(WCHAR));
- prop->u.bstrVal[wcount - 1] = '\0'; - TRACE("Read string value %s\n", debugstr_w(prop->u.bstrVal)); + FAIL_IF_EOF(len * sizeof(WCHAR)); + memcpy(buffer, *vec, len * sizeof(WCHAR)); + MOVE_FORWARD(len * sizeof(WCHAR)); + PAD_TO_MUL_AND_MOVE(len * sizeof(WCHAR), 4); } else - hr = STG_E_INSUFFICIENTMEMORY; + { + DWORD narrowLen; + + narrowLen = WideCharToMultiByte(diskCodepage, 0, *vec, len, NULL, 0, NULL, NULL); + + WRITE_DWORD_AND_MOVE(narrowLen); + + FAIL_IF_EOF(narrowLen); + WideCharToMultiByte(diskCodepage, 0, *vec, len, (char*)buffer, narrowLen, NULL, NULL); + MOVE_FORWARD(narrowLen); + PAD_TO_MUL_AND_MOVE(narrowLen, 4); + } + TRACE("Write BSTR %s\n", debugstr_w(*vec)); } break; } case VT_BLOB: { - DWORD count; + WRITE_DWORD_AND_MOVE(prop.u.blob.cbSize); + FAIL_IF_EOF(prop.u.blob.cbSize); + memcpy(buffer, prop.u.blob.pBlobData, prop.u.blob.cbSize); + MOVE_FORWARD(prop.u.blob.cbSize); + PAD_TO_MUL_AND_MOVE(prop.u.blob.cbSize, 4);
- StorageUtl_ReadDWord(data, 0, &count); - prop->u.blob.cbSize = count; - prop->u.blob.pBlobData = allocate(allocate_data, count); - if (prop->u.blob.pBlobData) + TRACE("Write blob value of size %d\n", prop.u.blob.cbSize); + break; + } + CASE_OR_VECTOR(VT_LPWSTR) + { + FOR_POSSIBLE_VECTOR(LPWSTR, pwszVal, calpwstr, 0) { - memcpy(prop->u.blob.pBlobData, data + sizeof(DWORD), count); - TRACE("Read blob value of size %d\n", count); + DWORD len; + + len = lstrlenW((LPCWSTR)*vec) + 1; + WRITE_DWORD_AND_MOVE(len); + + FAIL_IF_EOF(len * sizeof(WCHAR)); + memcpy(buffer, *vec, len * sizeof(WCHAR)); + MOVE_FORWARD(len * sizeof(WCHAR)); + PAD_TO_MUL_AND_MOVE(len * sizeof(WCHAR), 4); + TRACE("Write lpwstr %s\n", debugstr_w(*vec)); } - else - hr = STG_E_INSUFFICIENTMEMORY; break; } - case VT_LPWSTR: + CASE_OR_VECTOR(VT_FILETIME) { - DWORD count; + ULARGE_INTEGER tmp; + FOR_POSSIBLE_VECTOR(FILETIME, filetime, cafiletime, 8) + { + tmp.LowPart = vec->dwLowDateTime; + tmp.HighPart = vec->dwHighDateTime;
- StorageUtl_ReadDWord(data, 0, &count); - prop->u.pwszVal = allocate(allocate_data, count * sizeof(WCHAR)); - if (prop->u.pwszVal) + StorageUtl_WriteULargeInteger(buffer, 0, &tmp); + } + break; + } + CASE_OR_VECTOR(VT_CY) + { + ULARGE_INTEGER tmp; + FOR_POSSIBLE_VECTOR(CY, cyVal, cacy, 8) { - memcpy(prop->u.pwszVal, data + sizeof(DWORD), - count * sizeof(WCHAR)); - /* make sure string is NULL-terminated */ - prop->u.pwszVal[count - 1] = '\0'; - PropertyStorage_ByteSwapString(prop->u.pwszVal, count); - TRACE("Read string value %s\n", debugstr_w(prop->u.pwszVal)); + *((LONGLONG*)&tmp.QuadPart) = vec->int64; + StorageUtl_WriteULargeInteger(buffer, 0, &tmp); } - else - hr = STG_E_INSUFFICIENTMEMORY; break; } - case VT_FILETIME: - StorageUtl_ReadULargeInteger(data, 0, - (ULARGE_INTEGER *)&prop->u.filetime); + CASE_OR_VECTOR(VT_CF) + { + FOR_POSSIBLE_VECTOR_OR_SINGLEP(CLIPDATA, pclipdata, caclipdata, 0) + { + WRITE_DWORD_AND_MOVE(vec->cbSize); + WRITE_DWORD_AND_MOVE(vec->ulClipFmt); + + FAIL_IF_EOF(vec->cbSize - 4); + memcpy(buffer, vec->pClipData, vec->cbSize - 4); + MOVE_FORWARD(vec->cbSize - 4); + + PAD_TO_MUL_AND_MOVE(vec->cbSize, 4); + TRACE("Written CLPIDATA with size %lu\n", (unsigned long)vec->cbSize); + } break; - case VT_CF: + } + CASE_OR_VECTOR(VT_DATE) + CASE_OR_VECTOR(VT_R8) + { + FOR_POSSIBLE_VECTOR(double, dblVal, cadbl, sizeof(double)) { - DWORD len = 0, tag = 0; + memcpy(buffer, vec, sizeof(double)); + } + break; + } + CASE_OR_VECTOR(VT_R4) + { + FOR_POSSIBLE_VECTOR(float, fltVal, caflt, sizeof(float)) + { + memcpy(buffer, vec, sizeof(double)); + } + break; + } + case VT_DECIMAL: + { + DECIMAL *dec; + + FAIL_IF_EOF(16); + + dec = (DECIMAL*)∝ + buffer[0] = 0; + buffer[1] = 0; + buffer[2] = dec->u.scale; + buffer[3] = dec->u.sign; + StorageUtl_WriteDWord(buffer, 4, dec->Hi32); + StorageUtl_WriteULargeInteger(buffer, 8, (ULARGE_INTEGER*)&dec->u1);
- StorageUtl_ReadDWord(data, 0, &len); - StorageUtl_ReadDWord(data, 4, &tag); - if (len > 8) + MOVE_FORWARD(16); + break; + } + CASE_OR_VECTOR(VT_CLSID) + { + FOR_POSSIBLE_VECTOR_OR_SINGLEP(GUID, puuid, cauuid, 16) + { + StorageUtl_WriteGUID(buffer, 0, vec); + } + break; + } + case VT_VECTOR|VT_VARIANT: + { + FOR_POSSIBLE_VECTOR_OR_SINGLEP(PROPVARIANT, /* dummy */pvarVal, capropvar, 0) + { + ULONG cbBufferUsed; + hr = wine_WritePropertyToBuffer(vec, buffer, cbBufSize, + &cbBufferUsed, diskCodepage, runningCodepage); + if (FAILED(hr)) { - len -= 8; - prop->u.pclipdata = allocate(allocate_data, sizeof (CLIPDATA)); - prop->u.pclipdata->cbSize = len; - prop->u.pclipdata->ulClipFmt = tag; - prop->u.pclipdata->pClipData = allocate(allocate_data, len - sizeof(prop->u.pclipdata->ulClipFmt)); - memcpy(prop->u.pclipdata->pClipData, data+8, len - sizeof(prop->u.pclipdata->ulClipFmt)); + return hr; } - else - hr = STG_E_INVALIDPARAMETER; + + buffer += cbBufferUsed; + cbBufSize -= cbBufferUsed; } break; + } default: - FIXME("unsupported type %d\n", prop->vt); - hr = STG_E_INVALIDPARAMETER; + FIXME("unsupported type %d\n", prop.vt); + return STG_E_INVALIDPARAMETER; } + + /* Pad to 4 bytes. Always. */ + PAD_TO_MUL_AND_MOVE(cbBufSize_Start - cbBufSize, 4); + + if (pcbBufSizeUsed) + *pcbBufSizeUsed = cbBufSize_Start - cbBufSize; + +#undef FAIL_IF_EOF +#undef WRITE_DWORD_AND_MOVE +#undef WRITE_WORD_AND_MOVE +#undef MOVE_FORWARD +#undef FOR_POSSIBLE_VECTOR +#undef FOR_POSSIBLE_VECTOR_OR_SINGLEP +#undef PAD_TO_MUL_AND_MOVE +#undef CASE_OR_VECTOR + return hr; }
@@ -1439,11 +2747,14 @@ static HRESULT PropertyStorage_ReadFromStream(PropertyStorage_impl *This) else { PROPVARIANT prop; + HRESULT hr;
PropVariantInit(&prop); - if (SUCCEEDED(PropertyStorage_ReadProperty(&prop, - buf + idOffset->dwOffset - sizeof(PROPERTYSECTIONHEADER), - This->codePage, Allocate_CoTaskMemAlloc, NULL))) + hr = wine_ReadProperty(&prop, + buf + idOffset->dwOffset - sizeof(PROPERTYSECTIONHEADER), + sectionHdr.cbSection - idOffset->dwOffset, + NULL, This->codePage, This->codePage, NULL, NULL); + if (SUCCEEDED(hr)) { TRACE("Read property with ID 0x%08x, type %d\n", idOffset->propid, prop.vt); @@ -1468,6 +2779,11 @@ static HRESULT PropertyStorage_ReadFromStream(PropertyStorage_impl *This) idOffset->propid, &prop, This->codePage); } } + else + { + WARN("Failed to read property with ID 0x%08x, hr=0x%08x\n", + idOffset->propid, hr); + } PropVariantClear(&prop); } } @@ -1689,7 +3005,9 @@ static HRESULT PropertyStorage_WritePropertyToStream(PropertyStorage_impl *This, LARGE_INTEGER seek; PROPERTYIDOFFSET propIdOffset; ULONG count; - DWORD dwType, bytesWritten; + DWORD bytesWritten; + BYTE *propBuffer = NULL; + ULONG bufferUsed = 0;
assert(var); assert(sectionOffset); @@ -1711,112 +3029,33 @@ static HRESULT PropertyStorage_WritePropertyToStream(PropertyStorage_impl *This, hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); if (FAILED(hr)) goto end; - StorageUtl_WriteDWord((LPBYTE)&dwType, 0, var->vt); - hr = IStream_Write(This->stm, &dwType, sizeof(dwType), &count); - if (FAILED(hr)) - goto end; - *sectionOffset += sizeof(dwType);
- switch (var->vt) - { - case VT_EMPTY: - case VT_NULL: - bytesWritten = 0; - break; - case VT_I1: - case VT_UI1: - hr = IStream_Write(This->stm, &var->u.cVal, sizeof(var->u.cVal), - &count); - bytesWritten = count; - break; - case VT_I2: - case VT_UI2: + hr = wine_SerializedPropertySize(var, &count, This->codePage, This->codePage); + if (FAILED(hr)) { - WORD wTemp; - - StorageUtl_WriteWord((LPBYTE)&wTemp, 0, var->u.iVal); - hr = IStream_Write(This->stm, &wTemp, sizeof(wTemp), &count); - bytesWritten = count; - break; + WARN("Property space could not be calculated: hr=%08x\n", hr); + goto end; } - case VT_I4: - case VT_UI4: - { - DWORD dwTemp;
- StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, var->u.lVal); - hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); - bytesWritten = count; - break; - } - case VT_LPSTR: + propBuffer = CoTaskMemAlloc(count); + if (!propBuffer) { - DWORD len, dwTemp; - - if (This->codePage == CP_UNICODE) - len = (lstrlenW(var->u.pwszVal) + 1) * sizeof(WCHAR); - else - len = lstrlenA(var->u.pszVal) + 1; - StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, len); - hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); - if (FAILED(hr)) - goto end; - hr = IStream_Write(This->stm, var->u.pszVal, len, &count); - bytesWritten = count + sizeof(DWORD); - break; + hr = STG_E_INSUFFICIENTMEMORY; + goto end; } - case VT_LPWSTR: - { - DWORD len = lstrlenW(var->u.pwszVal) + 1, dwTemp;
- StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, len); - hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); - if (FAILED(hr)) - goto end; - hr = IStream_Write(This->stm, var->u.pwszVal, len * sizeof(WCHAR), - &count); - bytesWritten = count + sizeof(DWORD); - break; - } - case VT_FILETIME: + hr = wine_WritePropertyToBuffer(var, propBuffer, count, &bufferUsed, This->codePage, This->codePage); + if (SUCCEEDED(hr)) { - FILETIME temp; + if (bufferUsed != count) + WARN("Property buffer calculation was off by %d bytes\n", (int)(count - bufferUsed));
- StorageUtl_WriteULargeInteger((BYTE *)&temp, 0, - (const ULARGE_INTEGER *)&var->u.filetime); - hr = IStream_Write(This->stm, &temp, sizeof(FILETIME), &count); + hr = IStream_Write(This->stm, propBuffer, bufferUsed, &count); bytesWritten = count; - break; - } - case VT_CF: - { - DWORD cf_hdr[2], len; - - len = var->u.pclipdata->cbSize; - StorageUtl_WriteDWord((LPBYTE)&cf_hdr[0], 0, len + 8); - StorageUtl_WriteDWord((LPBYTE)&cf_hdr[1], 0, var->u.pclipdata->ulClipFmt); - hr = IStream_Write(This->stm, cf_hdr, sizeof(cf_hdr), &count); - if (FAILED(hr)) - goto end; - hr = IStream_Write(This->stm, var->u.pclipdata->pClipData, - len - sizeof(var->u.pclipdata->ulClipFmt), &count); - if (FAILED(hr)) - goto end; - bytesWritten = count + sizeof cf_hdr; - break; } - case VT_CLSID: + else { - CLSID temp; - - StorageUtl_WriteGUID((BYTE *)&temp, 0, var->u.puuid); - hr = IStream_Write(This->stm, &temp, sizeof(temp), &count); - bytesWritten = count; - break; - } - default: - FIXME("unsupported type: %d\n", var->vt); - return STG_E_INVALIDPARAMETER; + WARN("Property could not be written: hr=%08x\n", hr); }
if (SUCCEEDED(hr)) @@ -2748,11 +3987,14 @@ BOOLEAN WINAPI StgConvertPropertyToVariant(const SERIALIZEDPROPERTYVALUE* prop, { HRESULT hr;
- hr = PropertyStorage_ReadProperty(pvar, (const BYTE*)prop, CodePage, Allocate_PMemoryAllocator, pma); + hr = wine_ReadProperty(pvar, (const BYTE*)prop, /*FIXME*/0xFFFFFFFF, NULL, + CodePage, CP_ACP, Allocate_PMemoryAllocator, pma);
if (FAILED(hr)) { FIXME("should raise C++ exception on failure\n"); + /* FIXME: can't clear partially initialized PROPVARIANT because + * PropVariantClear can't deal with a custom Free function */ PropVariantInit(pvar); }
@@ -2763,7 +4005,32 @@ SERIALIZEDPROPERTYVALUE* WINAPI StgConvertVariantToProperty(const PROPVARIANT *p USHORT CodePage, SERIALIZEDPROPERTYVALUE *pprop, ULONG *pcb, PROPID pid, BOOLEAN fReserved, ULONG *pcIndirect) { - FIXME("%p,%d,%p,%p,%d,%d,%p\n", pvar, CodePage, pprop, pcb, pid, fReserved, pcIndirect); + HRESULT hr; + ULONG buffer_size; + + TRACE("%p,%d,%p,%p,%d,%d,%p\n", pvar, CodePage, pprop, pcb, pid, fReserved, pcIndirect); + + if (pprop && pcb) + buffer_size = *pcb; + else + buffer_size = (ULONG)-1; + + /* TODO: indirect properties and other crazyness */ + if (pprop) + { + hr = wine_WritePropertyToBuffer(pvar, (BYTE*)pprop, buffer_size, pcb, CodePage, CP_ACP); + } + else + { + hr = wine_SerializedPropertySize(pvar, pcb, CodePage, CP_ACP); + } + + if (FAILED(hr)) + { + FIXME("should raise exception on failure\n"); + *pcb = 0; + return NULL; + }
- return NULL; + return pprop; } diff --git a/dlls/ole32/tests/propvariant.c b/dlls/ole32/tests/propvariant.c index c45ca2a..d9b4320 100644 --- a/dlls/ole32/tests/propvariant.c +++ b/dlls/ole32/tests/propvariant.c @@ -438,6 +438,23 @@ static const char serialized_bstr_mb[] = { 0,0,0,0 };
+static const char serialized_ascii_str_wc[] = { + 30,0, /* VT_LPSTR */ + 0,0, /* padding */ + 10,0,0,0, /* size */ + 't',0,'e',0, + 's',0,'t',0, + 0,0,0,0 +}; + +static const char serialized_ascii_str_mb[] = { + 30,0, /* VT_LPSTR */ + 0,0, /* padding */ + 5,0,0,0, /* size */ + 't','e','s','t', + 0,0,0,0 +}; + static void test_propertytovariant(void) { HANDLE hole32; @@ -447,6 +464,7 @@ static void test_propertytovariant(void) struct _PMemoryAllocator_vtable vtable; BOOLEAN ret; static const WCHAR test_string[] = {'t','e','s','t',0}; + static const char test_astring[] = "test";
hole32 = GetModuleHandleA("ole32");
@@ -495,6 +513,22 @@ static void test_propertytovariant(void) ok(propvar.vt == VT_BSTR, "unexpected vt %x\n", propvar.vt); ok(!lstrcmpW(U(propvar).bstrVal, test_string), "unexpected string value\n"); PropVariantClear(&propvar); + + ret = pStgConvertPropertyToVariant((SERIALIZEDPROPERTYVALUE*)serialized_ascii_str_wc, + CP_WINUNICODE, &propvar, &allocator); + + ok(ret == 0, "StgConvertPropertyToVariant returned %i\n", ret); + ok(propvar.vt == VT_LPSTR, "unexpected vt %x\n", propvar.vt); + ok(!lstrcmpA(U(propvar).pszVal, test_astring), "unexpected string value '%s'\n", U(propvar).pszVal); + PropVariantClear(&propvar); + + ret = pStgConvertPropertyToVariant((SERIALIZEDPROPERTYVALUE*)serialized_ascii_str_mb, + CP_UTF8, &propvar, &allocator); + + ok(ret == 0, "StgConvertPropertyToVariant returned %i\n", ret); + ok(propvar.vt == VT_LPSTR, "unexpected vt %x\n", propvar.vt); + ok(!lstrcmpA(U(propvar).pszVal, test_astring), "unexpected string value '%s'\n", U(propvar).pszVal); + PropVariantClear(&propvar); }
static void test_varianttoproperty(void) @@ -506,6 +540,7 @@ static void test_varianttoproperty(void) const PROPVARIANT*,USHORT,SERIALIZEDPROPERTYVALUE*,ULONG*,PROPID,BOOLEAN,ULONG*); ULONG len; static const WCHAR test_string[] = {'t','e','s','t',0}; + static char test_astring[] = "test"; BSTR test_string_bstr;
hole32 = GetModuleHandleA("ole32"); @@ -530,7 +565,7 @@ static void test_varianttoproperty(void) 0, FALSE, 0);
ok(propvalue == NULL, "got nonnull propvalue\n"); - todo_wine ok(len == 8, "unexpected length %d\n", len); + ok(len == 8, "unexpected length %d\n", len);
if (len == 0xdeadbeef) { @@ -588,6 +623,24 @@ static void test_varianttoproperty(void)
SysFreeString(test_string_bstr);
+ propvar.vt = VT_LPSTR; + U(propvar).pszVal = test_astring; + len = 20; + propvalue = pStgConvertVariantToProperty(&propvar, CP_WINUNICODE, own_propvalue, &len, + 0, FALSE, 0); + + ok(propvalue == own_propvalue, "unexpected propvalue %p\n", propvalue); + ok(len == 20, "unexpected length %d\n", len); + ok(!memcmp(propvalue, serialized_ascii_str_wc, 20), "got wrong data\n"); + + len = 20; + propvalue = pStgConvertVariantToProperty(&propvar, CP_UTF8, own_propvalue, &len, + 0, FALSE, 0); + + ok(propvalue == own_propvalue, "unexpected propvalue %p\n", propvalue); + ok(len == 16, "unexpected length %d\n", len); + ok(!memcmp(propvalue, serialized_ascii_str_mb, 16), "got wrong data\n"); + HeapFree(GetProcessHeap(), 0, own_propvalue); }
diff --git a/dlls/propsys/propsys.spec b/dlls/propsys/propsys.spec index 92b6d7c..50bdf6e 100644 --- a/dlls/propsys/propsys.spec +++ b/dlls/propsys/propsys.spec @@ -27,24 +27,24 @@ @ stdcall -private DllGetClassObject(ptr ptr ptr) @ stdcall -private DllRegisterServer() @ stdcall -private DllUnregisterServer() -@ stub InitPropVariantFromBooleanVector +@ stdcall InitPropVariantFromBooleanVector(ptr long ptr) @ stdcall InitPropVariantFromBuffer(ptr long ptr) -@ stub InitPropVariantFromCLSID -@ stub InitPropVariantFromDoubleVector -@ stub InitPropVariantFromFileTime -@ stub InitPropVariantFromFileTimeVector +@ stdcall InitPropVariantFromCLSID(ptr ptr) +@ stdcall InitPropVariantFromDoubleVector(ptr long ptr) +@ stdcall InitPropVariantFromFileTime(ptr ptr) +@ stdcall InitPropVariantFromFileTimeVector(ptr long ptr) @ stdcall InitPropVariantFromGUIDAsString(ptr ptr) -@ stub InitPropVariantFromInt16Vector -@ stub InitPropVariantFromInt32Vector -@ stub InitPropVariantFromInt64Vector +@ stdcall InitPropVariantFromInt16Vector(ptr long ptr) +@ stdcall InitPropVariantFromInt32Vector(ptr long ptr) +@ stdcall InitPropVariantFromInt64Vector(ptr long ptr) @ stub InitPropVariantFromPropVariantVectorElem @ stub InitPropVariantFromResource @ stub InitPropVariantFromStrRet @ stub InitPropVariantFromStringAsVector -@ stub InitPropVariantFromStringVector -@ stub InitPropVariantFromUInt16Vector -@ stub InitPropVariantFromUInt32Vector -@ stub InitPropVariantFromUInt64Vector +@ stdcall InitPropVariantFromStringVector(ptr long ptr) +@ stdcall InitPropVariantFromUInt16Vector(ptr long ptr) +@ stdcall InitPropVariantFromUInt32Vector(ptr long ptr) +@ stdcall InitPropVariantFromUInt64Vector(ptr long ptr) @ stub InitPropVariantVectorFromPropVariant @ stub InitVariantFromBooleanArray @ stdcall InitVariantFromBuffer(ptr long ptr) @@ -151,8 +151,8 @@ @ stub PropVariantToUInt64VectorAlloc @ stub PropVariantToUInt64WithDefault @ stub PropVariantToVariant -@ stub StgDeserializePropVariant -@ stub StgSerializePropVariant +@ stdcall StgDeserializePropVariant(ptr long ptr) +@ stdcall StgSerializePropVariant(ptr ptr ptr) @ stub VariantCompare @ stub VariantGetBooleanElem @ stub VariantGetDoubleElem diff --git a/dlls/propsys/propvar.c b/dlls/propsys/propvar.c index fd22c9d..e7ba3f3 100644 --- a/dlls/propsys/propvar.c +++ b/dlls/propsys/propvar.c @@ -30,6 +30,8 @@ #include "winuser.h" #include "shlobj.h" #include "propvarutil.h" +#include "mimeole.h" +#include "oleauto.h"
#include "wine/debug.h" #include "wine/unicode.h" @@ -346,6 +348,20 @@ HRESULT WINAPI InitVariantFromGUIDAsString(REFGUID guid, VARIANT *pvar) return S_OK; }
+HRESULT WINAPI InitPropVariantFromCLSID(REFCLSID clsid, PROPVARIANT *ppropvar) +{ + TRACE("(%p %p)\n", clsid, ppropvar); + + ppropvar->u.puuid = CoTaskMemAlloc(sizeof(CLSID)); + if (!ppropvar->u.puuid) + return E_OUTOFMEMORY; + + *ppropvar->u.puuid = *clsid; + ppropvar->vt = VT_CLSID; + + return S_OK; +} + HRESULT WINAPI InitPropVariantFromBuffer(const VOID *pv, UINT cb, PROPVARIANT *ppropvar) { TRACE("(%p %u %p)\n", pv, cb, ppropvar); @@ -391,6 +407,95 @@ HRESULT WINAPI InitVariantFromBuffer(const VOID *pv, UINT cb, VARIANT *pvar) return S_OK; }
+#define DEFINE_INIT_FROM_VECTOR(ctype, functype, vtype, usuffix) \ + HRESULT WINAPI InitPropVariantFrom##functype##Vector(const ctype *pval, ULONG cEl, PROPVARIANT *ppropvar) \ + { \ + TRACE("(%p %u %p)\n", pval, cEl, ppropvar); \ + \ + ppropvar->u.ca##usuffix.pElems = CoTaskMemAlloc(cEl * sizeof(ctype)); \ + if(!ppropvar->u.ca##usuffix.pElems) \ + return E_OUTOFMEMORY; \ + \ + ppropvar->vt = VT_VECTOR|VT_##vtype; \ + ppropvar->u.ca##usuffix.cElems = cEl; \ + memcpy(ppropvar->u.ca##usuffix.pElems, pval, cEl * sizeof(ctype)); \ + return S_OK; \ + } + +DEFINE_INIT_FROM_VECTOR(SHORT, Int16, I2, i) +DEFINE_INIT_FROM_VECTOR(USHORT, UInt16, UI2, ui) +DEFINE_INIT_FROM_VECTOR(LONG, Int32, I4, l) +DEFINE_INIT_FROM_VECTOR(ULONG, UInt32, UI4, ul) +DEFINE_INIT_FROM_VECTOR(LONGLONG, Int64, I8, h) +DEFINE_INIT_FROM_VECTOR(ULONGLONG, UInt64, UI8, uh) +DEFINE_INIT_FROM_VECTOR(double, Double, R8, dbl) +DEFINE_INIT_FROM_VECTOR(FILETIME, FileTime, FILETIME, filetime) + +#undef DEFINE_INIT_FROM_VECTOR + +HRESULT WINAPI InitPropVariantFromBooleanVector(const BOOL *pbool, ULONG cEl, PROPVARIANT *ppropvar) +{ + ULONG i; + TRACE("(%p %u %p)\n", pbool, cEl, ppropvar); + + ppropvar->u.cabool.pElems = CoTaskMemAlloc(cEl * sizeof(VARIANT_BOOL)); + if (!ppropvar->u.cabool.pElems) + return E_OUTOFMEMORY; + + ppropvar->vt = VT_VECTOR|VT_BOOL; + ppropvar->u.cabool.cElems = cEl; + + for (i = 0; i < cEl; ++i) + { + ppropvar->u.cabool.pElems[i] = pbool[i] ? VARIANT_TRUE : VARIANT_FALSE; + } + + return S_OK; +} + +HRESULT WINAPI InitPropVariantFromFileTime(const FILETIME *pft, PROPVARIANT *pprop) +{ + TRACE("(%p %p)\n", pft, pprop); + + pprop->vt = VT_FILETIME; + pprop->u.filetime = *pft; + + return S_OK; +} + +HRESULT WINAPI InitPropVariantFromStringVector(const WCHAR **pstr, ULONG c, PROPVARIANT *pprop) +{ + ULONG i; + TRACE("(%p %u %p)\n", pstr, c, pprop); + + pprop->u.calpwstr.pElems = CoTaskMemAlloc(c * sizeof(WCHAR *)); + if (!pprop->u.calpwstr.pElems) + return E_OUTOFMEMORY; + + ZeroMemory(pprop->u.calpwstr.pElems, c * sizeof(WCHAR *)); + + pprop->vt = VT_VECTOR|VT_LPWSTR; + pprop->u.calpwstr.cElems = c; + + for (i = 0; i < c; ++i) + { + ULONG len; + + len = lstrlenW(pstr[i]) + 1; + + pprop->u.calpwstr.pElems[i] = CoTaskMemAlloc(len * sizeof(WCHAR)); + if (!pprop->u.calpwstr.pElems[i]) + { + PropVariantClear(pprop); /* release already allocated memory */ + return E_OUTOFMEMORY; + } + + memcpy(pprop->u.calpwstr.pElems[i], pstr[i], len * sizeof(WCHAR)); + } + + return S_OK; +} + static inline DWORD PROPVAR_HexToNum(const WCHAR *hex) { DWORD ret; @@ -511,7 +616,17 @@ static BOOL isemptyornull(const PROPVARIANT *propvar) } return i == propvar->u.parray->cDims; } - /* FIXME: vectors, byrefs, errors? */ + if ((propvar->vt & VT_VECTOR) == VT_VECTOR) + { + /* all vector structs are equal */ + return !propvar->u.cac.cElems || !propvar->u.cac.pElems; + } + if ((propvar->vt & VT_BYREF) == VT_BYREF) + { + /* all pointers are equal */ + return !propvar->u.punkVal; + } + /* FIXME: errors? */ return FALSE; }
@@ -555,36 +670,321 @@ INT WINAPI PropVariantCompareEx(REFPROPVARIANT propvar1, REFPROPVARIANT propvar2 else \ res = 0; \ } while (0) +#define CMP_INT_VECTOR(var) do { \ + if (propvar1->u.var.cElems > propvar2_converted->u.var.cElems) \ + { res = 1; } \ + else if (propvar1->u.var.cElems < propvar2_converted->u.var.cElems) \ + { res = -1; } \ + else \ + { \ + ULONG i; \ + res = 0; \ + for (i = 0; i < propvar1->u.var.cElems; ++i) \ + { \ + if (propvar1->u.var.pElems[i] > propvar2_converted->u.var.pElems[i]) \ + { \ + res = 1; \ + break; \ + } \ + else if (propvar1->u.var.pElems[i] < propvar2_converted->u.var.pElems[i]) \ + { \ + res = -1; \ + break; \ + } \ + } \ + } \ + } while(0) + +#define INT_CASE(vtype, svar, vecvar) \ + case vtype: \ + CMP_INT_VALUE(svar); \ + break; \ + case VT_VECTOR|vtype: \ + CMP_INT_VECTOR(vecvar); \ + break; \
switch (propvar1->vt) { - case VT_I1: - CMP_INT_VALUE(cVal); - break; - case VT_UI1: - CMP_INT_VALUE(bVal); - break; - case VT_I2: - CMP_INT_VALUE(iVal); - break; - case VT_UI2: - CMP_INT_VALUE(uiVal); - break; - case VT_I4: + INT_CASE(VT_I1, cVal, cac) + INT_CASE(VT_UI1, bVal, caub) + INT_CASE(VT_I2, iVal, cai) + INT_CASE(VT_UI2, uiVal, caui) + INT_CASE(VT_I4, lVal, cal) + INT_CASE(VT_UI4, ulVal, caul) + INT_CASE(VT_BOOL, boolVal, cabool) + INT_CASE(VT_R4, fltVal, caflt) + INT_CASE(VT_R8, dblVal, cadbl) + INT_CASE(VT_DATE, date, cadate) /* DATE == double */ + INT_CASE(VT_ERROR, scode, cascode) /* == ULONG */ + case VT_INT: /* does not appear as vector */ CMP_INT_VALUE(lVal); break; - case VT_UI4: - CMP_INT_VALUE(uiVal); + case VT_UINT: /* does not appear as vector */ + CMP_INT_VALUE(ulVal); break; case VT_I8: CMP_INT_VALUE(hVal.QuadPart); break; + case VT_VECTOR|VT_I8: + if (propvar1->u.cah.cElems > propvar2_converted->u.cah.cElems) + res = 1; + else if (propvar1->u.cah.cElems < propvar2_converted->u.cah.cElems) + res = -1; + else + { + ULONG i; + res = 0; + for (i = 0; i < propvar1->u.cah.cElems; ++i) + { + if (propvar1->u.cah.pElems[i].QuadPart > propvar2_converted->u.cah.pElems[i].QuadPart) + { + res = 1; + break; + } + else if (propvar1->u.cah.pElems[i].QuadPart < propvar2_converted->u.cah.pElems[i].QuadPart) + { + res = -1; + break; + } + } + } + break; case VT_UI8: CMP_INT_VALUE(uhVal.QuadPart); break; - case VT_BSTR: + case VT_VECTOR|VT_UI8: + if (propvar1->u.cauh.cElems > propvar2_converted->u.cauh.cElems) + res = 1; + else if (propvar1->u.cauh.cElems < propvar2_converted->u.cauh.cElems) + res = -1; + else + { + ULONG i; + res = 0; + for (i = 0; i < propvar1->u.cauh.cElems; ++i) + { + if (propvar1->u.cauh.pElems[i].QuadPart > propvar2_converted->u.cauh.pElems[i].QuadPart) + { + res = 1; + break; + } + else if (propvar1->u.cauh.pElems[i].QuadPart < propvar2_converted->u.cauh.pElems[i].QuadPart) + { + res = -1; + break; + } + } + } + break; + case VT_FILETIME: + { + CMP_INT_VALUE(filetime.dwHighDateTime); + if (res == 0) + CMP_INT_VALUE(filetime.dwLowDateTime); + + break; + } + case VT_VECTOR|VT_FILETIME: + { + ULONG i; + + CMP_INT_VALUE(cafiletime.cElems); + if (res) + break; + + for (i = 0; i < propvar1->u.cafiletime.cElems; ++i) + { + CMP_INT_VALUE(cafiletime.pElems[i].dwHighDateTime); + if (res) + break; + + CMP_INT_VALUE(cafiletime.pElems[i].dwLowDateTime); + if (res) + break; + } + break; + } + case VT_LPSTR: + res = lstrcmpA(propvar1->u.pszVal, propvar2_converted->u.pszVal); + break; + case VT_VECTOR|VT_LPSTR: + { + ULONG i; + + CMP_INT_VALUE(calpstr.cElems); + if (res) + break; + + for (i = 0; i < propvar1->u.calpstr.cElems; ++i) + { + /* FIXME: Use string flags. */ + res = lstrcmpA(propvar1->u.calpstr.pElems[i], propvar2_converted->u.calpstr.pElems[i]); + + if (res) + break; + } + break; + } + case VT_BSTR: /* BSTR and LPWSTR are NOT EQUAL in general, but here */ + case VT_LPWSTR: /* FIXME: Use string flags. */ - res = lstrcmpW(propvar1->u.bstrVal, propvar2->u.bstrVal); + res = lstrcmpW(propvar1->u.bstrVal, propvar2_converted->u.bstrVal); + break; + case VT_VECTOR|VT_BSTR: + case VT_VECTOR|VT_LPWSTR: + { + ULONG i; + + CMP_INT_VALUE(calpwstr.cElems); + if (res) + break; + + for (i = 0; i < propvar1->u.calpwstr.cElems; ++i) + { + /* FIXME: Use string flags. */ + res = lstrcmpW(propvar1->u.calpwstr.pElems[i], propvar2_converted->u.calpwstr.pElems[i]); + + if (res) + break; + } + break; + } + case VT_CLSID: + /* IsEqualUUID only compares for equality :( */ + CMP_INT_VALUE(puuid->Data1); + if (res) + break; + + CMP_INT_VALUE(puuid->Data2); + if (res) + break; + + CMP_INT_VALUE(puuid->Data3); + if (res) + break; + + res = memcmp(propvar1->u.puuid->Data4, propvar2_converted->u.puuid->Data4, 8); + break; + case VT_VECTOR|VT_CLSID: + { + ULONG i; + + CMP_INT_VALUE(cauuid.cElems); + if (res) + break; + + for (i = 0; i < propvar1->u.cauuid.cElems; ++i) + { + CMP_INT_VALUE(cauuid.pElems[i].Data1); + if (res) + break; + + CMP_INT_VALUE(cauuid.pElems[i].Data2); + if (res) + break; + + CMP_INT_VALUE(cauuid.pElems[i].Data3); + if (res) + break; + + res = memcmp(propvar1->u.cauuid.pElems[i].Data4, + propvar2_converted->u.cauuid.pElems[i].Data4, + 8); + + if (res) + break; + } + break; + } + case VT_CF: + CMP_INT_VALUE(pclipdata->cbSize); + if (res) + break; + + CMP_INT_VALUE(pclipdata->ulClipFmt); + if (res) + break; + + res = memcmp(propvar1->u.pclipdata->pClipData, + propvar2_converted->u.pclipdata->pClipData, + propvar1->u.pclipdata->cbSize - 4); + break; + case VT_VECTOR|VT_CF: + { + ULONG i; + + CMP_INT_VALUE(caclipdata.cElems); + if (res) + break; + + for (i = 0; i < propvar1->u.caclipdata.cElems; ++i) + { + CMP_INT_VALUE(caclipdata.pElems[i].cbSize); + if (res) + break; + + CMP_INT_VALUE(caclipdata.pElems[i].ulClipFmt); + if (res) + break; + + res = memcmp(propvar1->u.caclipdata.pElems[i].pClipData, + propvar2_converted->u.caclipdata.pElems[i].pClipData, + propvar1->u.caclipdata.pElems[i].cbSize - 4); + + if (res) + break; + } + break; + } + case VT_VECTOR|VT_VARIANT: + { + ULONG i; + + CMP_INT_VALUE(capropvar.cElems); + if (res) + break; + + for (i = 0; i < propvar1->u.capropvar.cElems; ++i) + { + res = PropVariantCompareEx(&propvar1->u.capropvar.pElems[i], + &propvar2_converted->u.capropvar.pElems[i], + unit, flags); + + if (res) + break; + } + break; + } + case VT_DECIMAL: + { + HRESULT hr; + DECIMAL dec1 = *(DECIMAL*)propvar1; + DECIMAL dec2 = *(DECIMAL*)propvar2_converted; + dec1.wReserved = 0; + dec2.wReserved = 0; + + hr = VarDecCmp(&dec1, &dec2); + if (hr == VARCMP_LT) + res = -1; + else if (hr == VARCMP_GT) + res = 1; + else if (hr == VARCMP_EQ) + res = 0; + else + { + WARN("Comparing DECIMALS: hr=%08x\n", (unsigned)hr); + res = -1; + } + break; + } + case VT_BLOB: + CMP_INT_VALUE(blob.cbSize); + if (res) + break; + + res = memcmp(propvar1->u.blob.pBlobData, + propvar2_converted->u.blob.pBlobData, + propvar1->u.blob.cbSize); break; default: FIXME("vartype %d not handled\n", propvar1->vt); @@ -597,3 +997,69 @@ INT WINAPI PropVariantCompareEx(REFPROPVARIANT propvar1, REFPROPVARIANT propvar2
return res; } + +HRESULT WINAPI StgDeserializePropVariant(const SERIALIZEDPROPERTYVALUE *pprop, + ULONG cbMax, + PROPVARIANT *pvar) +{ + HRESULT hr; + + TRACE("(%p, %lu, %p)\n", pprop, (unsigned long)cbMax, pvar); + + if (!pprop || !pvar) + return E_INVALIDARG; + + hr = wine_ReadProperty(pvar, (BYTE*)pprop, cbMax, NULL, CP_UNICODE, CP_ACP, NULL, NULL); + if (FAILED(hr)) + { + PropVariantClear(pvar); + return hr; + } + + return S_OK; +} + +HRESULT WINAPI StgSerializePropVariant(const PROPVARIANT *pVar, + SERIALIZEDPROPERTYVALUE **ppProp, + ULONG *pcb) +{ + HRESULT hr; + ULONG space; + ULONG space_used; + BYTE *buffer = NULL; + + TRACE("(%p, %p, %p)\n", pVar, ppProp, pcb); + + if (!ppProp || !pcb) + return E_INVALIDARG; + + *ppProp = NULL; + *pcb = 0; + + hr = wine_SerializedPropertySize(pVar, &space, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + return hr; + + buffer = CoTaskMemAlloc(space); + if (!buffer) + return E_OUTOFMEMORY; + + hr = wine_WritePropertyToBuffer(pVar, buffer, space, &space_used, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + { + WARN("Failed to serialize property of type %d: hr=%08x\n", pVar->vt, hr); + CoTaskMemFree(buffer); + + return hr; + } + + if (space - space_used) + { + WARN("Buffer size calculation was off by %u bytes\n", space - space_used); + } + + *ppProp = (SERIALIZEDPROPERTYVALUE*)buffer; + *pcb = space_used; + + return S_OK; +} diff --git a/dlls/propsys/tests/propsys.c b/dlls/propsys/tests/propsys.c index 99d1c46..311d610 100644 --- a/dlls/propsys/tests/propsys.c +++ b/dlls/propsys/tests/propsys.c @@ -866,6 +866,382 @@ static void test_intconversions(void) ok(llval == -7, "got wrong value %s\n", debugstr_longlong(llval)); }
+static char *buffer_printable(void *buffer, size_t buffer_size) +{ + char *heap_buffer, *p; + unsigned char *source_buffer; + + p = heap_buffer = HeapAlloc(GetProcessHeap(), 0, buffer_size * 4 + 5); + if (!heap_buffer) + return NULL; + + source_buffer = buffer; + *p++ = '"'; + + for (; buffer_size; --buffer_size) + { + if ((*source_buffer >= '0' && *source_buffer <= '9') + || (*source_buffer >= 'a' && *source_buffer <= 'z') + || (*source_buffer >= 'A' && *source_buffer <= 'Z') + || *source_buffer == ' ' || *source_buffer == ',') + { + *p++ = (char)*source_buffer++; + } + else + { + sprintf(p, "\%03o", (unsigned)*source_buffer++); + p += 4; + } + } + + *p++ = '"'; + *p++ = '\0'; + + return heap_buffer; +} + +static void ok_compare(const char *type, + BYTE *expected_buffer, + size_t expected_buffer_size, + BYTE *got_buffer, + size_t got_buffer_size) +{ + int same = got_buffer_size == expected_buffer_size && + !memcmp(expected_buffer, got_buffer, got_buffer_size); + + char *got = buffer_printable(got_buffer, got_buffer_size); + char *expected = buffer_printable(expected_buffer, expected_buffer_size); + + ok(same, "Comparing serialized %s: Expected %s:%lu but got %s:%lu\n", type, expected, (unsigned long)expected_buffer_size, got, (unsigned long)got_buffer_size); + + HeapFree(GetProcessHeap(), 0, got); + HeapFree(GetProcessHeap(), 0, expected); +} + +static void check_serialize_func(const char *type, const PROPVARIANT *prop, BYTE *expected, size_t expected_size) +{ + PROPVARIANT out; + HRESULT hr; + INT cmp; + SERIALIZEDPROPERTYVALUE *serialized = NULL; + ULONG serialized_size = 0; + + PropVariantInit(&out); + + hr = StgSerializePropVariant(prop, &serialized, &serialized_size); + ok(hr == S_OK, "Serializing %s: %08x\n", type, (unsigned)hr); + + ok_compare(type, expected, expected_size, (BYTE*)serialized, (size_t)serialized_size); + + hr = StgDeserializePropVariant(serialized, serialized_size, &out); + /* WTF: Win8 chokes on VT_DECIMAL, won't deserialize it but whatever it + deserializes compares equal to the original value */ + ok(hr == S_OK || broken(hr == E_FAIL && prop->vt == VT_DECIMAL), "Deserializing %s: %08x\n", type, (unsigned)hr); + + cmp = PropVariantCompare(prop, &out); + ok(cmp == 0, "Deserialized %s is different from original value!\n", type); + + PropVariantClear(&out); + CoTaskMemFree(serialized); +} + +#define CHECK_SERIALIZE(type, prop, buffer_str_literal) \ + do { \ + unsigned char _str[] = buffer_str_literal; \ + check_serialize_func(type, prop, _str, sizeof(_str) - 1); \ + } while (0) + +#define DO_INT_TEST(ctype, functype, single_buffer_str, vector_buffer_str, values...) \ + do { \ + PROPVARIANT v; \ + HRESULT hr; \ + ctype vbuffer[] = { values }; \ + \ + /* first try a single value */ \ + PropVariantInit(&v); \ + hr = InitPropVariantFrom##functype(vbuffer[0], &v); \ + ok(hr == S_OK, "Initializing PROPVARIANT from " #ctype ": %08x\n", (unsigned)hr); \ + \ + CHECK_SERIALIZE(#ctype, &v, single_buffer_str); \ + \ + PropVariantClear(&v); \ + \ + /* then try a vector */ \ + PropVariantInit(&v); \ + hr = InitPropVariantFrom##functype##Vector(vbuffer, sizeof(vbuffer)/sizeof(vbuffer[0]), &v); \ + ok(hr == S_OK, "Initializing PROPVARIANT from " #ctype " Vector: %08x\n", (unsigned)hr); \ + \ + CHECK_SERIALIZE(#ctype " Vector", &v, vector_buffer_str); \ + \ + PropVariantClear(&v); \ + } while(0) + +/* FIXME: In wine, this really tests wine_ReadProperty/wine_WritePropertyToBuffer + * in OLE32.dll, but propsys.dll provides the only non-braindamaged interface to + * property (de)serialization. Therefore, we test it here. + */ +static void test_serialization(void) +{ + HRESULT hr; + + DO_INT_TEST(SHORT, Int16, "\002\000\000\000\040\000\000\000", "\002\020\000\000\003\000\000\000\040\000\167\362\052\000\000\000", 32, -3465, 42); + DO_INT_TEST(USHORT, UInt16, "\022\000\000\000\040\000\000\000", "\022\020\000\000\003\000\000\000\040\000\376\377\052\000\000\000", 32, 65534, 42); + DO_INT_TEST(LONG, Int32, "\003\000\000\000\040\000\000\000", "\003\020\000\000\003\000\000\000\040\000\000\000\066\113\005\000\312\366\377\377", 32, 346934, -2358); + DO_INT_TEST(ULONG, UInt32, "\023\000\000\000\357\276\255\336", "\023\020\000\000\005\000\000\000\357\276\255\336\276\272\376\312\276\272\255\336\376\312\357\276\357\276\276\272", 0xDEADBEEF, 0xCAFEBABE, 0xDEADBABE, 0xBEEFCAFE, 0xBABEBEEF); + DO_INT_TEST(LONGLONG, Int64, "\024\000\000\000\065\065\043\316\114\000\000\000", "\024\020\000\000\003\000\000\000\065\065\043\316\114\000\000\000\341\030\011\376\377\377\377\377\343\226\003\000\000\000\000\000", 329875928373, -32958239, 235235); + DO_INT_TEST(ULONGLONG, UInt64, "\025\000\000\000\276\272\357\276\376\312\355\015", "\025\020\000\000\001\000\000\000\276\272\357\276\376\312\355\015", 0xDEDCAFEBEEFBABE); + DO_INT_TEST(BOOL, Boolean, "\013\000\000\000\377\377\000\000", "\013\020\000\000\005\000\000\000\377\377\000\000\377\377\000\000\000\000\000\000", TRUE, FALSE, TRUE, FALSE, FALSE); + + /* FileTime */ + { + PROPVARIANT vSingle; + PROPVARIANT vVector; + FILETIME ftVec[] = { { 0xDEADBEEF, 0xCAFEBABE }, { 0xDEADBABE, 0xCAFEBEEF }, { 239508, 2484 } }; + + PropVariantInit(&vSingle); PropVariantInit(&vVector); + + hr = InitPropVariantFromFileTime(&ftVec[0], &vSingle); + ok(hr == S_OK, "InitPropVariantFromFileTime: %08x\n", (unsigned)hr); + + hr = InitPropVariantFromFileTimeVector(ftVec, sizeof(ftVec)/sizeof(ftVec[0]), &vVector); + ok(hr == S_OK, "InitPropVariantFromFileTimeVector: %08x\n", (unsigned)hr); + + CHECK_SERIALIZE("FILETIME", &vSingle, "\100\000\000\000\357\276\255\336\276\272\376\312"); + CHECK_SERIALIZE("FILETIME Vector", &vVector, "\100\020\000\000\003\000\000\000\357\276\255\336\276\272\376\312\276\272\255\336\357\276\376\312\224\247\003\000\264\011\000\000"); + + PropVariantClear(&vSingle); PropVariantClear(&vVector); + } + + /* Int8 / Uint8 */ + { + PROPVARIANT vc; + PROPVARIANT vac; + PROPVARIANT vb; + PROPVARIANT vab; + + char cVec[] = "Hello World, How are You?"; + BYTE bVec[] = { 0xDE, 0xAD, 0xCA, 0xFE, 0xBA }; + + vc.vt = VT_I1; + vc.u.cVal = cVec[0]; + + vac.vt = VT_VECTOR|VT_I1; + vac.u.cac.cElems = sizeof(cVec)/sizeof(cVec[0]); + vac.u.cac.pElems = cVec; + + vb.vt = VT_UI1; + vb.u.bVal = bVec[0]; + + vab.vt = VT_VECTOR|VT_UI1; + vab.u.caub.cElems = sizeof(bVec)/sizeof(bVec[0]); + vab.u.caub.pElems = bVec; + + CHECK_SERIALIZE("char", &vc, "\020\000\000\000\110\000\000\000"); + CHECK_SERIALIZE("char vector", &vac, "\020\020\000\000\032\000\000\000\110\145\154\154\157\040\127\157\162\154\144\054\040\110\157\167\040\141\162\145\040\131\157\165\077\000\000\000"); + CHECK_SERIALIZE("byte", &vb, "\021\000\000\000\336\000\000\000"); + CHECK_SERIALIZE("byte vector", &vab, "\021\020\000\000\005\000\000\000\336\255\312\376\272\000\000\000"); + } + + /* Float */ + { + PROPVARIANT vSingle; + PROPVARIANT vVector; + + FLOAT fVec[] = { 3.14156778354f, 0.239852935758f, 128471284.354f, -523525.236f }; + + PropVariantInit(&vSingle); PropVariantInit(&vVector); + + vSingle.vt = VT_R4; + vSingle.u.fltVal = fVec[0]; + vVector.vt = VT_VECTOR|VT_R4; + vVector.u.caflt.cElems = sizeof(fVec)/sizeof(fVec[0]); + vVector.u.caflt.pElems = fVec; + + CHECK_SERIALIZE("float", &vSingle, "\004\000\000\000\162\017\111\100"); + CHECK_SERIALIZE("float vector", &vVector, "\004\020\000\000\004\000\000\000\162\017\111\100\002\234\165\076\037\012\365\114\250\240\377\310"); + } + + /* LPSTR */ + /* The serialization routine converts these to UTF-16 and back to CP_ACP + * on return */ + { + PROPVARIANT vSingle; + PROPVARIANT vVector; + + char str1[] = "HelloWorld"; + char str2[] = "aBc"; + char str3[] = "Blub"; + char *arr[] = { str1, str2, str3 }; + + vSingle.vt = VT_LPSTR; + vSingle.u.pszVal = str1; + + vVector.vt = VT_VECTOR|VT_LPSTR; + vVector.u.calpstr.cElems = sizeof(arr)/sizeof(arr[0]); + vVector.u.calpstr.pElems = arr; + + CHECK_SERIALIZE("lpstr", &vSingle, "\036\000\000\000\026\000\000\000H\000e\000l\000l\000o\000W\000o\000r\000l\000d\000\000\000\000\000"); + CHECK_SERIALIZE("lpstr vector", &vVector, "\036\020\000\000\003\000\000\000\026\000\000\000H\000e\000l\000l\000o\000W\000o\000r\000l\000d\000\000\000\000\000\010\000\000\000a\000B\000c\000\000\000\012\000\000\000B\000l\000u\000b\000\000\000\000\000"); + } + + /* LPWSTR */ + { + PROPVARIANT vSingle; + PROPVARIANT vVector; + + WCHAR str1[] = { 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', 0 }; + WCHAR str2[] = { 'a', 'B', 'c', 0 }; + WCHAR str3[] = { 'B', 'l', 'u', 'b', 0 }; + /* disclaimer: I don't speak chinese, I just want to test some Unicode characters */ + WCHAR str4[] = { 0x4E40, 0x4FB0, 0x4F47, 0x4EB9, 0 }; + const WCHAR *arr[] = { str1, str2, str3, str4 }; + + PropVariantInit(&vSingle); PropVariantInit(&vVector); + + hr = InitPropVariantFromString(str1, &vSingle); + ok(hr == S_OK, "InitPropVariantFromString: %08x\n", (unsigned)hr); + + hr = InitPropVariantFromStringVector(arr, sizeof(arr)/sizeof(arr[0]), &vVector); + + CHECK_SERIALIZE("lpwstr", &vSingle, "\037\000\000\000\013\000\000\000\110\000\145\000\154\000\154\000\157\000\127\000\157\000\162\000\154\000\144\000\000\000\000\000"); + CHECK_SERIALIZE("lpwstr vector", &vVector, "\037\020\000\000\004\000\000\000\013\000\000\000\110\000\145\000\154\000\154\000\157\000\127\000\157\000\162\000\154\000\144\000\000\000\000\000\004\000\000\000\141\000\102\000\143\000\000\000\005\000\000\000\102\000\154\000\165\000\142\000\000\000\000\000\005\000\000\000\100\116\260\117\107\117\271\116\000\000\000\000"); + + PropVariantClear(&vSingle); + PropVariantClear(&vVector); + } + + /* BSTR */ + { + PROPVARIANT vSingle; + PROPVARIANT vVector; + + WCHAR str1[] = { 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', 0 }; + WCHAR str2[] = { 'a', 'B', 'c', 0 }; + WCHAR str3[] = { 'B', 'l', 'u', 'b', 0 }; + /* disclaimer: I don't speak chinese, I just want to test some Unicode characters */ + WCHAR str4[] = { 0x4E40, 0x4FB0, 0x4F47, 0x4EB9, 0 }; + + BSTR arr[4]; + + arr[0] = SysAllocString(str1); /*FIXME: handle NULL*/ + arr[1] = SysAllocString(str2); + arr[2] = SysAllocString(str3); + arr[3] = SysAllocString(str4); + + vSingle.vt = VT_BSTR; + vSingle.u.bstrVal = arr[0]; + vVector.vt = VT_VECTOR|VT_BSTR; + vVector.u.cabstr.cElems = sizeof(arr)/sizeof(arr[0]); + vVector.u.cabstr.pElems = arr; + + CHECK_SERIALIZE("BSTR", &vSingle, "\010\000\000\000\026\000\000\000H\000e\000l\000l\000o\000W\000o\000r\000l\000d\000\000\000\000\000"); + CHECK_SERIALIZE("BSTR vector", &vVector, "\010\020\000\000\004\000\000\000\026\000\000\000H\000e\000l\000l\000o\000W\000o\000r\000l\000d\000\000\000\000\000\010\000\000\000a\000B\000c\000\000\000\012\000\000\000B\000l\000u\000b\000\000\000\000\000\012\000\000\000\100N\260OGO\271N\000\000\000\000"); + + SysFreeString(arr[0]); + SysFreeString(arr[1]); + SysFreeString(arr[2]); + SysFreeString(arr[3]); + } + + /* Clipdata */ + { + PROPVARIANT vSingle; + PROPVARIANT vVector; + + char data1[] = "Hello World, how are you today?"; + char data2[] = "Boo"; + + CLIPDATA cfArr[] = { + { + sizeof(data1) + 4, + 42, + (BYTE*)data1 + }, { + sizeof(data2) + 4, + 0xBABE, + (BYTE*)data2 + } + + }; + + vSingle.vt = VT_CF; + vSingle.u.pclipdata = &cfArr[0]; + + vVector.vt = VT_VECTOR|VT_CF; + vVector.u.caclipdata.cElems = sizeof(cfArr)/sizeof(cfArr[0]); + vVector.u.caclipdata.pElems = cfArr; + + CHECK_SERIALIZE("CF", &vSingle, "G\000\000\000\044\000\000\000\052\000\000\000Hello World, how are you today\077\000"); + CHECK_SERIALIZE("CF vector", &vVector, "G\020\000\000\002\000\000\000\044\000\000\000\052\000\000\000Hello World, how are you today\077\000\010\000\000\000\276\272\000\000Boo\000"); + } + + /* CLSID */ + { + PROPVARIANT vSingle; + PROPVARIANT vVector; + + CLSID arr[] = { + { 0x557cf406, 0x1a04, 0x11d3, { 0x9a,0x73,0x00,0x00,0xf8,0x1e,0xf3,0x2e } }, + { 0xDEADBEEF, 0xCAFE, 0xBABE, { 0xDE,0xAD,0xBA,0xBE,0xCA,0xFE,0xBE,0xEF } } + }; + + InitPropVariantFromCLSID(&arr[0], &vSingle); + + vVector.vt = VT_VECTOR|VT_CLSID; + vVector.u.cauuid.cElems = sizeof(arr)/sizeof(arr[0]); + vVector.u.cauuid.pElems = arr; + + CHECK_SERIALIZE("CLSID", &vSingle, "H\000\000\000\006\364\174U\004\032\323\021\232s\000\000\370\036\363\056"); + CHECK_SERIALIZE("CLSID", &vVector, "H\020\000\000\002\000\000\000\006\364\174U\004\032\323\021\232s\000\000\370\036\363\056\357\276\255\336\376\312\276\272\336\255\272\276\312\376\276\357"); + + PropVariantClear(&vSingle); + } + + /* DECIMAL */ + { + PROPVARIANT v; + DECIMAL *asDecimal = (DECIMAL*)&v; + + PropVariantInit(&v); + asDecimal->u.sign = DECIMAL_NEG; + asDecimal->u.scale = 5; + asDecimal->Hi32 = 0xCAFEBABE; + asDecimal->u1.Lo64 = 0xDEADBABECAFEBEEF; + v.vt = VT_DECIMAL; + + + CHECK_SERIALIZE("DECIMAL", &v, "\016\000\000\000\000\000\005\200\276\272\376\312\357\276\376\312\276\272\255\336"); + } + + /* VARIANT */ + /* Unfortunately, we may only apply one level of variants :( */ + { + PROPVARIANT vVec[2]; + PROPVARIANT v; + + InitPropVariantFromUInt32(0xDEADBABE, &vVec[0]); + InitPropVariantFromInt16(1342, &vVec[1]); + + v.vt = VT_VECTOR|VT_VARIANT; + v.u.capropvar.cElems = sizeof(vVec)/sizeof(vVec[0]); + v.u.capropvar.pElems = vVec; + + CHECK_SERIALIZE("VARIANT vector", &v, "\014\020\000\000\002\000\000\000\023\000\000\000\276\272\255\336\002\000\000\000\076\005\000\000"); + } + + /* BLOB */ + { + PROPVARIANT vBlob; + char buffer[] = "Hello, World"; + + vBlob.vt = VT_BLOB; + vBlob.u.blob.cbSize = sizeof(buffer); + vBlob.u.blob.pBlobData = (BYTE*)buffer; + + CHECK_SERIALIZE("BLOB", &vBlob, "A\000\000\000\015\000\000\000Hello, World\000\000\000\000"); + } +} + START_TEST(propsys) { test_PSStringFromPropertyKey(); @@ -876,4 +1252,5 @@ START_TEST(propsys) test_PropVariantToGUID(); test_PropVariantCompare(); test_intconversions(); + test_serialization(); } diff --git a/include/propidl.idl b/include/propidl.idl index fbe80d8..c4acb42 100644 --- a/include/propidl.idl +++ b/include/propidl.idl @@ -85,6 +85,7 @@ interface IPropertyStorage : IUnknown type *pElems; \ } name
+ TYPEDEF_CA(char, CAC); TYPEDEF_CA(unsigned char, CAUB); TYPEDEF_CA(short, CAI); TYPEDEF_CA(USHORT, CAUI); @@ -137,10 +138,13 @@ interface IPropertyStorage : IUnknown [case(VT_CF)] CLIPDATA *pclipdata; [case(VT_STREAM, VT_STREAMED_OBJECT)] IStream *pStream; [case(VT_STORAGE, VT_STORED_OBJECT)] IStorage *pStorage; + [case(VT_DISPATCH)] IDispatch *pdispVal; + [case(VT_UNKNOWN)] IUnknown *punkVal; [case(VT_BSTR)] BSTR bstrVal; [case(VT_BSTR_BLOB)] BSTRBLOB bstrblobVal; [case(VT_LPSTR)] LPSTR pszVal; [case(VT_LPWSTR)] LPWSTR pwszVal; + [case(VT_I1|VT_VECTOR)] CAC cac; [case(VT_UI1|VT_VECTOR)] CAUB caub; [case(VT_I2|VT_VECTOR)] CAI cai; [case(VT_UI2|VT_VECTOR)] CAUI caui; @@ -439,6 +443,12 @@ typedef struct SERIALIZEDPROPERTYVALUE { cpp_quote("HRESULT WINAPI FreePropVariantArray(ULONG,PROPVARIANT*);") cpp_quote("HRESULT WINAPI PropVariantClear(PROPVARIANT*);") cpp_quote("HRESULT WINAPI PropVariantCopy(PROPVARIANT*,const PROPVARIANT*);") +cpp_quote("SERIALIZEDPROPERTYVALUE* WINAPI StgConvertVariantToProperty(const PROPVARIANT *pvar, USHORT CodePage, SERIALIZEDPROPERTYVALUE *pprop, ULONG *pcb, PROPID pid, BOOLEAN fReserved, ULONG *pcIndirect);") +cpp_quote("BOOLEAN WINAPI StgConvertPropertyToVariant(const SERIALIZEDPROPERTYVALUE* prop, USHORT CodePage, PROPVARIANT* pvar, void* pma);") +cpp_quote("") +cpp_quote("HRESULT WINAPI wine_ReadProperty(PROPVARIANT *prop, const BYTE *data, ULONG cbData, ULONG *pcbDataLeft, UINT diskCodePage, UINT runningCodePage, void* (WINAPI *allocate)(void *this, ULONG size), void *allocate_data);") +cpp_quote("HRESULT WINAPI wine_SerializedPropertySize(const PROPVARIANT *in_prop, ULONG *pcbSize, UINT diskCodePage, UINT runningCodePage);") +cpp_quote("HRESULT WINAPI wine_WritePropertyToBuffer(const PROPVARIANT *in_prop, BYTE *buffer, ULONG cbBufSize, ULONG *pcbBufSizeUsed, UINT diskCodePage, UINT runningCodePage);") cpp_quote("") cpp_quote("#define _PROPVARIANT_INIT_DEFINED_") cpp_quote("#define PropVariantInit(p) memset((p), 0, sizeof(PROPVARIANT))") diff --git a/include/propvarutil.h b/include/propvarutil.h index 4791543..a2ec2af 100644 --- a/include/propvarutil.h +++ b/include/propvarutil.h @@ -21,6 +21,7 @@
#include <shtypes.h> #include <shlwapi.h> +#include <propidl.h>
enum tagPROPVAR_CHANGE_FLAGS { @@ -63,6 +64,7 @@ HRESULT WINAPI PropVariantChangeType(PROPVARIANT *ppropvarDest, REFPROPVARIANT p PROPVAR_CHANGE_FLAGS flags, VARTYPE vt); HRESULT WINAPI InitPropVariantFromGUIDAsString(REFGUID guid, PROPVARIANT *ppropvar); HRESULT WINAPI InitVariantFromGUIDAsString(REFGUID guid, VARIANT *pvar); +HRESULT WINAPI InitPropVariantFromCLSID(REFCLSID clsid, PROPVARIANT *ppropvar); HRESULT WINAPI InitPropVariantFromBuffer(const VOID *pv, UINT cb, PROPVARIANT *ppropvar); HRESULT WINAPI InitVariantFromBuffer(const VOID *pv, UINT cb, VARIANT *pvar); HRESULT WINAPI PropVariantToGUID(const PROPVARIANT *ppropvar, GUID *guid); @@ -77,42 +79,117 @@ HRESULT WINAPI PropVariantToUInt16(REFPROPVARIANT propvarIn, USHORT *ret); HRESULT WINAPI PropVariantToUInt32(REFPROPVARIANT propvarIn, ULONG *ret); HRESULT WINAPI PropVariantToUInt64(REFPROPVARIANT propvarIn, ULONGLONG *ret);
-#ifdef __cplusplus - +#ifdef NO_PROPVAR_INLINES HRESULT InitPropVariantFromBoolean(BOOL fVal, PROPVARIANT *ppropvar); HRESULT InitPropVariantFromString(PCWSTR psz, PROPVARIANT *ppropvar); HRESULT InitPropVariantFromInt64(LONGLONG llVal, PROPVARIANT *ppropvar); +#endif
+HRESULT WINAPI InitPropVariantFromInt16Vector(const SHORT *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromUInt16Vector(const USHORT *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromInt32Vector(const LONG *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromUInt32Vector(const ULONG *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromInt64Vector(const LONGLONG *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromUInt64Vector(const ULONGLONG *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromBooleanVector(const BOOL *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromFileTime(const FILETIME *pft, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromFileTimeVector(const FILETIME *pv, ULONG c, PROPVARIANT *pprop); +HRESULT WINAPI InitPropVariantFromStringVector(const WCHAR **pstr, ULONG c, PROPVARIANT *pprop); + +HRESULT WINAPI StgSerializePropVariant(const PROPVARIANT *ppropvar, + SERIALIZEDPROPERTYVALUE **ppProp, + ULONG *pcb); +HRESULT WINAPI StgDeserializePropVariant(const SERIALIZEDPROPERTYVALUE *pprop, + ULONG cbMax, + PROPVARIANT *ppropvar); + + +/* FIXME: Make this available only if the compiler supports the inline keyword */ #ifndef NO_PROPVAR_INLINES
-inline HRESULT InitPropVariantFromBoolean(BOOL fVal, PROPVARIANT *ppropvar) +#ifdef NONAMELESSUNION +# define PROPVARIANT_U(x) (x).u +#else +# define PROPVARIANT_U(x) (x) +#endif + +static inline HRESULT InitPropVariantFromInt16(SHORT val, PROPVARIANT *ppropvar) +{ + ppropvar->vt = VT_I2; + PROPVARIANT_U(*ppropvar).iVal = val; + return S_OK; +} + +static inline HRESULT InitPropVariantFromUInt16(USHORT val, PROPVARIANT *ppropvar) +{ + ppropvar->vt = VT_UI2; + PROPVARIANT_U(*ppropvar).uiVal = val; + return S_OK; +} + +static inline HRESULT InitPropVariantFromInt32(LONG val, PROPVARIANT *ppropvar) +{ + ppropvar->vt = VT_I4; + PROPVARIANT_U(*ppropvar).lVal = val; + return S_OK; +} + +static inline HRESULT InitPropVariantFromUInt32(ULONG val, PROPVARIANT *ppropvar) +{ + ppropvar->vt = VT_UI4; + PROPVARIANT_U(*ppropvar).ulVal = val; + return S_OK; +} + +static inline HRESULT InitPropVariantFromBoolean(BOOL fVal, PROPVARIANT *ppropvar) { ppropvar->vt = VT_BOOL; - ppropvar->boolVal = fVal ? VARIANT_TRUE : VARIANT_FALSE; + PROPVARIANT_U(*ppropvar).boolVal = fVal ? VARIANT_TRUE : VARIANT_FALSE; return S_OK; }
-inline HRESULT InitPropVariantFromString(PCWSTR psz, PROPVARIANT *ppropvar) +static inline HRESULT InitPropVariantFromString(PCWSTR psz, PROPVARIANT *ppropvar) { - HRESULT hres; + ULONG len;
- hres = SHStrDupW(psz, &ppropvar->pwszVal); - if(SUCCEEDED(hres)) + len = lstrlenW(psz) + 1; + PROPVARIANT_U(*ppropvar).pwszVal = CoTaskMemAlloc(len * sizeof(WCHAR)); + if (PROPVARIANT_U(*ppropvar).pwszVal) + { ppropvar->vt = VT_LPWSTR; + memcpy(PROPVARIANT_U(*ppropvar).pwszVal, psz, len * sizeof(WCHAR)); + + return S_OK; + } else + { PropVariantInit(ppropvar); - - return hres; + return E_OUTOFMEMORY; + } }
-inline HRESULT InitPropVariantFromInt64(LONGLONG llVal, PROPVARIANT *ppropvar) +static inline HRESULT InitPropVariantFromInt64(LONGLONG llVal, PROPVARIANT *ppropvar) { ppropvar->vt = VT_I8; - ppropvar->hVal.QuadPart = llVal; + PROPVARIANT_U(*ppropvar).hVal.QuadPart = llVal; return S_OK; }
-#endif + +static inline HRESULT InitPropVariantFromUInt64(ULONGLONG val, PROPVARIANT *ppropvar) +{ + ppropvar->vt = VT_UI8; + PROPVARIANT_U(*ppropvar).uhVal.QuadPart = val; + return S_OK; +} + +static inline INT PropVariantCompare(REFPROPVARIANT v1, REFPROPVARIANT v2) +{ + return PropVariantCompareEx(v1, v2, PVCU_DEFAULT, PVCF_DEFAULT); +} + +#undef PROPVARIANT_U + #endif
#endif /* __WINE_PROPVARUTIL_H */
The in-memory property store now implements the INamedPropertyStore, IPersistSerializedPropStorage(2), IPersistStream interfaces. --- dlls/propsys/propstore.c | 1088 +++++++++++++++++++++++++++++++++++++- dlls/propsys/propsys_classes.idl | 7 +- dlls/propsys/tests/Makefile.in | 2 +- dlls/propsys/tests/propstore.c | 400 +++++++++++++- dlls/propsys/tests/propsys.c | 3 +- 5 files changed, 1478 insertions(+), 22 deletions(-)
diff --git a/dlls/propsys/propstore.c b/dlls/propsys/propstore.c index 9c848fc..7dd36cb 100644 --- a/dlls/propsys/propstore.c +++ b/dlls/propsys/propstore.c @@ -2,6 +2,7 @@ * standard IPropertyStore implementation * * Copyright 2012 Vincent Povirk for CodeWeavers + * Copyright 2015 Jonas Kümmerlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -28,9 +29,11 @@ #include "objbase.h" #include "rpcproxy.h" #include "propsys.h" +#include "propvarutil.h" #include "wine/debug.h" #include "wine/unicode.h" #include "wine/list.h" +#include "mimeole.h"
#include "initguid.h" #include "propsys_private.h" @@ -53,11 +56,22 @@ typedef struct { DWORD count; } propstore_format;
+/* FIXME: We should really do something clever, like using a hashtable */ +typedef struct { + struct list entry; + PROPVARIANT propvar; + WCHAR name[1]; +} propstore_named_value; + typedef struct { IPropertyStoreCache IPropertyStoreCache_iface; + IPersistSerializedPropStorage2 IPersistSerializedPropStorage2_iface; + INamedPropertyStore INamedPropertyStore_iface; + IPersistStream IPersistStream_iface; LONG ref; CRITICAL_SECTION lock; struct list formats; /* list of struct propstore_format */ + struct list named; /* list of struct propstore_named_value */ } PropertyStore;
static inline PropertyStore *impl_from_IPropertyStoreCache(IPropertyStoreCache *iface) @@ -65,12 +79,23 @@ static inline PropertyStore *impl_from_IPropertyStoreCache(IPropertyStoreCache * return CONTAINING_RECORD(iface, PropertyStore, IPropertyStoreCache_iface); }
-static HRESULT WINAPI PropertyStore_QueryInterface(IPropertyStoreCache *iface, REFIID iid, - void **ppv) +static inline PropertyStore *impl_from_IPersistSerializedPropStorage2(IPersistSerializedPropStorage2 *iface) { - PropertyStore *This = impl_from_IPropertyStoreCache(iface); - TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv); + return CONTAINING_RECORD(iface, PropertyStore, IPersistSerializedPropStorage2_iface); +}
+static inline PropertyStore *impl_from_INamedPropertyStore(INamedPropertyStore *iface) +{ + return CONTAINING_RECORD(iface, PropertyStore, INamedPropertyStore_iface); +} + +static inline PropertyStore *impl_from_IPersistStream(IPersistStream *iface) +{ + return CONTAINING_RECORD(iface, PropertyStore, IPersistStream_iface); +} + +static HRESULT query_interface(PropertyStore *This, REFIID iid, void **ppv) +{ if (!ppv) return E_INVALIDARG;
if (IsEqualIID(&IID_IUnknown, iid) || IsEqualIID(&IID_IPropertyStore, iid) || @@ -78,6 +103,19 @@ static HRESULT WINAPI PropertyStore_QueryInterface(IPropertyStoreCache *iface, R { *ppv = &This->IPropertyStoreCache_iface; } + else if (IsEqualIID(&IID_IPersistSerializedPropStorage, iid) + || IsEqualIID(&IID_IPersistSerializedPropStorage2, iid)) + { + *ppv = &This->IPersistSerializedPropStorage2_iface; + } + else if (IsEqualIID(&IID_INamedPropertyStore, iid)) + { + *ppv = &This->INamedPropertyStore_iface; + } + else if (IsEqualIID(&IID_IPersistStream, iid) || IsEqualIID(&IID_IPersist, iid)) + { + *ppv = &This->IPersistStream_iface; + } else { FIXME("No interface for %s\n", debugstr_guid(iid)); @@ -89,10 +127,85 @@ static HRESULT WINAPI PropertyStore_QueryInterface(IPropertyStoreCache *iface, R return S_OK; }
-static ULONG WINAPI PropertyStore_AddRef(IPropertyStoreCache *iface) +static HRESULT WINAPI PropertyStore_IPropertyStoreCache_QueryInterface( + IPropertyStoreCache *iface, REFIID iid, void **ppv) +{ + PropertyStore *This = impl_from_IPropertyStoreCache(iface); + + TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv); + + return query_interface(This, iid, ppv); +} + +static HRESULT WINAPI PropertyStore_IPersistSerializedPropStorage_QueryInterface( + IPersistSerializedPropStorage2 *iface, REFIID iid, void **ppv) +{ + PropertyStore *This = impl_from_IPersistSerializedPropStorage2(iface); + + TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv); + + return query_interface(This, iid, ppv); +} + +static HRESULT WINAPI PropertyStore_INamedPropertyStore_QueryInterface( + INamedPropertyStore *iface, REFIID iid, void **ppv) +{ + PropertyStore *This = impl_from_INamedPropertyStore(iface); + + TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv); + + return query_interface(This, iid, ppv); +} + +static HRESULT WINAPI PropertyStore_IPersistStream_QueryInterface( + IPersistStream *iface, REFIID iid, void **ppv) +{ + PropertyStore *This = impl_from_IPersistStream(iface); + + TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv); + + return query_interface(This, iid, ppv); +} + +static ULONG add_ref(PropertyStore *This) +{ + return InterlockedIncrement(&This->ref); +} + +static ULONG WINAPI PropertyStore_IPropertyStoreCache_AddRef(IPropertyStoreCache *iface) { PropertyStore *This = impl_from_IPropertyStoreCache(iface); - ULONG ref = InterlockedIncrement(&This->ref); + ULONG ref = add_ref(This); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static ULONG WINAPI PropertyStore_IPersistSerializedPropStorage_AddRef(IPersistSerializedPropStorage2 *iface) +{ + PropertyStore *This = impl_from_IPersistSerializedPropStorage2(iface); + ULONG ref = add_ref(This); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static ULONG WINAPI PropertyStore_INamedPropertyStore_AddRef(INamedPropertyStore *iface) +{ + PropertyStore *This = impl_from_INamedPropertyStore(iface); + ULONG ref = add_ref(This); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static ULONG WINAPI PropertyStore_IPersistStream_AddRef(IPersistStream *iface) +{ + PropertyStore *This = impl_from_IPersistStream(iface); + ULONG ref = add_ref(This);
TRACE("(%p) refcount=%u\n", iface, ref);
@@ -110,26 +223,73 @@ static void destroy_format(propstore_format *format) HeapFree(GetProcessHeap(), 0, format); }
-static ULONG WINAPI PropertyStore_Release(IPropertyStoreCache *iface) +static void destroy_named(propstore_named_value *value) { - PropertyStore *This = impl_from_IPropertyStoreCache(iface); - ULONG ref = InterlockedDecrement(&This->ref); + PropVariantClear(&value->propvar); + HeapFree(GetProcessHeap(), 0, value); +}
- TRACE("(%p) refcount=%u\n", iface, ref); +static ULONG WINAPI release(PropertyStore *This) +{ + ULONG ref = InterlockedDecrement(&This->ref);
if (ref == 0) { propstore_format *cursor, *cursor2; + propstore_named_value *cnamed, *cnamed2; + This->lock.DebugInfo->Spare[0] = 0; DeleteCriticalSection(&This->lock); LIST_FOR_EACH_ENTRY_SAFE(cursor, cursor2, &This->formats, propstore_format, entry) destroy_format(cursor); + LIST_FOR_EACH_ENTRY_SAFE(cnamed, cnamed2, &This->named, propstore_named_value, entry) + destroy_named(cnamed); HeapFree(GetProcessHeap(), 0, This); }
return ref; }
+static ULONG WINAPI PropertyStore_IPropertyStoreCache_Release(IPropertyStoreCache *iface) +{ + PropertyStore *This = impl_from_IPropertyStoreCache(iface); + ULONG ref = release(This); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static ULONG WINAPI PropertyStore_IPersistSerializedPropStorage_Release(IPersistSerializedPropStorage2 *iface) +{ + PropertyStore *This = impl_from_IPersistSerializedPropStorage2(iface); + ULONG ref = release(This); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static ULONG WINAPI PropertyStore_INamedPropertyStore_Release(INamedPropertyStore *iface) +{ + PropertyStore *This = impl_from_INamedPropertyStore(iface); + ULONG ref = release(This); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static ULONG WINAPI PropertyStore_IPersistStream_Release(IPersistStream *iface) +{ + PropertyStore *This = impl_from_IPersistStream(iface); + ULONG ref = release(This); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + static HRESULT WINAPI PropertyStore_GetCount(IPropertyStoreCache *iface, DWORD *cProps) { @@ -153,6 +313,29 @@ static HRESULT WINAPI PropertyStore_GetCount(IPropertyStoreCache *iface, return S_OK; }
+static HRESULT WINAPI PropertyStore_GetNameCount(INamedPropertyStore *iface, + DWORD *cProps) +{ + PropertyStore *This = impl_from_INamedPropertyStore(iface); + propstore_named_value *value; + + TRACE("%p,%p\n", iface, cProps); + + if (!cProps) + return E_POINTER; + + *cProps = 0; + + EnterCriticalSection(&This->lock); + + LIST_FOR_EACH_ENTRY(value, &This->named, propstore_named_value, entry) + *cProps += 1; + + LeaveCriticalSection(&This->lock); + + return S_OK; +} + static HRESULT WINAPI PropertyStore_GetAt(IPropertyStoreCache *iface, DWORD iProp, PROPERTYKEY *pkey) { @@ -203,6 +386,47 @@ static HRESULT WINAPI PropertyStore_GetAt(IPropertyStoreCache *iface, return hr; }
+static HRESULT WINAPI PropertyStore_GetNameAt(INamedPropertyStore *iface, + DWORD iProp, BSTR *pname) +{ + PropertyStore *This = impl_from_INamedPropertyStore(iface); + propstore_named_value *cursor, *value = NULL; + HRESULT hr; + + TRACE("%p,%d,%p\n", iface, iProp, pname); + + if (!pname) + return E_POINTER; + + EnterCriticalSection(&This->lock); + + LIST_FOR_EACH_ENTRY(cursor, &This->named, propstore_named_value, entry) + { + if (iProp == 0) + { + value = cursor; + break; + } + + iProp -= 1; + } + + if (value) + { + *pname = SysAllocString(value->name); + if (*pname) + hr = S_OK; + else + hr = E_OUTOFMEMORY; + } + else + hr = E_INVALIDARG; + + LeaveCriticalSection(&This->lock); + + return hr; +} + static HRESULT PropertyStore_LookupValue(PropertyStore *This, REFPROPERTYKEY key, BOOL insert, propstore_value **result) { @@ -297,6 +521,42 @@ static HRESULT WINAPI PropertyStore_GetValue(IPropertyStoreCache *iface, return hr; }
+static HRESULT WINAPI PropertyStore_GetNamedValue(INamedPropertyStore *iface, + const WCHAR *name, PROPVARIANT *pv) +{ + PropertyStore *This = impl_from_INamedPropertyStore(iface); + propstore_named_value *cursor, *value = NULL; + + TRACE("%p,%p,%p\n", iface, name, pv); + + if (!name || !pv) + return E_POINTER; + + EnterCriticalSection(&This->lock); + + LIST_FOR_EACH_ENTRY(cursor, &This->named, propstore_named_value, entry) + { + if (0 == lstrcmpW(cursor->name, name)) + { + value = cursor; + break; + } + } + + if (value) + { + PropVariantCopy(pv, &value->propvar); + } + else + { + PropVariantInit(pv); + } + + LeaveCriticalSection(&This->lock); + + return S_OK; +} + static HRESULT WINAPI PropertyStore_SetValue(IPropertyStoreCache *iface, REFPROPERTYKEY key, REFPROPVARIANT propvar) { @@ -325,6 +585,59 @@ static HRESULT WINAPI PropertyStore_SetValue(IPropertyStoreCache *iface, return hr; }
+static HRESULT WINAPI PropertyStore_SetNamedValue(INamedPropertyStore *iface, + const WCHAR *name, REFPROPVARIANT propvar) +{ + PropertyStore *This = impl_from_INamedPropertyStore(iface); + propstore_named_value *cursor, *value = NULL; + HRESULT hr = S_OK; + PROPVARIANT temp; + + TRACE("%p,%p,%p\n", iface, name, propvar); + + hr = PropVariantCopy(&temp, propvar); + if (FAILED(hr)) + return hr; + + EnterCriticalSection(&This->lock); + + LIST_FOR_EACH_ENTRY(cursor, &This->named, propstore_named_value, entry) + { + if (0 == lstrcmpW(name, cursor->name)) + { + value = cursor; + break; + } + } + + if (value) + { + /* replace old value */ + PropVariantClear(&value->propvar); + value->propvar = temp; + } + else + { + /* add new value */ + ULONG namesize = (lstrlenW(name) + 1) * sizeof(WCHAR); + + value = HeapAlloc(GetProcessHeap(), 0, sizeof(propstore_named_value) + namesize); + if (value) + { + value->propvar = temp; + memcpy(value->name, name, namesize); + + list_add_head(&This->named, &value->entry); + } + else + hr = E_OUTOFMEMORY; + } + + LeaveCriticalSection(&This->lock); + + return hr; +} + static HRESULT WINAPI PropertyStore_Commit(IPropertyStoreCache *iface) { FIXME("%p: stub\n", iface); @@ -435,10 +748,719 @@ static HRESULT WINAPI PropertyStore_SetValueAndState(IPropertyStoreCache *iface, return hr; }
-static const IPropertyStoreCacheVtbl PropertyStore_Vtbl = { - PropertyStore_QueryInterface, - PropertyStore_AddRef, - PropertyStore_Release, +static HRESULT WINAPI PropertyStore_SetFlags(IPersistSerializedPropStorage2 *iface, + PERSIST_SPROPSTORE_FLAGS flags) +{ + TRACE("%p,%d: stub\n", iface, flags); + + return S_OK; +} + +static HRESULT WINAPI PropertyStore_GetPropertyStorageSize( + IPersistSerializedPropStorage2 *iface, DWORD *pcb) +{ + DWORD count; + DWORD size = 0; + HRESULT hr = S_OK; + PropertyStore *This = impl_from_IPersistSerializedPropStorage2(iface); + propstore_format *format; + propstore_value *value; + propstore_named_value *named_value; + + TRACE("%p,%p\n", iface, pcb); + + if (!pcb) + return E_POINTER; + + EnterCriticalSection(&This->lock); + + /* As a special case, an empty propstore has size 0 */ + hr = PropertyStore_GetCount(&This->IPropertyStoreCache_iface, &count); + if (FAILED(hr) || !count) + goto out; + + LIST_FOR_EACH_ENTRY(format, &This->formats, propstore_format, entry) + { + size += 4; /* DWORD Storage size */ + size += 4; /* DWORD Storage Version */ + size += 16; /* GUID fmtid */ + + LIST_FOR_EACH_ENTRY(value, &format->values, propstore_value, entry) + { + DWORD propsize; + + size += 4; /* DWORD Size */ + size += 4; /* DWORD pid */ + size += 1; /* Reserved. */ + + hr = wine_SerializedPropertySize(&value->propvar, &propsize, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + { + WARN("Property of type %d is not serializable\n", value->propvar.vt); + size = 0; + goto out; + } + + size += propsize; + } + + size += 4; /* Terminating Element */ + } + + if (!list_empty(&This->named)) + { + size += 4; /* DWORD Storage size */ + size += 4; /* DWORD Storage Version */ + size += 16; /* GUID fmtid */ + + LIST_FOR_EACH_ENTRY(named_value, &This->named, propstore_named_value, entry) + { + DWORD propsize; + DWORD namesize; + + size += 4; /* DWORD Size */ + size += 4; /* DWORD String size */ + size += 1; /* Reserved */ + + namesize = (lstrlenW(named_value->name) + 1) * sizeof(WCHAR); + + hr = wine_SerializedPropertySize(&named_value->propvar, &propsize, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + { + WARN("Property of type %d is not serializable\n", named_value->propvar.vt); + size = 0; + goto out; + } + + size += namesize + propsize; + } + + size += 4; /* Terminating element */ + } + + size += 4; /* Terminating Storage */ + +out: + LeaveCriticalSection(&This->lock); + + *pcb = size; + return hr; +} + +static inline void write_dword(BYTE *buffer, DWORD dw) +{ + buffer[0] = (dw & 0x000000FF); + buffer[1] = (dw & 0x0000FF00) >> 8; + buffer[2] = (dw & 0x00FF0000) >> 16; + buffer[3] = (dw & 0xFF000000) >> 24; +} + +static inline void write_word(BYTE *buffer, WORD w) +{ + buffer[0] = (w & 0x00FF); + buffer[1] = (w & 0xFF00) >> 8; +} + +static inline void write_clsid(BYTE *buffer, const CLSID *id) +{ + write_dword(buffer, id->Data1); + write_word(buffer + 4, id->Data2); + write_word(buffer + 6, id->Data3); + memcpy(buffer + 8, id->Data4, 8); +} + +static inline void write_wstring(BYTE *buffer, const WCHAR *str, ULONG cChar) +{ + for (; cChar; --cChar, buffer += 2, ++str) + { + write_word(buffer, (WORD)*str); + } +} + +static HRESULT WINAPI PropertyStore_GetPropertyStorageBuffer( + IPersistSerializedPropStorage2 *iface, SERIALIZEDPROPSTORAGE *psps, DWORD cb, DWORD *cbUsed) +{ + HRESULT hr = S_OK; + PropertyStore *This = impl_from_IPersistSerializedPropStorage2(iface); + propstore_format *format; + propstore_value *value; + propstore_named_value *named_value; + DWORD cb_Start = cb; + BYTE *buffer = (BYTE*)psps; + +#define REQUIRE_SPACE(cBytes) \ + do { \ + if (cBytes > cb) \ + { \ + hr = E_INVALIDARG; \ + goto out; \ + } \ + } while(0) +#define MOVE(cBytes) \ + do { \ + buffer += cBytes; \ + cb -= cBytes; \ + } while(0) + + TRACE("%p,%p,%d,%p\n", iface, psps, cb, cbUsed); + + if (!psps) + return E_POINTER; + + EnterCriticalSection(&This->lock); + + LIST_FOR_EACH_ENTRY(format, &This->formats, propstore_format, entry) + { + BYTE *pStorageSizeBuf = buffer; /* we set it afterwards */ + DWORD storageSize = 24; /* storage size, storage version, fmtid */ + + REQUIRE_SPACE(24); /* Storage size, storage version, fmtid */ + + write_dword(buffer + 4, 0x53505331); /* DWORD Version */ + write_clsid(buffer + 8, &format->fmtid); + + MOVE(24); + + LIST_FOR_EACH_ENTRY(value, &format->values, propstore_value, entry) + { + DWORD propsize; + DWORD propsizeUsed; + + hr = wine_SerializedPropertySize(&value->propvar, &propsize, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + { + WARN("Property of type %d is not serializable\n", value->propvar.vt); + goto out; + } + + REQUIRE_SPACE(9 + propsize); /* Size, pid, reserved, content */ + + write_dword(buffer, propsize + 9); /* size */ + write_dword(buffer + 4, value->pid); /* pid */ + buffer[8] = 0; /* reserved byte to make sure everything is misaligned */ + + MOVE(9); + + hr = wine_WritePropertyToBuffer(&value->propvar, buffer, + propsize, &propsizeUsed, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + { + WARN("Couldn't serialize property of type %d, hr=%08x\n", + value->propvar.vt, (unsigned)hr); + goto out; + } + + if (propsizeUsed != propsize) + { + WARN("Serialized property size calculation was off by %u bytes\n", + propsize - propsizeUsed); + } + + MOVE(propsize); + storageSize += propsize + 9; + } + + /* Terminating Element */ + REQUIRE_SPACE(4); + ZeroMemory(buffer, 4); + MOVE(4); + storageSize += 4; + + write_dword(pStorageSizeBuf, storageSize); + } + + if (!list_empty(&This->named)) + { + BYTE *pStorageSizeBuf = buffer; /* we set it afterwards */ + DWORD storageSize = 24; /* storage size, storage version, fmtid */ + + REQUIRE_SPACE(24); /* Storage size, storage version, fmtid */ + + write_dword(buffer + 4, 0x53505331); /* DWORD Version */ + write_clsid(buffer + 8, &FMTID_NamedProperties); + + MOVE(24); + + LIST_FOR_EACH_ENTRY(named_value, &This->named, propstore_named_value, entry) + { + DWORD propsize; + DWORD propsizeUsed; + DWORD namesize; + + namesize = (lstrlenW(named_value->name) + 1) * sizeof(WCHAR); + TRACE("Writing named property with name %s:%u\n", wine_dbgstr_w(named_value->name), namesize); + + hr = wine_SerializedPropertySize(&named_value->propvar, &propsize, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + { + WARN("Property of type %d is not serializable\n", named_value->propvar.vt); + goto out; + } + + REQUIRE_SPACE(9 + namesize + propsize); + + write_dword(buffer, 9 + namesize + propsize); + write_dword(buffer + 4, namesize); + buffer[8] = 0; + + MOVE(9); + + write_wstring(buffer, named_value->name, namesize/2); + MOVE(namesize); + + hr = wine_WritePropertyToBuffer(&named_value->propvar, buffer, + propsize, &propsizeUsed, CP_UNICODE, CP_ACP); + if (FAILED(hr)) + { + WARN("Couldn't serialize property of type %d, hr=%08x\n", + named_value->propvar.vt, (unsigned)hr); + goto out; + } + + if (propsizeUsed != propsize) + { + WARN("Serialized property size calculation was off by %u bytes\n", + propsize - propsizeUsed); + } + + MOVE(propsize); + storageSize += namesize + propsize + 9; + } + + /* Terminating Element */ + REQUIRE_SPACE(4); + ZeroMemory(buffer, 4); + MOVE(4); + storageSize += 4; + + write_dword(pStorageSizeBuf, storageSize); + } + + /* Terminating Storage */ + REQUIRE_SPACE(4); + ZeroMemory(buffer, 4); + MOVE(4); + +#undef REQUIRE_SPACE +#undef MOVE + +out: + LeaveCriticalSection(&This->lock); + + if (cbUsed) + *cbUsed = SUCCEEDED(hr) ? cb_Start - cb : 0; + + return hr; +} + +static HRESULT WINAPI PropertyStore_GetPropertyStorage( + IPersistSerializedPropStorage2 *iface, SERIALIZEDPROPSTORAGE **ppsps, DWORD *pcb) +{ + HRESULT hr; + DWORD size; + DWORD usedSize; + PropertyStore *This = impl_from_IPersistSerializedPropStorage2(iface); + + TRACE("%p,%p,%p\n", iface, ppsps, pcb); + + if (!ppsps || !pcb) + return E_POINTER; + + EnterCriticalSection(&This->lock); + + hr = PropertyStore_GetPropertyStorageSize(iface, &size); + + if (SUCCEEDED(hr)) + { + if (size == 0) + { + usedSize = 0; + *ppsps = NULL; + } + else if ((*ppsps = CoTaskMemAlloc(size))) + { + hr = PropertyStore_GetPropertyStorageBuffer(iface, *ppsps, size, &usedSize); + if (FAILED(hr)) + { + CoTaskMemFree(*ppsps); + } + + if (size != usedSize) + { + WARN("Buffer calculation was off by %u bytes\n", size - usedSize); + } + } + else + { + hr = E_OUTOFMEMORY; + } + } + + LeaveCriticalSection(&This->lock); + + if (SUCCEEDED(hr)) + { + *pcb = usedSize; + } + else + { + *ppsps = NULL; + *pcb = 0; + } + + return hr; +} + +static inline void read_dword(const BYTE *buffer, DWORD *dw) +{ + *dw = buffer[0] + | (buffer[1] << 8) + | (buffer[2] << 16) + | (buffer[3] << 24); +} + +static inline void read_word(const BYTE *buffer, WORD *w) +{ + *w = buffer[0] | (buffer[1] << 8); +} + +static inline void read_clsid(const BYTE *buffer, CLSID *id) +{ + read_dword(buffer, &id->Data1); + read_word(buffer + 4, &id->Data2); + read_word(buffer + 6, &id->Data3); + memcpy(id->Data4, buffer + 8, 8); +} + +static inline void read_wstring(const BYTE *buffer, WCHAR *str, ULONG cChar) +{ + for (; cChar; --cChar, ++str, buffer += 2) + { + read_word(buffer, (WORD*)str); + } +} + +static HRESULT WINAPI PropertyStore_SetPropertyStorage( + IPersistSerializedPropStorage2 *iface, const SERIALIZEDPROPSTORAGE *psps, DWORD cb) +{ + HRESULT hr = S_OK; + PropertyStore *This = impl_from_IPersistSerializedPropStorage2(iface); + const BYTE *buffer = (const BYTE*)psps; + +#define REQUIRE_SPACE(cBytes) \ + do { \ + if (cBytes > cb) \ + { \ + WARN("Trying to read %u bytes at offset %u where only %u are available\n", \ + cBytes, (unsigned)(buffer - (const BYTE*)psps), cb); \ + hr = E_INVALIDARG; \ + goto out; \ + } \ + } while(0) +#define MOVE(cBytes) \ + do { \ + buffer += cBytes; \ + cb -= cBytes; \ + } while(0) + + TRACE("%p,%p,%d\n", iface, psps, cb); + + /*FIXME: Should we clear existing properties in the store? */ + + if (!psps && !cb) /* special case: empty storage */ + return S_OK; + + if (!psps) + return E_POINTER; + + EnterCriticalSection(&This->lock); + + for (;;) + { + DWORD storageSize; + DWORD version; + CLSID fmtid; + + REQUIRE_SPACE(4); + + /* Read size field */ + read_dword(buffer, &storageSize); + + if (storageSize == 0) /* final element */ + { + MOVE(4); + break; + } + + REQUIRE_SPACE(24); + read_dword(buffer + 4, &version); + read_clsid(buffer + 8, &fmtid); + + if (version != 0x53505331) + { + WARN("Found invalid version 0x%X\n", version); + hr = E_INVALIDARG; + goto out; + } + + MOVE(24); + + if (IsEqualGUID(&fmtid, &FMTID_NamedProperties)) + { + for (;;) + { + DWORD propsize; + DWORD namesize; + PROPVARIANT prop; + WCHAR *name; + + REQUIRE_SPACE(4); + read_dword(buffer, &propsize); + + if (propsize == 0) /* final element */ + { + MOVE(4); + break; + } + + REQUIRE_SPACE(9); + read_dword(buffer + 4, &namesize); + + if (namesize % 2 || namesize > (propsize - 9 - 4)) + { + WARN("unicode string has invalid number of bytes\n"); + hr = E_INVALIDARG; + goto out; + } + + if (buffer[8]) + WARN("reserved byte should be zero, but is 0x%X\n", (unsigned)buffer[4]); + + REQUIRE_SPACE(propsize); /* includes header */ + MOVE(9); + + /* read the name */ + name = CoTaskMemAlloc(namesize + sizeof(WCHAR)); + if (!name) + { + WARN("not enough memory for reading name\n"); + hr = E_OUTOFMEMORY; + goto out; + } + + read_wstring(buffer, name, namesize/2); + name[namesize/2] = 0; /* just to be safe */ + MOVE(namesize); + + /* read the property */ + hr = StgDeserializePropVariant( + (const SERIALIZEDPROPERTYVALUE *)buffer, propsize - 9, &prop); + if (FAILED(hr)) + { + WARN("Couldn't deserialize property, hr=%08x\n", (unsigned)hr); + CoTaskMemFree(name); + goto out; + } + + /* add it to the store */ + hr = PropertyStore_SetNamedValue(&This->INamedPropertyStore_iface, name, &prop); + PropVariantClear(&prop); + CoTaskMemFree(name); + + if (FAILED(hr)) + { + WARN("Couldn't save deserialized property in store, hr=%08x\n", (unsigned)hr); + goto out; + } + + MOVE(propsize - namesize - 9); + } + } + else + { + for (;;) + { + DWORD propsize; + DWORD pid; + PROPVARIANT prop; + PROPERTYKEY key; + + REQUIRE_SPACE(4); + read_dword(buffer, &propsize); + + if (propsize == 0) /* final element */ + { + MOVE(4); + break; + } + + REQUIRE_SPACE(9); + read_dword(buffer + 4, &pid); + + if (buffer[8]) + WARN("reserved byte should be zero, but is 0x%X\n", (unsigned)buffer[4]); + + REQUIRE_SPACE(propsize); /* includes header */ + MOVE(9); + + /* read the property */ + hr = StgDeserializePropVariant( + (const SERIALIZEDPROPERTYVALUE *)buffer, propsize - 9, &prop); + if (FAILED(hr)) + { + WARN("Couldn't deserialize property, hr=%08x\n", (unsigned)hr); + goto out; + } + + /* add it to the store */ + key.fmtid = fmtid; + key.pid = pid; + + hr = PropertyStore_SetValue(&This->IPropertyStoreCache_iface, &key, &prop); + PropVariantClear(&prop); + + if (FAILED(hr)) + { + WARN("Couldn't save deserialized property in store, hr=%08x\n", (unsigned)hr); + goto out; + } + + MOVE(propsize - 9); + } + } + } + +#undef REQUIRE_SPACE +#undef MOVE + +out: + LeaveCriticalSection(&This->lock); + + return hr; +} + +static HRESULT WINAPI PropertyStore_GetClassID(IPersistStream *iface, CLSID *pclsid) +{ + TRACE("%p,%p\n", iface, pclsid); + + if (!pclsid) + return E_POINTER; + + *pclsid = CLSID_InMemoryPropertyStore; + return S_OK; +} + +static HRESULT WINAPI PropertyStore_IsDirty(IPersistStream *iface) +{ + TRACE("%p: stub\n", iface); + + return S_FALSE; +} + +static HRESULT WINAPI PropertyStore_Load(IPersistStream *iface, IStream *stream) +{ + PropertyStore *This = impl_from_IPersistStream(iface); + HRESULT hr = S_OK; + DWORD count; + BYTE sizeBuffer[4]; + BYTE *buffer; + + TRACE("%p,%p\n", iface, stream); + + if (!stream) + return E_POINTER; + + hr = IStream_Read(stream, sizeBuffer, 4, &count); + if (hr != S_OK) + return hr; + + read_dword(sizeBuffer, &count); + buffer = CoTaskMemAlloc(count); + if (!buffer) + return E_OUTOFMEMORY; + + hr = IStream_Read(stream, buffer, count, &count); + if (hr == S_OK) + { + hr = PropertyStore_SetPropertyStorage(&This->IPersistSerializedPropStorage2_iface, + (const SERIALIZEDPROPSTORAGE *)buffer, + count); + } + + CoTaskMemFree(buffer); + + /* MSDN mandates generic error codes */ + if (hr != S_OK && hr != E_OUTOFMEMORY) + hr = E_FAIL; + + return hr; +} + +static HRESULT WINAPI PropertyStore_Save(IPersistStream *iface, + IStream *stream, + BOOL clearDirty) +{ + PropertyStore *This = impl_from_IPersistStream(iface); + HRESULT hr = S_OK; + DWORD count; + DWORD written; + BYTE sizeBuffer[4]; + SERIALIZEDPROPSTORAGE *buffer; + + TRACE("%p %p %d\n", iface, stream, (int)clearDirty); + + if (!stream) + return E_POINTER; + + hr = PropertyStore_GetPropertyStorage(&This->IPersistSerializedPropStorage2_iface, + &buffer, &count); + if (FAILED(hr)) + goto out; + + write_dword(sizeBuffer, count); + + hr = IStream_Write(stream, sizeBuffer, 4, &written); + if (hr != S_OK) + goto out; + + hr = IStream_Write(stream, buffer, count, &written); + + CoTaskMemFree(buffer); + +out: + /* MSDN mandates generic error codes */ + if (hr != S_OK && hr != STG_E_MEDIUMFULL) + hr = STG_E_CANTSAVE; + + return hr; +} + +static HRESULT WINAPI PropertyStore_GetSizeMax(IPersistStream *iface, ULARGE_INTEGER *pcbSize) +{ + PropertyStore *This = impl_from_IPersistStream(iface); + HRESULT hr = S_OK; + DWORD size; + + TRACE("%p,%p", iface, pcbSize); + + if (!pcbSize) + return E_POINTER; + + hr = PropertyStore_GetPropertyStorageSize(&This->IPersistSerializedPropStorage2_iface, + &size); + if (SUCCEEDED(hr)) + { + pcbSize->QuadPart = size; + pcbSize->QuadPart += 4; + } + + return hr; +} + +static const IPropertyStoreCacheVtbl PropertyStore_IPropertyStoreCache_Vtbl = { + PropertyStore_IPropertyStoreCache_QueryInterface, + PropertyStore_IPropertyStoreCache_AddRef, + PropertyStore_IPropertyStoreCache_Release, PropertyStore_GetCount, PropertyStore_GetAt, PropertyStore_GetValue, @@ -450,6 +1472,38 @@ static const IPropertyStoreCacheVtbl PropertyStore_Vtbl = { PropertyStore_SetValueAndState };
+static const IPersistSerializedPropStorage2Vtbl PropertyStore_IPersistSerializedPropStorage_Vtbl = { + PropertyStore_IPersistSerializedPropStorage_QueryInterface, + PropertyStore_IPersistSerializedPropStorage_AddRef, + PropertyStore_IPersistSerializedPropStorage_Release, + PropertyStore_SetFlags, + PropertyStore_SetPropertyStorage, + PropertyStore_GetPropertyStorage, + PropertyStore_GetPropertyStorageSize, + PropertyStore_GetPropertyStorageBuffer +}; + +static const INamedPropertyStoreVtbl PropertyStore_INamedPropertyStore_Vtbl = { + PropertyStore_INamedPropertyStore_QueryInterface, + PropertyStore_INamedPropertyStore_AddRef, + PropertyStore_INamedPropertyStore_Release, + PropertyStore_GetNamedValue, + PropertyStore_SetNamedValue, + PropertyStore_GetNameCount, + PropertyStore_GetNameAt +}; + +static const IPersistStreamVtbl PropertyStore_IPersistStream_Vtbl = { + PropertyStore_IPersistStream_QueryInterface, + PropertyStore_IPersistStream_AddRef, + PropertyStore_IPersistStream_Release, + PropertyStore_GetClassID, + PropertyStore_IsDirty, + PropertyStore_Load, + PropertyStore_Save, + PropertyStore_GetSizeMax +}; + HRESULT PropertyStore_CreateInstance(IUnknown *pUnkOuter, REFIID iid, void** ppv) { PropertyStore *This; @@ -464,11 +1518,15 @@ HRESULT PropertyStore_CreateInstance(IUnknown *pUnkOuter, REFIID iid, void** ppv This = HeapAlloc(GetProcessHeap(), 0, sizeof(PropertyStore)); if (!This) return E_OUTOFMEMORY;
- This->IPropertyStoreCache_iface.lpVtbl = &PropertyStore_Vtbl; + This->IPropertyStoreCache_iface.lpVtbl = &PropertyStore_IPropertyStoreCache_Vtbl; + This->IPersistSerializedPropStorage2_iface.lpVtbl = &PropertyStore_IPersistSerializedPropStorage_Vtbl; + This->INamedPropertyStore_iface.lpVtbl = &PropertyStore_INamedPropertyStore_Vtbl; + This->IPersistStream_iface.lpVtbl = &PropertyStore_IPersistStream_Vtbl; This->ref = 1; InitializeCriticalSection(&This->lock); This->lock.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": PropertyStore.lock"); list_init(&This->formats); + list_init(&This->named);
ret = IPropertyStoreCache_QueryInterface(&This->IPropertyStoreCache_iface, iid, ppv); IPropertyStoreCache_Release(&This->IPropertyStoreCache_iface); diff --git a/dlls/propsys/propsys_classes.idl b/dlls/propsys/propsys_classes.idl index 02555a3..0d5589b 100644 --- a/dlls/propsys/propsys_classes.idl +++ b/dlls/propsys/propsys_classes.idl @@ -25,4 +25,9 @@ threading(both), uuid(9a02e012-6303-4e1e-b9a1-630f802592c5) ] -coclass InMemoryPropertyStore { interface IPropertyStoreCache; } +coclass InMemoryPropertyStore { + interface IPropertyStoreCache; + interface IPersistSerializedPropStorage2; + interface INamedPropertyStore; + interface IPersistStream; +} diff --git a/dlls/propsys/tests/Makefile.in b/dlls/propsys/tests/Makefile.in index d07d675..2913673 100644 --- a/dlls/propsys/tests/Makefile.in +++ b/dlls/propsys/tests/Makefile.in @@ -1,5 +1,5 @@ TESTDLL = propsys.dll -IMPORTS = propsys ole32 oleaut32 +IMPORTS = propsys ole32 oleaut32 uuid
C_SRCS = \ propstore.c \ diff --git a/dlls/propsys/tests/propstore.c b/dlls/propsys/tests/propstore.c index 01500dd..e69ce73 100644 --- a/dlls/propsys/tests/propstore.c +++ b/dlls/propsys/tests/propstore.c @@ -2,6 +2,7 @@ * Unit tests for IPropertyStore and related interfaces * * Copyright 2012 Vincent Povirk + * Copyright 2015 Jonas Kümmerlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -30,6 +31,7 @@ #include "objbase.h" #include "propsys.h" #include "wine/test.h" +#include "propvarutil.h"
#include "initguid.h"
@@ -196,21 +198,194 @@ static void test_inmemorystore(void) IPropertyStoreCache_Release(propcache); }
-static void test_persistserialized(void) +static void test_namedpropertystore(void) { IPropertyStore *propstore; - IPersistSerializedPropStorage *serialized; + INamedPropertyStore *named; + HRESULT hr; + PROPVARIANT propvar; + const WCHAR wcsJava[] = { 'J', 'a', 'v', 'a', 0 }; + const WCHAR wcsBlub[] = { 'B', 'l', 'u', 'b', 0 }; + DWORD count; + BSTR name; + + hr = CoCreateInstance(&CLSID_InMemoryPropertyStore, NULL, CLSCTX_INPROC_SERVER, + &IID_IPropertyStore, (void**)&propstore); + ok(hr == S_OK, "CoCreateInstance failed, hr=%x\n", hr); + + hr = IPropertyStore_QueryInterface(propstore, &IID_INamedPropertyStore, + (void**)&named); + ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr); + + if (FAILED(hr)) + { + skip("IPersistSerializedPropStorage not supported\n"); + return; + } + + InitPropVariantFromUInt32(0xcafebabe, &propvar); + + hr = INamedPropertyStore_SetNamedValue(named, wcsJava, &propvar); + ok(hr == S_OK, "SetNamedValue failed, hr=%x\n", hr); + + hr = INamedPropertyStore_GetNameCount(named, &count); + ok(hr == S_OK, "GetNameCount failed, hr=%x\n", hr); + ok(count == 1, "Expected 1 element, got %u\n", count); + + hr = INamedPropertyStore_GetNameAt(named, 0, &name); + ok(hr == S_OK, "GetNameAt failed, hr=%x\n", hr); + ok(0 == lstrcmpW(name, wcsJava), "Unexpected name %s\n", wine_dbgstr_w(name)); + + SysFreeString(name); + + PropVariantInit(&propvar); + hr = INamedPropertyStore_GetNamedValue(named, wcsJava, &propvar); + ok(hr == S_OK, "GetNamedValue failed, hr=%x\n", hr); + ok(propvar.vt == VT_UI4, "Unexpected vt %d\n", propvar.vt); + ok(U(propvar).ulVal == 0xcafebabe, "Unexpected value %x\n", U(propvar).ulVal); + + InitPropVariantFromInt16(2523, &propvar); + hr = INamedPropertyStore_SetNamedValue(named, wcsBlub, &propvar); + ok(hr == S_OK, "SetNamedValue failed, hr=%x\n", hr); + + hr = INamedPropertyStore_GetNameCount(named, &count); + ok(hr == S_OK, "GetNameCount failed, hr=%x\n", hr); + ok(count == 2, "Expected 2 elements, got %u\n", count); + + InitPropVariantFromUInt32(0xdeadbeef, &propvar); + hr = INamedPropertyStore_SetNamedValue(named, wcsJava, &propvar); + ok(hr == S_OK, "SetNameValue failed, hr=%x\n", hr); + + hr = INamedPropertyStore_GetNameCount(named, &count); + ok(hr == S_OK, "GetNameCount failed, hr=%x\n", hr); + ok(count == 2, "Expected 2 elements, got %u\n", count); + + PropVariantInit(&propvar); + hr = INamedPropertyStore_GetNamedValue(named, wcsJava, &propvar); + ok(hr == S_OK, "GetNamedValue failed, hr=%x\n", hr); + ok(propvar.vt == VT_UI4, "Unexpected vt %d\n", propvar.vt); + ok(U(propvar).ulVal == 0xdeadbeef, "Unexpected value %x\n", U(propvar).ulVal); +} + +static void test_persistserialized(void) +{ + IPropertyStore *propstore = NULL; + INamedPropertyStore *named = NULL; + IPersistSerializedPropStorage *serialized = NULL; + IPersistStream *persiststream = NULL; HRESULT hr; SERIALIZEDPROPSTORAGE *result; DWORD result_size; + HGLOBAL hSerialized; + + WCHAR hello[] = { 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', 0 }; + WCHAR wcsJava[] = { 'J', 'a', 'v', 'a', 0 }; + + /* The spec doesn't impose any order. + * Since we have two properties, there are two valid serializations */ + BYTE expected_result1[] = { + /* WTF: Contrary to what the spec says, the store size field is missing! */ + + 0x45, 0x00, 0x00, 0x00, /* 1st Storage size (69 bytes) */ + + 0x31, 0x53, 0x50, 0x53, /* Version (DWORD) 0x53505331 */ + + /* fmtid GUID 0x7b317433, 0xdfa3, 0x4c44, 0xad, 0x3e, 0x2f, 0x80, 0x4b, 0x90, 0xdb, 0xf4 */ + 0x33, 0x74, 0x31, 0x7b, 0xa3, 0xdf, 0x44, 0x4c, + 0xad, 0x3e, 0x2f, 0x80, 0x4b, 0x90, 0xdb, 0xf4, + + 0x29, 0x00, 0x00, 0x00, /* Value size */ + 0x02, 0x00, 0x00, 0x00, /* PID */ + 0x00, /* random reserved byte to make everything misaligned */ + + 0x1f, 0x00, 0x00, 0x00, /* Variant Type */ + 0x0b, 0x00, 0x00, 0x00, /* String length */ + + /* UTF-16 string: "HelloWorld", padded to 4 bytes */ + 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, + 0x6f, 0x00, 0x57, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x6c, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, /* Final value with size 0 */ + + 0x37, 0x00, 0x00, 0x00, /* 2nd Storage size */ + 0x31, 0x53, 0x50, 0x53, /* Version */ + + /* fmtid named properties */ + 0x05, 0xd5, 0xcd, 0xd5, 0x9c, 0x2e, 0x1b, 0x10, + 0x93, 0x97, 0x08, 0x00, 0x2b, 0x2c, 0xf9, 0xae, + + 0x1b, 0x00, 0x00, 0x00, /* Value size */ + 0x0a, 0x00, 0x00, 0x00, /* Name size */ + 0x00, /* random reserved byte to ensure misalignment */ + + /* UTF-16 string "Java" */ + 0x4a, 0x00, 0x61, 0x00, 0x76, 0x00, 0x61, 0x00, 0x00, 0x00, + + 0x13, 0x00, 0x00, 0x00, /* Variant Type */ + 0xbe, 0xba, 0xfe, 0xca, /* Variant Value */ + + 0x00, 0x00, 0x00, 0x00, /* Final value with size 0 */ + 0x00, 0x00, 0x00, 0x00 /* Final storage with size 0 */ + }; + BYTE expected_result2[] = { + /* WTF: Contrary to what the spec says, the store size field is missing! */ + + 0x37, 0x00, 0x00, 0x00, /* 2nd Storage size */ + 0x31, 0x53, 0x50, 0x53, /* Version */ + + /* fmtid named properties */ + 0x05, 0xd5, 0xcd, 0xd5, 0x9c, 0x2e, 0x1b, 0x10, + 0x93, 0x97, 0x08, 0x00, 0x2b, 0x2c, 0xf9, 0xae, + + 0x1b, 0x00, 0x00, 0x00, /* Value size */ + 0x0a, 0x00, 0x00, 0x00, /* Name size */ + 0x00, /* random reserved byte to ensure misalignment */ + + /* UTF-16 string "Java" */ + 0x4a, 0x00, 0x61, 0x00, 0x76, 0x00, 0x61, 0x00, 0x00, 0x00, + + 0x13, 0x00, 0x00, 0x00, /* Variant Type */ + 0xbe, 0xba, 0xfe, 0xca, /* Variant Value */ + + 0x00, 0x00, 0x00, 0x00, /* Final value with size 0 */ + + 0x45, 0x00, 0x00, 0x00, /* 1st Storage size (69 bytes) */ + + 0x31, 0x53, 0x50, 0x53, /* Version (DWORD) 0x53505331 */ + + /* fmtid GUID 0x7b317433, 0xdfa3, 0x4c44, 0xad, 0x3e, 0x2f, 0x80, 0x4b, 0x90, 0xdb, 0xf4 */ + 0x33, 0x74, 0x31, 0x7b, 0xa3, 0xdf, 0x44, 0x4c, + 0xad, 0x3e, 0x2f, 0x80, 0x4b, 0x90, 0xdb, 0xf4, + + 0x29, 0x00, 0x00, 0x00, /* Value size */ + 0x02, 0x00, 0x00, 0x00, /* PID */ + 0x00, /* random reserved byte to make everything misaligned */ + + 0x1f, 0x00, 0x00, 0x00, /* Variant Type */ + 0x0b, 0x00, 0x00, 0x00, /* String length */ + + /* UTF-16 string: "HelloWorld", padded to 4 bytes */ + 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, + 0x6f, 0x00, 0x57, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x6c, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, /* Final value with size 0 */ + + 0x00, 0x00, 0x00, 0x00 /* Final storage with size 0 */ + };
hr = CoCreateInstance(&CLSID_InMemoryPropertyStore, NULL, CLSCTX_INPROC_SERVER, &IID_IPropertyStore, (void**)&propstore); ok(hr == S_OK, "CoCreateInstance failed, hr=%x\n", hr);
+ hr = IPropertyStore_QueryInterface(propstore, &IID_INamedPropertyStore, + (void**)&named); + ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr); + hr = IPropertyStore_QueryInterface(propstore, &IID_IPersistSerializedPropStorage, (void**)&serialized); - todo_wine ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr); + ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr);
if (FAILED(hr)) { @@ -244,7 +419,225 @@ static void test_persistserialized(void) ok(hr == S_OK, "GetCount failed, hr=%x\n", hr); ok(result_size == 0, "expecting 0, got %d\n", result_size);
+ { + PROPVARIANT vHello; + PROPVARIANT vUlong; + PROPERTYKEY kHello = { PKEY_WineTest, PID_FIRST_USABLE }; + + vHello.vt = VT_LPWSTR; + vHello.u.pwszVal = hello; + vUlong.vt = VT_UI4; + vUlong.u.ulVal = 0xCAFEBABE; + + hr = INamedPropertyStore_SetNamedValue(named, wcsJava, &vUlong); + ok(hr == S_OK, "SetValue failed, hr=%08x\n", hr); + + hr = IPropertyStore_SetValue(propstore, &kHello, &vHello); + ok(hr == S_OK, "SetValue failed, hr=%08x\n", hr); + } + + hr = IPersistSerializedPropStorage_GetPropertyStorage(serialized, &result, &result_size); + ok(hr == S_OK, "GetPropertyStorage failed, hr=%x\n", hr); + ok(result != NULL, "GetPropertyStorage returned NULL where it shouldn't\n"); + + /* compare the result */ + if (result) + { + BOOL same; + + same = result_size == sizeof(expected_result1) + && (!memcmp(expected_result1, result, result_size) + || !memcmp(expected_result2, result, result_size)); + ok(same, "GetPropertyStorage returned unexpected result of size %u (%u expected)\n", + result_size, (unsigned)sizeof(expected_result1)); + + if (!same) + { + size_t i; + + printf("Got result: {"); + for (i = 0; i < result_size; ++i) + { + printf("0x%02x, ", (unsigned)((BYTE*)result)[i]); + } + printf("}\n"); + } + } + + /* Load it again into a new property store */ + if (result) + { + IPropertyStore *propstore2 = NULL; + IPersistSerializedPropStorage *serialized2 = NULL; + INamedPropertyStore *named2 = NULL; + PROPVARIANT vHello; + PROPVARIANT vJava; + PROPERTYKEY key; + + hr = CoCreateInstance(&CLSID_InMemoryPropertyStore, NULL, CLSCTX_INPROC_SERVER, + &IID_IPropertyStore, (void**)&propstore2); + ok(hr == S_OK, "CoCreateInstance failed, hr=%x\n", hr); + + hr = IPropertyStore_QueryInterface(propstore2, &IID_IPersistSerializedPropStorage, + (void**)&serialized2); + ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr); + + hr = IPropertyStore_QueryInterface(propstore2, &IID_INamedPropertyStore, + (void**)&named2); + ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr); + + hr = IPersistSerializedPropStorage_SetPropertyStorage(serialized2, result, result_size); + ok(hr == S_OK, "SetPropertyStorage failed, hr=%x\n", hr); + + hr = IPropertyStore_GetCount(propstore2, &result_size); + ok(hr == S_OK, "GetCount failed, hr=%x\n", hr); + ok(result_size == 1, "expecting 1, got %d\n", result_size); + + hr = INamedPropertyStore_GetNameCount(named2, &result_size); + ok(hr == S_OK, "GetNameCount failed, hr=%x\n", hr); + ok(result_size == 1, "expecting 1, got %d\n", result_size); + + key.fmtid = PKEY_WineTest; + key.pid = PID_FIRST_USABLE; + + hr = IPropertyStore_GetValue(propstore2, &key, &vHello); + ok(hr == S_OK, "GetValue failed, hr=%x\n", hr); + + ok(vHello.vt == VT_LPWSTR, "Variant has wrong type %d\n", vHello.vt); + ok(lstrcmpW(vHello.u.pwszVal, hello) == 0, + "Variant has wrong value %s\n", wine_dbgstr_w(vHello.u.pwszVal)); + + hr = INamedPropertyStore_GetNamedValue(named2, wcsJava, &vJava); + ok(hr == S_OK, "GetNamedValue failed, hr=%x\n", hr); + + ok(vJava.vt == VT_UI4, "Variant has wrong type %d\n", vJava.vt); + ok(vJava.u.ulVal == 0xCAFEBABE, "Variant has wrong value %X\n", vJava.u.ulVal); + + PropVariantClear(&vHello); + PropVariantClear(&vJava); + + IPropertyStore_Release(propstore2); + INamedPropertyStore_Release(named2); + IPersistSerializedPropStorage_Release(serialized2); + + CoTaskMemFree(result); + } + + /* Serialize using IPersistStream */ + hr = IPropertyStore_QueryInterface(propstore, &IID_IPersistStream, (void**)&persiststream); + ok(hr == S_OK, "QueryInterface(IPersistStream) failed, hr=%x\n", hr); + + if (persiststream) + { + IStream *stream; + CLSID clazz; + BYTE *mem; + DWORD size; + + hr = CreateStreamOnHGlobal(NULL, FALSE, &stream); + ok(hr == S_OK, "Failed to create stream on HGLOBAL, hr=%x\n", hr); + + /* check the CLSID */ + hr = IPersistStream_GetClassID(persiststream, &clazz); + ok(hr == S_OK, "Failed to retrieve CLSID, hr=%x\n", hr); + ok(IsEqualGUID(&clazz, &CLSID_InMemoryPropertyStore), + "Wrong CLSID %s returned\n", wine_dbgstr_guid(&clazz)); + + hr = IPersistStream_Save(persiststream, stream, TRUE); + ok(hr == S_OK, "IPersistStream::Save failed, hr=%x\n", hr); + + /* The HGLOBAL should now contain one of the possible serializations, + * prefixed with the length of the following serialized storage. */ + hr = GetHGlobalFromStream(stream, &hSerialized); + ok(hr == S_OK, "WTF: Can't retrieve HGLOBAL from stream, hr=%x\n", hr); + + ok(GlobalSize(hSerialized)-4 == sizeof(expected_result1), + "Serialized result has invalid size %lu, expected %lu\n", + (unsigned long)GlobalSize(hSerialized), (unsigned long)sizeof(expected_result1) + 4); + + mem = GlobalLock(hSerialized); + ok(mem != NULL, "WTF: Can't lock HGLOBAL"); + + size = mem[0] | (mem[1] << 8) | (mem[2] << 16) | (mem[3] << 24); + + ok(size == sizeof(expected_result1), + "Serialized result encodes invalid size %lu, expected %lu\n", + (unsigned long)size, (unsigned long)sizeof(expected_result1)); + ok(memcmp(mem+4, expected_result1, sizeof(expected_result1)) == 0 + || memcmp(mem+4, expected_result2, sizeof(expected_result2)) == 0, + "Serialized result differs from expected result\n"); + GlobalUnlock(hSerialized); + + IPersistStream_Release(persiststream); + IStream_Release(stream); + } + + /* Deserialize using IPersistStream */ + if (hSerialized) + { + IStream *stream; + IPropertyStore *propstore2 = NULL; + IPersistStream *persiststream2 = NULL; + INamedPropertyStore *named2 = NULL; + PROPVARIANT vHello; + PROPVARIANT vJava; + PROPERTYKEY key; + + hr = CreateStreamOnHGlobal(hSerialized, FALSE, &stream); + ok(hr == S_OK, "Failed to create stream on HGLOBAL, hr=%x\n", hr); + + hr = CoCreateInstance(&CLSID_InMemoryPropertyStore, NULL, CLSCTX_INPROC_SERVER, + &IID_IPropertyStore, (void**)&propstore2); + ok(hr == S_OK, "CoCreateInstance failed, hr=%x\n", hr); + + hr = IPropertyStore_QueryInterface(propstore2, &IID_IPersistStream, + (void**)&persiststream2); + ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr); + + hr = IPropertyStore_QueryInterface(propstore2, &IID_INamedPropertyStore, + (void**)&named2); + ok(hr == S_OK, "QueryInterface failed, hr=%x\n", hr); + + hr = IPersistStream_Load(persiststream2, stream); + ok(hr == S_OK, "IPersistStream::Load failed, hr=%x\n", hr); + + hr = IPropertyStore_GetCount(propstore2, &result_size); + ok(hr == S_OK, "GetCount failed, hr=%x\n", hr); + ok(result_size == 1, "expecting 1, got %d\n", result_size); + + hr = INamedPropertyStore_GetNameCount(named2, &result_size); + ok(hr == S_OK, "GetNameCount failed, hr=%x\n", hr); + ok(result_size == 1, "expecting 1, got %d\n", result_size); + + key.fmtid = PKEY_WineTest; + key.pid = PID_FIRST_USABLE; + + hr = IPropertyStore_GetValue(propstore2, &key, &vHello); + ok(hr == S_OK, "GetValue failed, hr=%x\n", hr); + + ok(vHello.vt == VT_LPWSTR, "Variant has wrong type %d\n", vHello.vt); + ok(lstrcmpW(vHello.u.pwszVal, hello) == 0, + "Variant has wrong value %s\n", wine_dbgstr_w(vHello.u.pwszVal)); + + hr = INamedPropertyStore_GetNamedValue(named2, wcsJava, &vJava); + ok(hr == S_OK, "GetNamedValue failed, hr=%x\n", hr); + + ok(vJava.vt == VT_UI4, "Variant has wrong type %d\n", vJava.vt); + ok(vJava.u.ulVal == 0xCAFEBABE, "Variant has wrong value %X\n", vJava.u.ulVal); + + PropVariantClear(&vHello); + PropVariantClear(&vJava); + + IPropertyStore_Release(propstore2); + INamedPropertyStore_Release(named2); + IPersistStream_Release(persiststream2); + IStream_Release(stream); + + GlobalFree(hSerialized); + } + IPropertyStore_Release(propstore); + INamedPropertyStore_Release(named); IPersistSerializedPropStorage_Release(serialized); }
@@ -253,6 +646,7 @@ START_TEST(propstore) CoInitialize(NULL);
test_inmemorystore(); + test_namedpropertystore(); test_persistserialized();
CoUninitialize(); diff --git a/dlls/propsys/tests/propsys.c b/dlls/propsys/tests/propsys.c index 311d610..acc35fa 100644 --- a/dlls/propsys/tests/propsys.c +++ b/dlls/propsys/tests/propsys.c @@ -29,12 +29,11 @@ #include "windef.h" #include "winbase.h" #include "objbase.h" -#include "initguid.h" #include "propsys.h" #include "propvarutil.h" #include "wine/test.h"
-DEFINE_GUID(GUID_NULL,0,0,0,0,0,0,0,0,0,0,0); +#include "initguid.h" DEFINE_GUID(dummy_guid, 0xdeadbeef, 0xdead, 0xbeef, 0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe); DEFINE_GUID(expect_guid, 0x12345678, 0x1234, 0x1234, 0x12, 0x34, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12);
Maybe you know this, but this patch does too many things and will need to be split before sending to wine-patches.
PropertyStore_SetNamedValue leaks a copy of the value when a new propstore_named_value can't be allocated.
+ if (!psps && !cb) /* special case: empty storage */
Is it really not enough to have a size of 0? The buffer has to be NULL as well?
+ /* MSDN mandates generic error codes */
MSDN's lists of error codes aren't necessarily exhaustive.
Vincent Povirk wrote:
Maybe you know this, but this patch does too many things and will need to be split before sending to wine-patches.
I suspected that, and a rework is in progress (the patches originally eaten by the list need that badly, too).
- if (!psps && !cb) /* special case: empty storage */
Is it really not enough to have a size of 0? The buffer has to be NULL as well?
I don't think so, size 0 should be enough. I'll add a test.
- /* MSDN mandates generic error codes */
MSDN's lists of error codes aren't necessarily exhaustive.
Oops. I generally claim to be aware of this fact.
On 14.07.2015 22:47, Jonas Kümmerlin wrote:
+@ stdcall wine_WritePropertyToBuffer(ptr ptr long ptr long long) +@ stdcall wine_SerializedPropertySize(ptr ptr long long) +@ stdcall wine_ReadProperty(ptr ptr long ptr long long ptr ptr)
We avoid custom exports, unless there's absolutely no way to go without them. If you're sure public API is not enough, you should consider duplication of relevant parts.
Nikolay Sivov wrote:
On 14.07.2015 22:47, Jonas Kümmerlin wrote:
+@ stdcall wine_WritePropertyToBuffer(ptr ptr long ptr long long) +@ stdcall wine_SerializedPropertySize(ptr ptr long long) +@ stdcall wine_ReadProperty(ptr ptr long ptr long long ptr ptr)
We avoid custom exports, unless there's absolutely no way to go without them. If you're sure public API is not enough, you should consider duplication of relevant parts.
The problem at hand: * propsys.dll has to do property serialization/deserialization, at least for getting Stg(De)SerializePropVariant() to work. * Implementing it twice is not really appealing since it amounts to >1000 lines of code. * The only interface to property (de)serialization provided by ole32.dll, the StgConvertVariantToProperty()/ StgConvertPropertyToVariant() functions, are poorly documented, hard to implement (HRESULTs need to be converted to NTSTATUS exceptions) and even more unpleasant to use (because one would need to catch the NTSTATUS exception and convert it back to a HRESULT). This is rather crazy and cumbersome.
How about moving Stg(De)SerializePropVariant() into OLE32.dll and reexporting it from propsys.dll? That could solve the problem without introducing a completely new API.
On 16.07.2015 11:09, Jonas Kümmerlin wrote:
Nikolay Sivov wrote:
On 14.07.2015 22:47, Jonas Kümmerlin wrote:
+@ stdcall wine_WritePropertyToBuffer(ptr ptr long ptr long long) +@ stdcall wine_SerializedPropertySize(ptr ptr long long) +@ stdcall wine_ReadProperty(ptr ptr long ptr long long ptr ptr)
We avoid custom exports, unless there's absolutely no way to go without them. If you're sure public API is not enough, you should consider duplication of relevant parts.
The problem at hand: * propsys.dll has to do property serialization/deserialization, at least for getting Stg(De)SerializePropVariant() to work.
Sure.
* Implementing it twice is not really appealing since it amounts to >1000 lines of code.
I mentioned it as an option to consider in case no public API exists to do same job.
* The only interface to property (de)serialization provided by ole32.dll, the StgConvertVariantToProperty()/ StgConvertPropertyToVariant() functions, are poorly documented, hard to implement (HRESULTs need to be converted to NTSTATUS exceptions) and even more unpleasant to use (because one would need to catch the NTSTATUS exception and convert it back to a HRESULT). This is rather crazy and cumbersome.
I wouldn't say they are that poorly documented, all used types and arguments are documented, prototypes are known. If they actually do what you need it sounds like a best option. The fact that they raise an exception instead of returning and error code is not that uncommon, Ndr* marshalling code does the same, and it actually simplifies error handling for client if serialization implies calling them sequentially - you only need one exception handling block around all of them, once one fails you can't/shouldn't try to recover, that's the reason of such design most likely. Note though that it's irrelevant if they are actually used in native propsys - it's implementation detail, and shouldn't rely on it or explore if it's actually the case or not.
First thing to do would be to write some tests for ole32 and propsys serialization, tests should of course be separated and go to respective tests dir.
How about moving Stg(De)SerializePropVariant() into OLE32.dll and reexporting it from propsys.dll? That could solve the problem without introducing a completely new API.
That's not better than adding custom named exports as you did in your patch. The point is to have clean inter-module interface and proper separation.
* The only interface to property (de)serialization provided by ole32.dll, the StgConvertVariantToProperty()/ StgConvertPropertyToVariant() functions, are poorly documented, hard to implement (HRESULTs need to be converted to NTSTATUS exceptions) and even more unpleasant to use (because one would need to catch the NTSTATUS exception and convert it back to a HRESULT). This is rather crazy and cumbersome.
I remember being concerned that StgConvertPropertyToVariant does not accept a size for the serialized value. This is not good when we don't trust the data we're reading.
So, at least for deserialization I don't think we should use the public API.
On 16.07.2015 21:23, Vincent Povirk wrote:
* The only interface to property (de)serialization provided by ole32.dll, the StgConvertVariantToProperty()/ StgConvertPropertyToVariant() functions, are poorly documented, hard to implement (HRESULTs need to be converted to NTSTATUS exceptions) and even more unpleasant to use (because one would need to catch the NTSTATUS exception and convert it back to a HRESULT). This is rather crazy and cumbersome.
I remember being concerned that StgConvertPropertyToVariant does not accept a size for the serialized value. This is not good when we don't trust the data we're reading.
So, at least for deserialization I don't think we should use the public API.
Yes, I agree, it would be nice to have buffer length. Maybe the idea is to check some kind of a header first, could be that first DWORD is actually stream length, if that's the case it's not that bad.
I remember being concerned that StgConvertPropertyToVariant does not accept a size for the serialized value. This is not good when we don't trust the data we're reading.
So, at least for deserialization I don't think we should use the public API.
Yes, I agree, it would be nice to have buffer length. Maybe the idea is to check some kind of a header first, could be that first DWORD is actually stream length, if that's the case it's not that bad.
Sadly, no, the size and how/if it's encoded depends on the type, which is the first thing in the structure.