Changes since the last version:
- Feedback by Vincent Porvik, Nikolay Sivov and GOUJON Alexandre has been incorporated.
- Where appropriate, bigger patches have been split into smaller chunks.
- All required features are now implemented: You can try it out by installing LibreOffice using the MSI package, set the windows version to Win7 in winecfg and observe the new fancy desktop file matching performed by your desktop shell. (assuming your shell does this; Gnome3 and Unity do, XFCE and LXDE don't)
- While Gnome3 works great, KDE hasn't been exactly cooperative so far. It now gets the grouping right, but ignores the StartupWMClass key in the desktop files, so pinning and fancy high-res icons won't work. This is most likely not fixable on our side, at least not without further research.
I am still not happy with the PROPVARIANT (de)serialization code (patches 4 and 5, hope the mailing list won't eat it this time). The ole32 <-> propsys interfacing has been implemented as Nikolay Sivov suggested, but I'm not really happy with it.
The new (de)serialization code also caused a weird regression in MSI.dll which I haven't been able to track down yet.
I'm currently leaning towards moving the new code to propsys.dll and leaving ole32.dll untouched, even though that would result in two different implementations of basically the same thing. Does anyone have strong opinions on this matter?
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.
Jonas Kümmerlin (15): shell32: Implement process-wide explicit AppUserModelID propsys: Implement several PropVariantInit*() functions propsys: Extend PropVariantCompareEx() ole32: Rework property (de)serialization propsys: Implement Stg(De)SerializePropVariant propsys: Implement named properties on the in-memory property store propsys: Implement serialization of the in-memory property store propsys: Implement IPersistStream on the in-memory property store propsys: Partially implement property stringification shell32: Properly implement IPropertyStore for shell links shell32: Implement window property store (SHGetPropertyStoreForWindow) winex11.drv,user32,shell32 Use AppUserModelID to populate WM_CLASS propsys: Partially implement/stub IPropertyDescription msi: Write shortcut properties winemenubuilder: Save AppUserModelID as StartupWMClass
dlls/msi/Makefile.in | 2 +- dlls/msi/action.c | 120 ++ dlls/msi/tests/Makefile.in | 2 +- dlls/msi/tests/action.c | 75 +- dlls/msi/tests/automation.c | 1 - dlls/msi/tests/suminfo.c | 4 +- dlls/ole32/stg_prop.c | 1717 +++++++++++++++++++++++----- dlls/ole32/tests/propvariant.c | 86 +- dlls/propsys/Makefile.in | 4 +- dlls/propsys/propdesc.c | 537 +++++++++ dlls/propsys/propdesc_builtin_data.c | 85 ++ dlls/propsys/propstore.c | 1062 ++++++++++++++++- dlls/propsys/propsys.spec | 36 +- dlls/propsys/propsys_classes.idl | 7 +- dlls/propsys/propsys_main.c | 6 - dlls/propsys/propsys_private.h | 23 + dlls/propsys/propvar.c | 889 +++++++++++++- dlls/propsys/tests/Makefile.in | 2 +- dlls/propsys/tests/propstore.c | 403 ++++++- dlls/propsys/tests/propsys.c | 709 +++++++++++- dlls/shell32/Makefile.in | 1 + dlls/shell32/shell32.spec | 2 + dlls/shell32/shell32_main.c | 109 +- dlls/shell32/shelllink.c | 218 +++- dlls/shell32/tests/Makefile.in | 1 + dlls/shell32/tests/appusermodel.c | 272 +++++ dlls/shell32/tests/shell32_test.h | 9 + dlls/shell32/tests/shelllink.c | 123 ++ dlls/shell32/winpropstore.c | 303 +++++ dlls/user32/message.c | 2 +- dlls/user32/user_private.h | 5 +- dlls/winex11.drv/window.c | 160 ++- include/propidl.idl | 6 + include/propsys.idl | 1 + include/propvarutil.h | 105 +- include/shellapi.h | 1 + include/shobjidl.idl | 6 + include/strsafe.h | 17 + include/winbase.h | 2 + include/wine/server_protocol.h | 59 +- programs/winemenubuilder/winemenubuilder.c | 123 +- server/protocol.def | 38 + server/request.h | 35 + server/trace.c | 47 + server/window.c | 148 +++ 45 files changed, 7098 insertions(+), 465 deletions(-) create mode 100644 dlls/propsys/propdesc.c create mode 100644 dlls/propsys/propdesc_builtin_data.c create mode 100644 dlls/shell32/tests/appusermodel.c create mode 100644 dlls/shell32/winpropstore.c
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 | 121 ++++++++++++++++++++++++++++++++++++++ include/shobjidl.idl | 6 ++ include/winbase.h | 2 + 6 files changed, 238 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..dd83ef8 --- /dev/null +++ b/dlls/shell32/tests/appusermodel.c @@ -0,0 +1,121 @@ +/* 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" + +static HRESULT (WINAPI *pSetCurrentProcessExplicitAppUserModelID)(const WCHAR *id); +static HRESULT (WINAPI *pGetCurrentProcessExplicitAppUserModelID)(WCHAR **id); + +static void test_process_aum_id(void) +{ + HRESULT hr; + WCHAR *received = NULL; + int i; + + /* MSDN claims the maximum length to be 128 chars, but in reality, + it is 127 chars + terminating NUL byte */ + WCHAR test_id[129] = { 'W','i','n','e','.','T','e','s','t','.','A' }; + + /* fill the remainder with 'a' to test the maximum length */ + for (i = 11; i < 128; ++i) + test_id[i] = 'a'; + test_id[128] = 0; + + if (!pGetCurrentProcessExplicitAppUserModelID + || !pSetCurrentProcessExplicitAppUserModelID) + { + win_skip("SetCurrentProcessExplicitAppUserModelID is not available"); + return; + } + + /* Receiving it without setting will fail with E_FAIL */ + hr = pGetCurrentProcessExplicitAppUserModelID(&received); + ok(hr == E_FAIL, "receiving the AppUserModelID unexpected hr=%08x\n", hr); + ok(received == NULL, "AppUserModelID '%s' has been returned even though none was set\n", + wine_dbgstr_w(received)); + + if (received) + { + CoTaskMemFree(received); + received = NULL; + } + + /* Passing NULL pointers segfaults on Win7 */ + if (0) + { + hr = pSetCurrentProcessExplicitAppUserModelID(NULL); + ok(hr == E_POINTER, "unexpected hr=%08x\n", hr); + + hr = pGetCurrentProcessExplicitAppUserModelID(NULL); + ok(hr == E_POINTER, "unexpected hr=%08x\n", hr); + } + + /* Test with 128 chars + null byte */ + hr = pSetCurrentProcessExplicitAppUserModelID(test_id); + ok(hr == E_INVALIDARG, "unexpexted hr=0x%08x\n", hr); + + /* Test again, this time with 127 chars + null byte */ + test_id[127] = 0; + hr = pSetCurrentProcessExplicitAppUserModelID(test_id); + ok(hr == S_OK, "unexpexted hr=0x%08x\n", hr); + + /* Set a different value. */ + test_id[10] = 'B'; + hr = pSetCurrentProcessExplicitAppUserModelID(test_id); + ok(hr == S_OK, "unexpexted hr=0x%08x\n", hr); + + /* Retrieve it again and compare it to the latest value set. */ + hr = pGetCurrentProcessExplicitAppUserModelID(&received); + ok(hr == S_OK, "GetCurrentProcessExplicitAppUserModelID failed (0x%08x)\n", hr); + ok(received != NULL, "GetCurrentProcessExplicitAppUserModelID returned a NULL ID\n"); + + if (received) + { + ok(!lstrcmpW(received, test_id), + "Expected '%s', but got '%s'\n", + wine_dbgstr_w(test_id), wine_dbgstr_w(received)); + + CoTaskMemFree(received); + } +} + +/* TODO: Test the window property store (SHGetPropertyStoreForWindow) */ + +#define RESOLVE(hDll, proc) p##proc = (void*)GetProcAddress(hDll, #proc) + +START_TEST(appusermodel) +{ + HMODULE hShell32; + + hShell32 = GetModuleHandleA("shell32.dll"); + + RESOLVE(hShell32, SetCurrentProcessExplicitAppUserModelID); + RESOLVE(hShell32, GetCurrentProcessExplicitAppUserModelID); + + 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 */
This implements most of them and makes the inline ones available to C code, too (instead of just C++ like before) --- dlls/propsys/propsys.spec | 24 +++++------ dlls/propsys/propvar.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++ include/propvarutil.h | 90 ++++++++++++++++++++++++++++++++++------ 3 files changed, 192 insertions(+), 25 deletions(-)
diff --git a/dlls/propsys/propsys.spec b/dlls/propsys/propsys.spec index 92b6d7c..100f825 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) diff --git a/dlls/propsys/propvar.c b/dlls/propsys/propvar.c index fd22c9d..1a4d9d1 100644 --- a/dlls/propsys/propvar.c +++ b/dlls/propsys/propvar.c @@ -346,6 +346,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 +405,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; diff --git a/include/propvarutil.h b/include/propvarutil.h index 4791543..1efa248 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,104 @@ 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); + +/* 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; +} + +#undef PROPVARIANT_U + #endif
#endif /* __WINE_PROPVARUTIL_H */
Sorry for the late reply, I've been quite busy lately...
On 18/07/2015 18:26, Jonas Kümmerlin wrote:
<snip>
+HRESULT WINAPI InitPropVariantFromCLSID(REFCLSID clsid, PROPVARIANT *ppropvar) +{
- TRACE("(%p %p)\n", clsid, ppropvar);
- ppropvar->u.puuid = CoTaskMemAlloc(sizeof(CLSID));
I looked at current proposys/propvar.c and parameter check is inconsistent. We sometimes check input pointer against NULL and return E_FAIL like in InitPropVariantFromGUIDAsString. I guess it does not matter until an app depend on it but if you want to, you can add tests.
<snip>
+#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
I know AJ doesn't like macro-as-a-function and I think he's right. Would it be possible to define a helper method, write plain functions calling it and play with something like FIELD_OFFSET ? The scheme is aways the same : allocate, check error, set vt and set field. The remaining part vary from function to function. I don't know if it'd be cleaner or even feasible.
+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));
Why not use your PROPVARIANT_U here ? Note that we generally name it U (see commit 9ecc71213fea7cf1656b249ebe2cfb7c7b51927f)
+/* FIXME: Make this available only if the compiler supports the inline keyword */
inline is only a hint for the compiler and should't matter (except if you're playing/comparing with inline function address).
PropVariantCompareEx() now supports all integer types, vectors of most things and other types we plan to implement in the serialization routines.
The new comparisons aid future test cases for the (de)serialization routines. --- dlls/propsys/propvar.c | 331 ++++++++++++++++++++++++++++++++++++++++++++++--- include/propidl.idl | 4 + include/propvarutil.h | 5 + 3 files changed, 322 insertions(+), 18 deletions(-)
diff --git a/dlls/propsys/propvar.c b/dlls/propsys/propvar.c index 1a4d9d1..ae3fd2f 100644 --- a/dlls/propsys/propvar.c +++ b/dlls/propsys/propvar.c @@ -614,7 +614,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; }
@@ -658,36 +668,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); diff --git a/include/propidl.idl b/include/propidl.idl index fbe80d8..2fb8370 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; diff --git a/include/propvarutil.h b/include/propvarutil.h index 1efa248..b0a1731 100644 --- a/include/propvarutil.h +++ b/include/propvarutil.h @@ -175,6 +175,11 @@ static inline HRESULT InitPropVariantFromUInt64(ULONGLONG val, PROPVARIANT *ppro return S_OK; }
+static inline INT PropVariantCompare(REFPROPVARIANT v1, REFPROPVARIANT v2) +{ + return PropVariantCompareEx(v1, v2, PVCU_DEFAULT, PVCF_DEFAULT); +} + #undef PROPVARIANT_U
#endif
On 18.07.2015 19:26, Jonas Kümmerlin wrote:
- 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;
You sure you can't just memcmp() whole structure?
- 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;
- }
I really think common vector bits should be done in one place (after tests show they all behave same way of course), as vector structure is always the same - cElems is always a first field. Regarding strings, it's better to use flags and call proper functions from the start.
P.S. it's up to you but maybe you could submit some obviously correct parts like Init* and header changes to wine-patches so we can reduce the diff?
The new code tries to protect itself against buffer overflows and handles a lot more cases.
The implementation of StgConvertPropertyToVariant/StgConvertVariantToProperty has been improved. --- dlls/msi/tests/suminfo.c | 4 +- dlls/ole32/stg_prop.c | 1717 +++++++++++++++++++++++++++++++++------- dlls/ole32/tests/propvariant.c | 86 +- include/propidl.idl | 2 + 4 files changed, 1535 insertions(+), 274 deletions(-)
diff --git a/dlls/msi/tests/suminfo.c b/dlls/msi/tests/suminfo.c index 8c2e292..d121f75 100644 --- a/dlls/msi/tests/suminfo.c +++ b/dlls/msi/tests/suminfo.c @@ -438,7 +438,7 @@ static void test_summary_binary(void) ival = -1; r = MsiSummaryInfoGetPropertyA(hsuminfo, PID_WORDCOUNT, &type, &ival, NULL, NULL, NULL); ok(r == ERROR_SUCCESS, "MsiSummaryInfoGetProperty failed\n"); - todo_wine ok( ival == 0, "value incorrect\n"); + ok( ival == 0, "value incorrect\n");
/* looks like msi adds some of its own values in here */ count = 0; @@ -447,7 +447,7 @@ static void test_summary_binary(void) todo_wine ok(count == 10, "prop count incorrect\n");
r = MsiSummaryInfoSetPropertyA( hsuminfo, PID_TITLE, VT_LPSTR, 0, NULL, "Mike" ); - ok(r == ERROR_FUNCTION_FAILED, "MsiSummaryInfoSetProperty failed %u\n", r); + todo_wine ok(r == ERROR_FUNCTION_FAILED, "MsiSummaryInfoSetProperty failed %u\n", r);
r = MsiSummaryInfoPersist( hsuminfo ); ok(r == ERROR_FUNCTION_FAILED, "MsiSummaryInfoPersist failed %u\n", r); diff --git a/dlls/ole32/stg_prop.c b/dlls/ole32/stg_prop.c index da361ce..f818998 100644 --- a/dlls/ole32/stg_prop.c +++ b/dlls/ole32/stg_prop.c @@ -144,6 +144,17 @@ static HRESULT PropertyStorage_PropVariantCopy(PROPVARIANT *prop, static HRESULT PropertyStorage_StringCopy(LPCSTR src, LCID srcCP, LPSTR *dst, LCID targetCP);
+/* Like PropertyStorage_StringCopy, but returns the size that needs to be allocated. + */ +static HRESULT PropertyStorage_StringCopy_Size(LPCSTR src, LCID srcCP, + DWORD *dstSize, LCID targetCP); + +/* Like PropertyStorage_StringCopy, but uses a caller-supplied buffer. + */ +static HRESULT PropertyStorage_StringCopy_Buffer(LPCSTR src, LCID srcCP, + LPSTR dst, DWORD cbDst, LCID targetCP); + + static const IPropertyStorageVtbl IPropertyStorage_Vtbl; static const IEnumSTATPROPSETSTGVtbl IEnumSTATPROPSETSTG_Vtbl; static const IEnumSTATPROPSTGVtbl IEnumSTATPROPSTG_Vtbl; @@ -354,18 +365,81 @@ static HRESULT WINAPI IPropertyStorage_fnReadMultiple( return hr; }
-static HRESULT PropertyStorage_StringCopy(LPCSTR src, LCID srcCP, LPSTR *dst, - LCID dstCP) +static HRESULT PropertyStorage_StringCopy_Size(LPCSTR src, LCID srcCP, + DWORD *dstSize, LCID dstCP) { HRESULT hr = S_OK; - int len; + DWORD size;
TRACE("%s, %p, %d, %d\n", + srcCP == CP_UNICODE ? debugstr_w((LPCWSTR)src) : debugstr_a(src), + dstSize, dstCP, srcCP); + + assert(src); + assert(dstSize); + *dstSize = 0; + if (dstCP == srcCP) + { + if (dstCP == CP_UNICODE) + size = (strlenW((LPCWSTR)src) + 1) * sizeof(WCHAR); + else + size = strlen(src) + 1; + } + else + { + if (dstCP == CP_UNICODE) + { + size = MultiByteToWideChar(srcCP, 0, src, -1, NULL, 0) * sizeof(WCHAR); + } + else + { + LPCWSTR wideStr = NULL; + LPWSTR wideStr_tmp = NULL; + + if (srcCP == CP_UNICODE) + wideStr = (LPCWSTR)src; + else + { + int len = MultiByteToWideChar(srcCP, 0, src, -1, NULL, 0); + wideStr_tmp = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); + if (wideStr_tmp) + { + MultiByteToWideChar(srcCP, 0, src, -1, wideStr_tmp, len); + wideStr = wideStr_tmp; + } + else + hr = STG_E_INSUFFICIENTMEMORY; + } + if (SUCCEEDED(hr)) + { + size = WideCharToMultiByte(dstCP, 0, wideStr, -1, NULL, 0, + NULL, NULL); + } + HeapFree(GetProcessHeap(), 0, wideStr_tmp); + } + } + + TRACE("returning 0x%08x (%u)\n", hr, size); + + *dstSize = size; + return hr; +} + +/* Like PropertyStorage_StringCopy, but uses a user-supplied buffer. + */ +static HRESULT PropertyStorage_StringCopy_Buffer(LPCSTR src, LCID srcCP, + LPSTR dst, DWORD cbDst, + LCID dstCP) +{ + HRESULT hr = S_OK; + int len; + + TRACE("%s, %p, %u, %d, %d\n", srcCP == CP_UNICODE ? debugstr_w((LPCWSTR)src) : debugstr_a(src), dst, - dstCP, srcCP); + cbDst, dstCP, srcCP); assert(src); assert(dst); - *dst = NULL; + if (dstCP == srcCP) { size_t len; @@ -374,22 +448,21 @@ static HRESULT PropertyStorage_StringCopy(LPCSTR src, LCID srcCP, LPSTR *dst, len = (strlenW((LPCWSTR)src) + 1) * sizeof(WCHAR); else len = strlen(src) + 1; - *dst = CoTaskMemAlloc(len * sizeof(WCHAR)); - if (!*dst) - hr = STG_E_INSUFFICIENTMEMORY; + + if (cbDst < len) + hr = STG_E_INVALIDPARAMETER; else - memcpy(*dst, src, len); + memcpy(dst, src, len); } else { if (dstCP == CP_UNICODE) { len = MultiByteToWideChar(srcCP, 0, src, -1, NULL, 0); - *dst = CoTaskMemAlloc(len * sizeof(WCHAR)); - if (!*dst) + if (cbDst < (len * sizeof(WCHAR))) hr = STG_E_INSUFFICIENTMEMORY; else - MultiByteToWideChar(srcCP, 0, src, -1, (LPWSTR)*dst, len); + MultiByteToWideChar(srcCP, 0, src, -1, (LPWSTR)dst, len); } else { @@ -412,20 +485,24 @@ static HRESULT PropertyStorage_StringCopy(LPCSTR src, LCID srcCP, LPSTR *dst, } if (SUCCEEDED(hr)) { + TRACE("Converted to wide string %s\n", debugstr_w(wideStr)); + len = WideCharToMultiByte(dstCP, 0, wideStr, -1, NULL, 0, NULL, NULL); - *dst = CoTaskMemAlloc(len); - if (!*dst) - hr = STG_E_INSUFFICIENTMEMORY; + if (cbDst < len) + hr = STG_E_INVALIDPARAMETER; else { BOOL defCharUsed = FALSE; + BOOL *lpDefCharUsed = &defCharUsed; + + /* for CP_UTF8/CP_UTF7, we may not even supply lpDefChar */ + if (dstCP == CP_UTF8 || dstCP == CP_UTF7) + lpDefCharUsed = NULL;
- if (WideCharToMultiByte(dstCP, 0, wideStr, -1, *dst, len, - NULL, &defCharUsed) == 0 || defCharUsed) + if (WideCharToMultiByte(dstCP, 0, wideStr, -1, dst, len, + NULL, lpDefCharUsed) == 0 || defCharUsed) { - CoTaskMemFree(*dst); - *dst = NULL; hr = HRESULT_FROM_WIN32(ERROR_NO_UNICODE_TRANSLATION); } } @@ -434,7 +511,34 @@ static HRESULT PropertyStorage_StringCopy(LPCSTR src, LCID srcCP, LPSTR *dst, } } TRACE("returning 0x%08x (%s)\n", hr, - dstCP == CP_UNICODE ? debugstr_w((LPCWSTR)*dst) : debugstr_a(*dst)); + dstCP == CP_UNICODE ? debugstr_w((LPCWSTR)dst) : debugstr_a(dst)); + return hr; +} + +static HRESULT PropertyStorage_StringCopy(LPCSTR src, LCID srcCP, LPSTR *dst, + LCID dstCP) +{ + HRESULT hr; + DWORD size; + + assert(src); + assert(dst); + + hr = PropertyStorage_StringCopy_Size(src, srcCP, &size, dstCP); + if (FAILED(hr)) + return hr; + + *dst = CoTaskMemAlloc(size); + if (!*dst) + return STG_E_INSUFFICIENTMEMORY; + + hr = PropertyStorage_StringCopy_Buffer(src, srcCP, *dst, size, dstCP); + if (FAILED(hr)) + { + CoTaskMemFree(*dst); + *dst = NULL; + } + return hr; }
@@ -1003,218 +1107,1328 @@ static HRESULT PropertyStorage_ReadDictionary(PropertyStorage_impl *This, assert(This->name_to_propid); assert(This->propid_to_name);
- StorageUtl_ReadDWord(ptr, 0, &numEntries); - TRACE("Reading %d entries:\n", numEntries); - ptr += sizeof(DWORD); - for (i = 0; SUCCEEDED(hr) && i < numEntries; i++) - { - PROPID propid; - DWORD cbEntry; + StorageUtl_ReadDWord(ptr, 0, &numEntries); + TRACE("Reading %d entries:\n", numEntries); + ptr += sizeof(DWORD); + for (i = 0; SUCCEEDED(hr) && i < numEntries; i++) + { + PROPID propid; + DWORD cbEntry; + + StorageUtl_ReadDWord(ptr, 0, &propid); + ptr += sizeof(PROPID); + StorageUtl_ReadDWord(ptr, 0, &cbEntry); + ptr += sizeof(DWORD); + TRACE("Reading entry with ID 0x%08x, %d bytes\n", propid, cbEntry); + /* Make sure the source string is NULL-terminated */ + if (This->codePage != CP_UNICODE) + ptr[cbEntry - 1] = '\0'; + else + *((LPWSTR)ptr + cbEntry / sizeof(WCHAR)) = '\0'; + hr = PropertyStorage_StoreNameWithId(This, (char*)ptr, This->codePage, propid); + if (This->codePage == CP_UNICODE) + { + /* Unicode entries are padded to DWORD boundaries */ + if (cbEntry % sizeof(DWORD)) + ptr += sizeof(DWORD) - (cbEntry % sizeof(DWORD)); + } + ptr += sizeof(DWORD) + cbEntry; + } + return hr; +} + +static void* WINAPI Allocate_CoTaskMemAlloc(void *this, ULONG size) +{ + return CoTaskMemAlloc(size); +} + +/* HACK to hide (ULONG)(something) < 0 from the analyzer */ +static inline int gt(ULONG a, ULONG b) { return a > b; } + +/* 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. + * + * Allocator: + * 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. + * + * Codepages: + * Serialized properties have no intrinsic knowledge of their codepage. + * VT_LPSTR and VT_BSTR will be interpreted using the given $diskCodepage, + * and VT_LPSTR will then be converted to $runningCodepage. + * $diskCodepage=CP_UNICODE is a sensible choice, but the OLE storage may + * also use some ANSI codepage. + * $runningCodepage=CP_ACP is the only sensible choice, but the current + * storage implementation passes $runningCodepage=$diskCodepage and converts + * narrow strings at a later stage (TODO: change this). + * + * 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 + */ +static HRESULT PropertyStorage_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; + char *tmpbuf; + + 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); + + /* allocate a temporary buffer to ensure NULL termination */ + tmpbuf = HeapAlloc(GetProcessHeap(), 0, count + 1); + if (!tmpbuf) + return STG_E_INSUFFICIENTMEMORY; + + memcpy(tmpbuf, data, count); + tmpbuf[count] = 0; + + if (diskCodepage == CP_UNICODE) + { + PropertyStorage_ByteSwapString((LPWSTR)tmpbuf, count/2); + } + + hr = PropertyStorage_StringCopy_Size( + tmpbuf, + diskCodepage, + &convertedCount, + runningCodepage); + if (FAILED(hr)) + { + HeapFree(GetProcessHeap(), 0, tmpbuf); + return hr; + } + + *vec = allocate(allocate_data, convertedCount); + if (!*vec) + { + HeapFree(GetProcessHeap(), 0, tmpbuf); + return STG_E_INSUFFICIENTMEMORY; + } + + hr = PropertyStorage_StringCopy_Buffer( + tmpbuf, + diskCodepage, + *vec, convertedCount, + runningCodepage); + + HeapFree(GetProcessHeap(), 0, tmpbuf); + + if (FAILED(hr)) + return hr; + + MOVE_FORWARD(count); + MOVE_FORWARD((4 - (count%4)) % 4); /* padding */ + } + 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(float)); + } + 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 = PropertyStorage_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; +} + +/* + * 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 PropertyStorage_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 convertedLen; + + size += 4; /* DWORD byte count */ + + hr = PropertyStorage_StringCopy_Size(prop.u.pszVal, + runningCodepage, + &convertedLen, + diskCodepage); + 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 convertedLen; + + size += 4; /* DWORD byte count */ + + hr = PropertyStorage_StringCopy_Size(prop.u.calpstr.pElems[i], + runningCodepage, + &convertedLen, + diskCodepage); + if (FAILED(hr)) + return hr; + + size += convertedLen; + + size += (4 - (convertedLen % 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 */
- StorageUtl_ReadDWord(ptr, 0, &propid); - ptr += sizeof(PROPID); - StorageUtl_ReadDWord(ptr, 0, &cbEntry); - ptr += sizeof(DWORD); - TRACE("Reading entry with ID 0x%08x, %d bytes\n", propid, cbEntry); - /* Make sure the source string is NULL-terminated */ - if (This->codePage != CP_UNICODE) - ptr[cbEntry - 1] = '\0'; - else - *((LPWSTR)ptr + cbEntry / sizeof(WCHAR)) = '\0'; - hr = PropertyStorage_StoreNameWithId(This, (char*)ptr, This->codePage, propid); - if (This->codePage == CP_UNICODE) + size += len; + } + break; + } + case VT_VECTOR|VT_VARIANT: { - /* Unicode entries are padded to DWORD boundaries */ - if (cbEntry % sizeof(DWORD)) - ptr += sizeof(DWORD) - (cbEntry % sizeof(DWORD)); + HRESULT hr; + ULONG vsize; + ULONG i; + + size += 4; /* Vector header */ + for (i = 0; i < prop.u.capropvar.cElems; ++i) + { + hr = PropertyStorage_SerializedPropertySize(&prop.u.capropvar.pElems[i], + &vsize, + diskCodepage, runningCodepage); + if (FAILED(hr)) + return hr; + + size += vsize; + size += (4 - (vsize % 4)) % 4; + } + break; } - ptr += sizeof(DWORD) + cbEntry; + default: + FIXME("unsupported type %d\n", prop.vt); + return STG_E_INVALIDPARAMETER; } - return hr; -}
-static void* WINAPI Allocate_CoTaskMemAlloc(void *this, ULONG size) -{ - return CoTaskMemAlloc(size); + /* always pad to 4 bytes */ + size += (4 - (size % 4)) % 4; + *pcbSize = size; + + return S_OK; }
-/* FIXME: there isn't any checking whether the read property extends past the - * end of the buffer. +/* + * 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. + * + * Codepages: + * Serialized properties have no intrinsic knowledge of their codepage. + * VT_LPSTR will be interpreted in the given $runningCodepage, and + * VT_LPSTR and VT_BSTR will be converted to the given $diskCodepage. + * $diskCodepage=CP_UNICODE is a sensible choice, but the OLE storage may + * also use some ANSI codepage. + * $runningCodepage=CP_ACP is the only sensible choice, but the current + * storage implementation passes $runningCodepage=$diskCodepage and converts + * narrow strings at a previous stage (TODO: change this). + * + * 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 PropertyStorage_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 convertedLen; + + hr = PropertyStorage_StringCopy_Size(*vec, + runningCodepage, + &convertedLen, + diskCodepage); + if (FAILED(hr)) + return hr; + + WRITE_DWORD_AND_MOVE(convertedLen); + FAIL_IF_EOF(convertedLen); + + hr = PropertyStorage_StringCopy_Buffer(*vec, + runningCodepage, + (char*)buffer, convertedLen, + diskCodepage); + if (FAILED(hr)) + return hr; + + if (diskCodepage == CP_UNICODE) + { + PropertyStorage_ByteSwapString(buffer, convertedLen/2); + TRACE("Write lpstr %s\n", debugstr_w((LPCWSTR)buffer)); + } else - wcount = MultiByteToWideChar(codepage, 0, (LPCSTR)(data + sizeof(DWORD)), count, NULL, 0); + { + TRACE("Write lpstr %s\n", debugstr_a((char*)buffer)); + }
- prop->u.bstrVal = SysAllocStringLen(NULL, wcount); /* FIXME: use allocator? */ + 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 (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); + 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)); + PropertyStorage_ByteSwapString(buffer, len); + 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)); + PropertyStorage_ByteSwapString(buffer, len); + 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); + } + break; + } + 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); } - else - hr = STG_E_INSUFFICIENTMEMORY; break; } - case VT_FILETIME: - StorageUtl_ReadULargeInteger(data, 0, - (ULARGE_INTEGER *)&prop->u.filetime); + CASE_OR_VECTOR(VT_DATE) + CASE_OR_VECTOR(VT_R8) + { + FOR_POSSIBLE_VECTOR(double, dblVal, cadbl, sizeof(double)) + { + memcpy(buffer, vec, sizeof(double)); + } break; - case VT_CF: + } + CASE_OR_VECTOR(VT_R4) + { + FOR_POSSIBLE_VECTOR(float, fltVal, caflt, sizeof(float)) { - DWORD len = 0, tag = 0; + memcpy(buffer, vec, sizeof(float)); + } + break; + } + case VT_DECIMAL: + { + DECIMAL *dec; + + FAIL_IF_EOF(16);
- StorageUtl_ReadDWord(data, 0, &len); - StorageUtl_ReadDWord(data, 4, &tag); - if (len > 8) + 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); + + 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 = PropertyStorage_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 +2653,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 = PropertyStorage_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 +2685,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 +2911,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 +2935,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 = PropertyStorage_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 = PropertyStorage_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,12 +3893,17 @@ BOOLEAN WINAPI StgConvertPropertyToVariant(const SERIALIZEDPROPERTYVALUE* prop, { HRESULT hr;
- hr = PropertyStorage_ReadProperty(pvar, (const BYTE*)prop, CodePage, Allocate_PMemoryAllocator, pma); + hr = PropertyStorage_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); + + /* TODO: get the HRESULT<->NTSTATUS mapping right */ + RaiseException(0xC000000D /*STATUS_INVALID_PARAMETER*/, 0, 0, NULL); }
return FALSE; @@ -2763,7 +3913,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 = PropertyStorage_WritePropertyToBuffer(pvar, (BYTE*)pprop, buffer_size, pcb, CodePage, CP_ACP); + } + else + { + hr = PropertyStorage_SerializedPropertySize(pvar, pcb, CodePage, CP_ACP); + } + + if (FAILED(hr)) + { + /* FIXME: what should we really do? I couldn't get windows to actually throw an exception */ + FIXME("should raise exception on failure\n"); + return NULL; + }
- return NULL; + return pprop; } diff --git a/dlls/ole32/tests/propvariant.c b/dlls/ole32/tests/propvariant.c index c45ca2a..401bc27 100644 --- a/dlls/ole32/tests/propvariant.c +++ b/dlls/ole32/tests/propvariant.c @@ -23,6 +23,7 @@ #include "ddeml.h"
#include "wine/test.h" +#include "wine/exception.h"
/* invalid in all versions */ #define PROP_INV 0x7f @@ -438,6 +439,30 @@ 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 const char serialized_invalid[] = { + 0xde, 0xad, /* invalid */ + 0, 0, + 'b','o','g','u','s', + 0,0,0 +}; + static void test_propertytovariant(void) { HANDLE hole32; @@ -447,6 +472,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 +521,36 @@ 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); + + /* provoke exceptions */ + __TRY + { + ret = pStgConvertPropertyToVariant((SERIALIZEDPROPERTYVALUE*)serialized_invalid, + CP_WINUNICODE, &propvar, &allocator); + ok(0, "StgConvertPropertyToVariant should have thrown\n"); + } + __EXCEPT_ALL + { + todo_wine ok(GetExceptionCode() == 0xC00000bb/*STATUS_NOT_SUPPORTED*/, + "StgConvertPropertyToVariant threw unexpected exception %x\n", GetExceptionCode()); + } + __ENDTRY; }
static void test_varianttoproperty(void) @@ -506,6 +562,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 +587,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) { @@ -538,6 +595,15 @@ static void test_varianttoproperty(void) return; }
+ len = 4; /* too small! */ + own_propvalue->dwType = 0xcafebabe; + propvalue = pStgConvertVariantToProperty(&propvar, CP_WINUNICODE, + own_propvalue, &len, 0, FALSE, 0); + ok(NULL == propvalue, "unexpected propvalue %p\n", propvalue); + todo_wine ok(len == 8, "unexpected length %d\n", len); + + /*TODO: how can we make it throw an exception? */ + len = 20; propvalue = pStgConvertVariantToProperty(&propvar, CP_WINUNICODE, own_propvalue, &len, 0, FALSE, 0); @@ -588,6 +654,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/include/propidl.idl b/include/propidl.idl index 2fb8370..5905694 100644 --- a/include/propidl.idl +++ b/include/propidl.idl @@ -443,6 +443,8 @@ 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("#define _PROPVARIANT_INIT_DEFINED_") cpp_quote("#define PropVariantInit(p) memset((p), 0, sizeof(PROPVARIANT))")
--- dlls/propsys/propsys.spec | 4 +- dlls/propsys/propvar.c | 172 ++++++++++++++++++++ dlls/propsys/tests/propsys.c | 374 +++++++++++++++++++++++++++++++++++++++++++ include/propvarutil.h | 7 + 4 files changed, 555 insertions(+), 2 deletions(-)
diff --git a/dlls/propsys/propsys.spec b/dlls/propsys/propsys.spec index 100f825..50bdf6e 100644 --- a/dlls/propsys/propsys.spec +++ b/dlls/propsys/propsys.spec @@ -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 ae3fd2f..54d72ad 100644 --- a/dlls/propsys/propvar.c +++ b/dlls/propsys/propvar.c @@ -30,9 +30,13 @@ #include "winuser.h" #include "shlobj.h" #include "propvarutil.h" +#include "mimeole.h" +#include "oleauto.h"
#include "wine/debug.h" #include "wine/unicode.h" +#include "wine/exception.h" +#include "winternl.h"
WINE_DEFAULT_DEBUG_CHANNEL(propsys);
@@ -995,3 +999,171 @@ INT WINAPI PropVariantCompareEx(REFPROPVARIANT propvar1, REFPROPVARIANT propvar2
return res; } + +struct _PMemoryAllocator_vtable { + void *Allocate; /* virtual void* Allocate(ULONG cbSize); */ + void *Free; /* virtual void Free(void *pv); */ +}; + +typedef struct _PMemoryAllocator { + struct _PMemoryAllocator_vtable *vt; +} PMemoryAllocator; + +static void * WINAPI PMemoryAllocator_Allocate(PMemoryAllocator *_this, ULONG cbSize) +{ + return CoTaskMemAlloc(cbSize); +} + +static void WINAPI PMemoryAllocator_Free(PMemoryAllocator *_this, void *pv) +{ + CoTaskMemFree(pv); +} + +#ifdef __i386__ + +#include "pshpack1.h" +typedef struct +{ + BYTE pop_eax; /* popl %eax */ + BYTE push_ecx; /* pushl %ecx */ + BYTE push_eax; /* pushl %eax */ + BYTE jmp_func; /* jmp $func */ + DWORD func; +} THISCALL_TO_STDCALL_THUNK; +#include "poppack.h" + +static THISCALL_TO_STDCALL_THUNK *wrapperCodeMem = NULL; + +static void fill_thunk(THISCALL_TO_STDCALL_THUNK *thunk, void *fn) +{ + thunk->pop_eax = 0x58; + thunk->push_ecx = 0x51; + thunk->push_eax = 0x50; + thunk->jmp_func = 0xe9; + thunk->func = (char*)fn - (char*)(&thunk->func + 1); +} + +static void setup_allocator_vtable(struct _PMemoryAllocator_vtable *vtable) +{ + if (!wrapperCodeMem) + { + wrapperCodeMem = VirtualAlloc(NULL, 2 * sizeof(*wrapperCodeMem), + MEM_COMMIT, PAGE_EXECUTE_READWRITE); + + fill_thunk(&wrapperCodeMem[0], PMemoryAllocator_Allocate); + fill_thunk(&wrapperCodeMem[1], PMemoryAllocator_Free); + } + + vtable->Allocate = &wrapperCodeMem[0]; + vtable->Free = &wrapperCodeMem[1]; +} + +#else + +static void setup_allocator_vtable(struct _PMemoryAllocator_vtable *vtable) +{ + vtable->Allocate = PMemoryAllocator_Allocate; + vtable->Free = PMemoryAllocator_Free; +} + +#endif + + +HRESULT WINAPI StgDeserializePropVariant(const SERIALIZEDPROPERTYVALUE *pprop, + ULONG cbMax, + PROPVARIANT *pvar) +{ + PMemoryAllocator allocator; + struct _PMemoryAllocator_vtable vtbl; + + TRACE("(%p, %lu, %p)\n", pprop, (unsigned long)cbMax, pvar); + + allocator.vt = &vtbl; + setup_allocator_vtable(&vtbl); + + if (!pprop || !pvar) + return E_INVALIDARG; + + __TRY + { + StgConvertPropertyToVariant(pprop, CP_UNICODE, pvar, &allocator); + } + __EXCEPT_ALL + { + return HRESULT_FROM_WIN32(RtlNtStatusToDosError(GetExceptionCode())); + } + __ENDTRY + + return S_OK; +} + +HRESULT WINAPI StgSerializePropVariant(const PROPVARIANT *pVar, + SERIALIZEDPROPERTYVALUE **ppProp, + ULONG *pcb) +{ + HRESULT hr = S_OK; + ULONG space = 0; + void *buffer = NULL; + + TRACE("(%p, %p, %p)\n", pVar, ppProp, pcb); + + if (!pVar || !pcb) + return E_POINTER; + + __TRY + { + StgConvertVariantToProperty(pVar, CP_UNICODE, NULL, &space, 0, FALSE, 0); + } + __EXCEPT_ALL + { + WARN("Failed to serialize property of type %d: hr=%08x\n", pVar->vt, hr); + return HRESULT_FROM_WIN32(RtlNtStatusToDosError(GetExceptionCode())); + } + __ENDTRY; + + *pcb = 0; + + /* MSDN requires ppProp to be defined, but, as an extension, we allow + * a NULL value and return the required size instead. + */ + if (ppProp) *ppProp = NULL; + + if (ppProp) + { + buffer = CoTaskMemAlloc(space); + if (!buffer) + return E_OUTOFMEMORY; + + __TRY + { + *ppProp = StgConvertVariantToProperty(pVar, CP_UNICODE, buffer, &space, 0, FALSE, 0); + if (!*ppProp) + hr = E_FAIL; + } + __EXCEPT_ALL + { + WARN("Failed to serialize property of type %d: hr=%08x\n", pVar->vt, hr); + *ppProp = NULL; + CoTaskMemFree(buffer); + return HRESULT_FROM_WIN32(RtlNtStatusToDosError(GetExceptionCode())); + } + __ENDTRY; + + if (FAILED(hr)) + { + WARN("Failed to serialize property of type %d: hr=%08x\n", pVar->vt, hr); + CoTaskMemFree(buffer); + + return hr; + } + + *pcb = space; + } + else + { + /* This is a wine extension */ + *pcb = space; + } + + return S_OK; +} diff --git a/dlls/propsys/tests/propsys.c b/dlls/propsys/tests/propsys.c index 99d1c46..4044a38 100644 --- a/dlls/propsys/tests/propsys.c +++ b/dlls/propsys/tests/propsys.c @@ -866,6 +866,379 @@ 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 OLE32.dll */ +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 +1249,5 @@ START_TEST(propsys) test_PropVariantToGUID(); test_PropVariantCompare(); test_intconversions(); + test_serialization(); } diff --git a/include/propvarutil.h b/include/propvarutil.h index b0a1731..0a35ca8 100644 --- a/include/propvarutil.h +++ b/include/propvarutil.h @@ -96,6 +96,13 @@ HRESULT WINAPI InitPropVariantFromFileTime(const FILETIME *pft, PROPVARIANT *ppr 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
On 18.07.2015 19:26, Jonas Kümmerlin wrote:
- __TRY
- {
StgConvertVariantToProperty(pVar, CP_UNICODE, NULL, &space, 0, FALSE, 0);
- }
- __EXCEPT_ALL
- {
WARN("Failed to serialize property of type %d: hr=%08x\n", pVar->vt, hr);
return HRESULT_FROM_WIN32(RtlNtStatusToDosError(GetExceptionCode()));
- }
- __ENDTRY;
- *pcb = 0;
- /* MSDN requires ppProp to be defined, but, as an extension, we allow
* a NULL value and return the required size instead.
*/
- if (ppProp) *ppProp = NULL;
- if (ppProp)
- {
buffer = CoTaskMemAlloc(space);
Why would you need to get only returned size from this? My understanding is that you pass it variant, and it returns buffer allocated with CoTaskMemAlloc() and size of it. And it looks like it should be enough to have single TRY block.
__TRY
{
*ppProp = StgConvertVariantToProperty(pVar, CP_UNICODE, buffer, &space, 0, FALSE, 0);
if (!*ppProp)
hr = E_FAIL;
}
__EXCEPT_ALL
{
WARN("Failed to serialize property of type %d: hr=%08x\n", pVar->vt, hr);
*ppProp = NULL;
CoTaskMemFree(buffer);
return HRESULT_FROM_WIN32(RtlNtStatusToDosError(GetExceptionCode()));
}
__ENDTRY;
if (FAILED(hr))
{
WARN("Failed to serialize property of type %d: hr=%08x\n", pVar->vt, hr);
CoTaskMemFree(buffer);
return hr;
}
It's probably safe to assume that last FAILED() is unreachable, because on any error exception will be raised.
- else
- {
/* This is a wine extension */
*pcb = space;
- }
It's better to avoid adding such workarounds.
On 18.07.2015 19:26, Jonas Kümmerlin wrote:
+HRESULT WINAPI StgDeserializePropVariant(const SERIALIZEDPROPERTYVALUE *pprop,
ULONG cbMax,
PROPVARIANT *pvar)
+{
- PMemoryAllocator allocator;
- struct _PMemoryAllocator_vtable vtbl;
- TRACE("(%p, %lu, %p)\n", pprop, (unsigned long)cbMax, pvar);
I don't think you need that cast.
- allocator.vt = &vtbl;
- setup_allocator_vtable(&vtbl);
- if (!pprop || !pvar)
return E_INVALIDARG;
- __TRY
- {
StgConvertPropertyToVariant(pprop, CP_UNICODE, pvar, &allocator);
- }
- __EXCEPT_ALL
Another question is if you need this allocator complexity at all, it's possible that ole32 call will use CoTaskMem* functions for NULL allocator pointer, I don't see any tests in ole32/tests for that.
The in-memory property store now supports INamedPropertyStore. --- dlls/propsys/propstore.c | 275 ++++++++++++++++++++++++++++++++++++--- dlls/propsys/propsys_classes.idl | 5 +- dlls/propsys/tests/propstore.c | 72 ++++++++++ 3 files changed, 336 insertions(+), 16 deletions(-)
diff --git a/dlls/propsys/propstore.c b/dlls/propsys/propstore.c index 9c848fc..3f028e9 100644 --- a/dlls/propsys/propstore.c +++ b/dlls/propsys/propstore.c @@ -28,6 +28,7 @@ #include "objbase.h" #include "rpcproxy.h" #include "propsys.h" +#include "propvarutil.h" #include "wine/debug.h" #include "wine/unicode.h" #include "wine/list.h" @@ -53,11 +54,20 @@ 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; + INamedPropertyStore INamedPropertyStore_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 +75,13 @@ 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_INamedPropertyStore(INamedPropertyStore *iface) { - PropertyStore *This = impl_from_IPropertyStoreCache(iface); - TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv); + return CONTAINING_RECORD(iface, PropertyStore, INamedPropertyStore_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 +89,10 @@ static HRESULT WINAPI PropertyStore_QueryInterface(IPropertyStoreCache *iface, R { *ppv = &This->IPropertyStoreCache_iface; } + else if (IsEqualIID(&IID_INamedPropertyStore, iid)) + { + *ppv = &This->INamedPropertyStore_iface; + } else { FIXME("No interface for %s\n", debugstr_guid(iid)); @@ -89,10 +104,45 @@ 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_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 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_INamedPropertyStore_AddRef(INamedPropertyStore *iface) +{ + PropertyStore *This = impl_from_INamedPropertyStore(iface); + ULONG ref = add_ref(This);
TRACE("(%p) refcount=%u\n", iface, ref);
@@ -110,26 +160,53 @@ 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_INamedPropertyStore_Release(INamedPropertyStore *iface) +{ + PropertyStore *This = impl_from_INamedPropertyStore(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 +230,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 +303,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 +438,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 +502,62 @@ 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 + { + PropVariantClear(&temp); + hr = E_OUTOFMEMORY; + } + } + + LeaveCriticalSection(&This->lock); + + return hr; +} + static HRESULT WINAPI PropertyStore_Commit(IPropertyStoreCache *iface) { FIXME("%p: stub\n", iface); @@ -435,10 +668,10 @@ static HRESULT WINAPI PropertyStore_SetValueAndState(IPropertyStoreCache *iface, return hr; }
-static const IPropertyStoreCacheVtbl PropertyStore_Vtbl = { - PropertyStore_QueryInterface, - PropertyStore_AddRef, - PropertyStore_Release, +static const IPropertyStoreCacheVtbl PropertyStore_IPropertyStoreCache_Vtbl = { + PropertyStore_IPropertyStoreCache_QueryInterface, + PropertyStore_IPropertyStoreCache_AddRef, + PropertyStore_IPropertyStoreCache_Release, PropertyStore_GetCount, PropertyStore_GetAt, PropertyStore_GetValue, @@ -450,6 +683,16 @@ static const IPropertyStoreCacheVtbl PropertyStore_Vtbl = { PropertyStore_SetValueAndState };
+static const INamedPropertyStoreVtbl PropertyStore_INamedPropertyStore_Vtbl = { + PropertyStore_INamedPropertyStore_QueryInterface, + PropertyStore_INamedPropertyStore_AddRef, + PropertyStore_INamedPropertyStore_Release, + PropertyStore_GetNamedValue, + PropertyStore_SetNamedValue, + PropertyStore_GetNameCount, + PropertyStore_GetNameAt +}; + HRESULT PropertyStore_CreateInstance(IUnknown *pUnkOuter, REFIID iid, void** ppv) { PropertyStore *This; @@ -464,11 +707,13 @@ 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->INamedPropertyStore_iface.lpVtbl = &PropertyStore_INamedPropertyStore_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..cdbbb77 100644 --- a/dlls/propsys/propsys_classes.idl +++ b/dlls/propsys/propsys_classes.idl @@ -25,4 +25,7 @@ threading(both), uuid(9a02e012-6303-4e1e-b9a1-630f802592c5) ] -coclass InMemoryPropertyStore { interface IPropertyStoreCache; } +coclass InMemoryPropertyStore { + interface IPropertyStoreCache; + interface INamedPropertyStore; +} diff --git a/dlls/propsys/tests/propstore.c b/dlls/propsys/tests/propstore.c index 01500dd..67ab80a 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,6 +198,75 @@ static void test_inmemorystore(void) IPropertyStoreCache_Release(propcache); }
+static void test_namedpropertystore(void) +{ + IPropertyStore *propstore; + 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; @@ -253,6 +324,7 @@ START_TEST(propstore) CoInitialize(NULL);
test_inmemorystore(); + test_namedpropertystore(); test_persistserialized();
CoUninitialize();
On 18.07.2015 19:26, Jonas Kümmerlin wrote:
The in-memory property store now supports INamedPropertyStore.
dlls/propsys/propstore.c | 275 ++++++++++++++++++++++++++++++++++++--- dlls/propsys/propsys_classes.idl | 5 +- dlls/propsys/tests/propstore.c | 72 ++++++++++ 3 files changed, 336 insertions(+), 16 deletions(-)
diff --git a/dlls/propsys/propstore.c b/dlls/propsys/propstore.c index 9c848fc..3f028e9 100644 --- a/dlls/propsys/propstore.c +++ b/dlls/propsys/propstore.c @@ -28,6 +28,7 @@ #include "objbase.h" #include "rpcproxy.h" #include "propsys.h" +#include "propvarutil.h" #include "wine/debug.h" #include "wine/unicode.h" #include "wine/list.h" @@ -53,11 +54,20 @@ 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;
INamedPropertyStore INamedPropertyStore_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 +75,13 @@ 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_INamedPropertyStore(INamedPropertyStore *iface) {
- PropertyStore *This = impl_from_IPropertyStoreCache(iface);
- TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv);
- return CONTAINING_RECORD(iface, PropertyStore, INamedPropertyStore_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 +89,10 @@ static HRESULT WINAPI PropertyStore_QueryInterface(IPropertyStoreCache *iface, R { *ppv = &This->IPropertyStoreCache_iface; }
- else if (IsEqualIID(&IID_INamedPropertyStore, iid))
- {
*ppv = &This->INamedPropertyStore_iface;
- } else { FIXME("No interface for %s\n", debugstr_guid(iid));
@@ -89,10 +104,45 @@ 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);
+}
We usually do it differently, keeping IUnknown methods implementations in one of interfaces object implements. After that the rest of interfaces use proper impl_from_* and forward to that "main" interface using "*_QueryInterface(&This->IPropertyStoreCache_iface, iid, ppv);" way.
I also suggest to change function naming, for existing IPropertyStoreCache methods to PropertyStoreCache_*, for new one to NamedPropertyStore_*.
-static const IPropertyStoreCacheVtbl PropertyStore_Vtbl = {
- PropertyStore_QueryInterface,
- PropertyStore_AddRef,
- PropertyStore_Release,
+static const IPropertyStoreCacheVtbl PropertyStore_IPropertyStoreCache_Vtbl = {
- PropertyStore_IPropertyStoreCache_QueryInterface,
- PropertyStore_IPropertyStoreCache_AddRef,
- PropertyStore_IPropertyStoreCache_Release, PropertyStore_GetCount, PropertyStore_GetAt, PropertyStore_GetValue,
@@ -450,6 +683,16 @@ static const IPropertyStoreCacheVtbl PropertyStore_Vtbl = { PropertyStore_SetValueAndState };
+static const INamedPropertyStoreVtbl PropertyStore_INamedPropertyStore_Vtbl = {
- PropertyStore_INamedPropertyStore_QueryInterface,
- PropertyStore_INamedPropertyStore_AddRef,
- PropertyStore_INamedPropertyStore_Release,
- PropertyStore_GetNamedValue,
- PropertyStore_SetNamedValue,
- PropertyStore_GetNameCount,
- PropertyStore_GetNameAt
+};
This name change will also make vtables look nicer.
+static void test_namedpropertystore(void) +{
- IPropertyStore *propstore;
- 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;
- }
If it works in win2000 you don't need to skip, if it's a newer addition you should use win_skip().
The in-memory property store now supports IPersistSerializedPropStorage2. --- dlls/propsys/propstore.c | 625 +++++++++++++++++++++++++++++++++++++++ dlls/propsys/propsys_classes.idl | 1 + dlls/propsys/tests/propstore.c | 216 +++++++++++++- 3 files changed, 839 insertions(+), 3 deletions(-)
diff --git a/dlls/propsys/propstore.c b/dlls/propsys/propstore.c index 3f028e9..1f5f58f 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 @@ -32,6 +33,7 @@ #include "wine/debug.h" #include "wine/unicode.h" #include "wine/list.h" +#include "mimeole.h"
#include "initguid.h" #include "propsys_private.h" @@ -63,6 +65,7 @@ typedef struct {
typedef struct { IPropertyStoreCache IPropertyStoreCache_iface; + IPersistSerializedPropStorage2 IPersistSerializedPropStorage2_iface; INamedPropertyStore INamedPropertyStore_iface; LONG ref; CRITICAL_SECTION lock; @@ -75,6 +78,11 @@ static inline PropertyStore *impl_from_IPropertyStoreCache(IPropertyStoreCache * return CONTAINING_RECORD(iface, PropertyStore, IPropertyStoreCache_iface); }
+static inline PropertyStore *impl_from_IPersistSerializedPropStorage2(IPersistSerializedPropStorage2 *iface) +{ + return CONTAINING_RECORD(iface, PropertyStore, IPersistSerializedPropStorage2_iface); +} + static inline PropertyStore *impl_from_INamedPropertyStore(INamedPropertyStore *iface) { return CONTAINING_RECORD(iface, PropertyStore, INamedPropertyStore_iface); @@ -89,6 +97,11 @@ static HRESULT query_interface(PropertyStore *This, REFIID iid, void **ppv) { *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; @@ -114,6 +127,16 @@ static HRESULT WINAPI PropertyStore_IPropertyStoreCache_QueryInterface( 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) { @@ -139,6 +162,16 @@ static ULONG WINAPI PropertyStore_IPropertyStoreCache_AddRef(IPropertyStoreCache 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); @@ -197,6 +230,16 @@ static ULONG WINAPI PropertyStore_IPropertyStoreCache_Release(IPropertyStoreCach 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); @@ -668,6 +711,576 @@ static HRESULT WINAPI PropertyStore_SetValueAndState(IPropertyStoreCache *iface, return hr; }
+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 = StgSerializePropVariant(&value->propvar, NULL, &propsize); + 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 = StgSerializePropVariant(&named_value->propvar, NULL, &propsize); + 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; + SERIALIZEDPROPERTYVALUE *tmp = NULL; + + hr = StgSerializePropVariant(&value->propvar, &tmp, &propsize); + if (FAILED(hr)) + { + WARN("Property of type %d is not serializable\n", value->propvar.vt); + goto out; + } + + if (cb < (9 + propsize)) /* Size, pid, reserved, content */ + { + CoTaskMemFree(buffer); + hr = E_INVALIDARG; + goto out; + } + + write_dword(buffer, propsize + 9); /* size */ + write_dword(buffer + 4, value->pid); /* pid */ + buffer[8] = 0; /* reserved byte to make sure everything is misaligned */ + + memcpy(buffer + 9, tmp, propsize); + CoTaskMemFree(tmp); + + MOVE(9 + 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; + SERIALIZEDPROPERTYVALUE *tmp; + 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 = StgSerializePropVariant(&named_value->propvar, &tmp, &propsize); + if (FAILED(hr)) + { + WARN("Property of type %d is not serializable\n", named_value->propvar.vt); + goto out; + } + + if ((9 + namesize + propsize) > cb) + { + CoTaskMemFree(tmp); + hr = E_INVALIDARG; + goto out; + } + + write_dword(buffer, 9 + namesize + propsize); + write_dword(buffer + 4, namesize); + buffer[8] = 0; + + write_wstring(buffer + 9, named_value->name, namesize/2); + memcpy(buffer + 9 + namesize, tmp, propsize); + + MOVE(9 + namesize + 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 (cb == 0) /* 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 const IPropertyStoreCacheVtbl PropertyStore_IPropertyStoreCache_Vtbl = { PropertyStore_IPropertyStoreCache_QueryInterface, PropertyStore_IPropertyStoreCache_AddRef, @@ -683,6 +1296,17 @@ static const IPropertyStoreCacheVtbl PropertyStore_IPropertyStoreCache_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, @@ -708,6 +1332,7 @@ HRESULT PropertyStore_CreateInstance(IUnknown *pUnkOuter, REFIID iid, void** ppv if (!This) return E_OUTOFMEMORY;
This->IPropertyStoreCache_iface.lpVtbl = &PropertyStore_IPropertyStoreCache_Vtbl; + This->IPersistSerializedPropStorage2_iface.lpVtbl = &PropertyStore_IPersistSerializedPropStorage_Vtbl; This->INamedPropertyStore_iface.lpVtbl = &PropertyStore_INamedPropertyStore_Vtbl; This->ref = 1; InitializeCriticalSection(&This->lock); diff --git a/dlls/propsys/propsys_classes.idl b/dlls/propsys/propsys_classes.idl index cdbbb77..b4fd550 100644 --- a/dlls/propsys/propsys_classes.idl +++ b/dlls/propsys/propsys_classes.idl @@ -28,4 +28,5 @@ coclass InMemoryPropertyStore { interface IPropertyStoreCache; interface INamedPropertyStore; + interface IPersistSerializedPropStorage2; } diff --git a/dlls/propsys/tests/propstore.c b/dlls/propsys/tests/propstore.c index 67ab80a..a91e456 100644 --- a/dlls/propsys/tests/propstore.c +++ b/dlls/propsys/tests/propstore.c @@ -269,19 +269,121 @@ static void test_namedpropertystore(void)
static void test_persistserialized(void) { - IPropertyStore *propstore; - IPersistSerializedPropStorage *serialized; + IPropertyStore *propstore = NULL; + INamedPropertyStore *named = NULL; + IPersistSerializedPropStorage *serialized = NULL; HRESULT hr; SERIALIZEDPROPSTORAGE *result; DWORD result_size;
+ 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)) { @@ -311,11 +413,119 @@ static void test_persistserialized(void) hr = IPersistSerializedPropStorage_SetPropertyStorage(serialized, NULL, 0); ok(hr == S_OK, "SetPropertyStorage failed, hr=%x\n", hr);
+ hr = IPersistSerializedPropStorage_SetPropertyStorage(serialized, (void*)0xdeadcafe, 0); + ok(hr == S_OK, "SetPropertyStorage failed, hr=%x\n", hr); + hr = IPropertyStore_GetCount(propstore, &result_size); 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); + } + IPropertyStore_Release(propstore); + INamedPropertyStore_Release(named); IPersistSerializedPropStorage_Release(serialized); }
+ if (cb < (9 + propsize)) /* Size, pid, reserved, content */ + { + CoTaskMemFree(buffer); + hr = E_INVALIDARG;
I think you meant to free tmp here instead?
--- dlls/propsys/propstore.c | 162 +++++++++++++++++++++++++++++++++++++++ dlls/propsys/propsys_classes.idl | 1 + dlls/propsys/tests/Makefile.in | 2 +- dlls/propsys/tests/propstore.c | 115 +++++++++++++++++++++++++++ dlls/propsys/tests/propsys.c | 3 +- 5 files changed, 280 insertions(+), 3 deletions(-)
diff --git a/dlls/propsys/propstore.c b/dlls/propsys/propstore.c index 1f5f58f..87b201f 100644 --- a/dlls/propsys/propstore.c +++ b/dlls/propsys/propstore.c @@ -67,6 +67,7 @@ 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 */ @@ -88,6 +89,11 @@ static inline PropertyStore *impl_from_INamedPropertyStore(INamedPropertyStore * 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; @@ -106,6 +112,10 @@ static HRESULT query_interface(PropertyStore *This, REFIID iid, void **ppv) { *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)); @@ -147,6 +157,16 @@ static HRESULT WINAPI PropertyStore_INamedPropertyStore_QueryInterface( 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); @@ -182,6 +202,16 @@ static ULONG WINAPI PropertyStore_INamedPropertyStore_AddRef(INamedPropertyStore 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); + + return ref; +} + static void destroy_format(propstore_format *format) { propstore_value *cursor, *cursor2; @@ -250,6 +280,16 @@ static ULONG WINAPI PropertyStore_INamedPropertyStore_Release(INamedPropertyStor 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) { @@ -1281,6 +1321,116 @@ out: 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); + + 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 = NULL; + + 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); + +out: + CoTaskMemFree(buffer); + + 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, @@ -1317,6 +1467,17 @@ static const INamedPropertyStoreVtbl PropertyStore_INamedPropertyStore_Vtbl = { 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; @@ -1334,6 +1495,7 @@ HRESULT PropertyStore_CreateInstance(IUnknown *pUnkOuter, REFIID iid, void** ppv 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"); diff --git a/dlls/propsys/propsys_classes.idl b/dlls/propsys/propsys_classes.idl index b4fd550..32eefa0 100644 --- a/dlls/propsys/propsys_classes.idl +++ b/dlls/propsys/propsys_classes.idl @@ -29,4 +29,5 @@ coclass InMemoryPropertyStore { interface IPropertyStoreCache; interface INamedPropertyStore; interface IPersistSerializedPropStorage2; + 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 a91e456..41c3fe8 100644 --- a/dlls/propsys/tests/propstore.c +++ b/dlls/propsys/tests/propstore.c @@ -272,9 +272,11 @@ 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 }; @@ -524,6 +526,119 @@ static void test_persistserialized(void) 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); diff --git a/dlls/propsys/tests/propsys.c b/dlls/propsys/tests/propsys.c index 4044a38..1199b0e 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 | 283 +++++++++++++++++++++++++++++++++++++++++++ dlls/propsys/tests/propsys.c | 80 +++++++++++- include/propvarutil.h | 3 + include/strsafe.h | 17 +++ 5 files changed, 385 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 54d72ad..f8abd2e 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" @@ -213,6 +214,263 @@ 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 = 1; + 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 = 22; /* enough for all 64bit integers */ + break; + case VT_LPWSTR: + *pcch = lstrlenW(propvarIn->u.pwszVal) + 1; + break; + case VT_LPSTR: + /* PropVariantToString converts narrow strings to wide strings */ + *pcch = MultiByteToWideChar(CP_ACP, 0, propvarIn->u.pszVal, -1, NULL, 0); + break; + case VT_BSTR: + *pcch = SysStringLen(propvarIn->u.bstrVal) + 1; + break; + default: + FIXME("Unsupported variant type %d\n", propvarIn->vt); + hr = E_NOTIMPL; + } + + return hr; +} + +/* snprintfW() still doesn't support 64bit integers on 32bit platforms :( */ +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++; + + /* 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; + + hr = PropVariantToInt64(propvar, &ll); + if (FAILED(hr)) + break; + + i64_to_wstr(ll, numBuffer); + hr = StringCchCopyW(buf, cch, numBuffer); + break; + } + case VT_UI1: + case VT_UI2: + case VT_UI4: + case VT_UI8: + case VT_UINT: + { + ULONGLONG ll; + + hr = PropVariantToUInt64(propvar, &ll); + if (FAILED(hr)) + break; + + u64_to_wstr(ll, numBuffer); + hr = StringCchCopyW(buf, cch, numBuffer); + break; + } + case VT_LPWSTR: + { + hr = StringCchCopyW(buf, cch, propvar->u.pwszVal); + 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) /* null terminator is already included */ + { + MultiByteToWideChar(CP_ACP, 0, propvar->u.pszVal, -1, buf, len); + } + else + { + WCHAR *tmp; + + /* Allocate larger buffer, copy truncated result */ + tmp = CoTaskMemAlloc(len * sizeof(WCHAR)); + if (tmp) + { + MultiByteToWideChar(CP_ACP, 0, propvar->u.pszVal, -1, tmp, len); + hr = StringCchCopyW(buf, cch, tmp); + CoTaskMemFree(tmp); + } + else + { + TRACE("No memory left to allocate a temporary buffer\n"); + hr = E_OUTOFMEMORY; + } + } + break; + } + case VT_BSTR: + { + hr = StringCchCopyW(buf, cch, propvar->u.bstrVal); + 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 * sizeof(WCHAR)); + if (!*pbuf) + return E_OUTOFMEMORY; + + hr = PropVariantToString(prop, *pbuf, size); + 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.@) */ @@ -292,6 +550,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 1199b0e..7be320d 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; @@ -1247,6 +1324,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 0a35ca8..3e6defb 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); diff --git a/include/strsafe.h b/include/strsafe.h index 842075f..a69d5cc 100644 --- a/include/strsafe.h +++ b/include/strsafe.h @@ -29,4 +29,21 @@ #define STRSAFE_E_INVALID_PARAM ((HRESULT)0x80070075) #define STRSAFE_E_END_OF_FILE ((HRESULT)0x80070026)
+static inline HRESULT StringCchCopyW(WCHAR *pszDest, size_t cchDest, const WCHAR *pszSrc) +{ + /* On windows, STRSAFE_MAX_CCH is also checked. We don't care. */ + if (!cchDest) + return STRSAFE_E_INSUFFICIENT_BUFFER; + + for (; cchDest > 1 && *pszSrc; --cchDest) + *pszDest++ = *pszSrc++; + + *pszDest = 0; /* always null-terminate */ + + if (*pszSrc) /* there is still data left */ + return STRSAFE_E_INSUFFICIENT_BUFFER; + + return S_OK; +} + #endif
--- 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";
This property store is underspecified, poorly documented by Microsoft and the tests expose a fair bit of implementation WTF. --- dlls/shell32/Makefile.in | 1 + dlls/shell32/shell32.spec | 1 + dlls/shell32/tests/appusermodel.c | 153 +++++++++++++++++++- dlls/shell32/winpropstore.c | 291 ++++++++++++++++++++++++++++++++++++++ include/shellapi.h | 1 + include/wine/server_protocol.h | 59 +++++++- server/protocol.def | 38 +++++ server/request.h | 35 +++++ server/trace.c | 47 ++++++ server/window.c | 148 +++++++++++++++++++ 10 files changed, 772 insertions(+), 2 deletions(-) create mode 100644 dlls/shell32/winpropstore.c
diff --git a/dlls/shell32/Makefile.in b/dlls/shell32/Makefile.in index 038db7f..234e700 100644 --- a/dlls/shell32/Makefile.in +++ b/dlls/shell32/Makefile.in @@ -53,6 +53,7 @@ C_SRCS = \ shpolicy.c \ systray.c \ trash.c \ + winpropstore.c \ xdg.c
RC_SRCS = shell32.rc diff --git a/dlls/shell32/shell32.spec b/dlls/shell32/shell32.spec index 1aa2c6b..358389f 100644 --- a/dlls/shell32/shell32.spec +++ b/dlls/shell32/shell32.spec @@ -390,6 +390,7 @@ @ stdcall SHGetPathFromIDListA(ptr ptr) @ stdcall SHGetPathFromIDListW(ptr ptr) @ stdcall SHGetPropertyStoreFromParsingName(wstr ptr long ptr ptr) +@ stdcall SHGetPropertyStoreForWindow(ptr ptr ptr) @ stdcall SHGetSettings(ptr long) @ stdcall SHGetSpecialFolderLocation(long long ptr) @ stdcall SHGetSpecialFolderPathA(long ptr long long) diff --git a/dlls/shell32/tests/appusermodel.c b/dlls/shell32/tests/appusermodel.c index dd83ef8..b6cdfd1 100644 --- a/dlls/shell32/tests/appusermodel.c +++ b/dlls/shell32/tests/appusermodel.c @@ -18,18 +18,25 @@ */
#define COBJMACROS +#define NONAMELESSUNION
#include "windows.h" #include "shlguid.h" #include "shobjidl.h" +#include "propsys.h" +#include "propkey.h" #include "shlobj.h" #include "shellapi.h" #include "wine/test.h"
#include "shell32_test.h"
+ +static GUID PKEY_WineTest = {0x7b317433,0xdfa3,0x4c44,{0xad,0x3e,0x2f,0x80,0x4b,0x90,0xdb,0xf4}}; + static HRESULT (WINAPI *pSetCurrentProcessExplicitAppUserModelID)(const WCHAR *id); static HRESULT (WINAPI *pGetCurrentProcessExplicitAppUserModelID)(WCHAR **id); +static HRESULT (WINAPI *pSHGetPropertyStoreForWindow)(HWND hwnd, REFIID iid, void **ppv);
static void test_process_aum_id(void) { @@ -104,7 +111,149 @@ static void test_process_aum_id(void) } }
-/* TODO: Test the window property store (SHGetPropertyStoreForWindow) */ +static LRESULT WINAPI wndprocA(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + return DefWindowProcA(hwnd, msg, wparam, lparam); +} + + +static void test_window_propstore(void) +{ + IPropertyStore *store; + HRESULT hr; + PROPERTYKEY pkey; + PROPVARIANT propvar; + DWORD count; + WNDCLASSA cls; + HWND hwndMain; + WCHAR nums[] = { '1','2','3','4','5',0 }; + WCHAR hello[] = { 'H','e','l','l','o',0 }; + + if (!pSHGetPropertyStoreForWindow) + { + win_skip("SHGetPropertyStoreForWindow is not available\n"); + return; + } + + /* create a small window to carry out our tests */ + cls.style = CS_DBLCLKS; + cls.lpfnWndProc = wndprocA; + cls.cbClsExtra = 0; + cls.cbWndExtra = 0; + cls.hInstance = GetModuleHandleA(NULL); + cls.hIcon = 0; + cls.hCursor = LoadCursorA(0, (LPCSTR)IDC_ARROW); + cls.hbrBackground = NULL; + cls.lpszMenuName = NULL; + cls.lpszClassName = "MainWindowClass"; + + ok(!!RegisterClassA(&cls), "WTF: registering class\n"); + + hwndMain = CreateWindowExA(0, "MainWindowClass", "Main window", + WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | + WS_MAXIMIZEBOX | WS_POPUP | WS_VISIBLE, + 100, 100, 200, 200, + 0, 0, GetModuleHandleA(NULL), NULL); + ok(!!hwndMain, "WTF: CreateWindowEx\n"); + + hr = pSHGetPropertyStoreForWindow(hwndMain, &IID_IPropertyStore, (void**)&store); + ok(hr == S_OK, "SHGetPropertyStoreForWindow failed, hr=%x\n", hr); + + /* crashes win7 */ + if (0) + { + hr = IPropertyStore_GetCount(store, NULL); + ok(hr == E_POINTER, "GetCount unexpected hr=%x\n", hr); + } + + hr = IPropertyStore_GetCount(store, &count); + ok(hr == S_OK /*Win7*/ || hr == E_FAIL/*Win8*/, "GetCount unexpected hr=%x\n", hr); + ok(count == 0, "GetCount returned count=%u\n", count); + + hr = IPropertyStore_Commit(store); + ok(hr == S_OK, "Commit failed, hr=%x\n", hr); + + hr = IPropertyStore_GetAt(store, 0, &pkey); + ok(hr == E_INVALIDARG /*Win7*/ || hr == E_FAIL /*Win8*/, "GetAt failed, hr=%x\n", hr); + + pkey.fmtid = PKEY_WineTest; + pkey.pid = 4; + + memset(&propvar, 0, sizeof(propvar)); + propvar.vt = VT_I4; + propvar.u.lVal = 12345; + + if (0) + { + /* Crashes on Windows 7 */ + hr = IPropertyStore_SetValue(store, NULL, &propvar); + ok(hr == E_POINTER, "SetValue failed, hr=%x\n", hr); + + hr = IPropertyStore_SetValue(store, &pkey, NULL); + ok(hr == E_POINTER, "SetValue failed, hr=%x\n", hr); + } + + /* Set an integer value */ + hr = IPropertyStore_SetValue(store, &pkey, &propvar); + ok(hr == S_OK, "SetValue failed, hr=%x\n", hr); + + memset(&propvar, 0, sizeof(propvar)); + + /* This is a really weird error code */ + hr = IPropertyStore_GetValue(store, NULL, &propvar); + todo_wine ok(hr == E_NOT_SUFFICIENT_BUFFER, "GetValue failed, hr=%x\n", hr); + + hr = IPropertyStore_GetValue(store, &pkey, NULL); + ok(hr == E_POINTER, "GetValue failed, hr=%x\n", hr); + + /* The windows implementation supports strings only and will coerce any other + * values to strings. This is undocumented, but we'll have to live with it */ + hr = IPropertyStore_GetValue(store, &pkey, &propvar); + ok(hr == S_FALSE, "GetValue failed, hr=%x\n", hr); + /* WTF: INPLACE_S_TRUNCATED would be appropriate */ + todo_wine ok(propvar.vt == VT_LPWSTR, "expected VT_LPWSTR, got %d\n", propvar.vt); + if (propvar.vt == VT_LPWSTR) + todo_wine ok(!lstrcmpW(propvar.u.pwszVal, nums), + "expected L"12345", got %s\n", wine_dbgstr_w(propvar.u.pwszVal)); + PropVariantClear(&propvar); + + /* Set a string value */ + propvar.vt = VT_LPWSTR; + propvar.u.pwszVal = hello; + + hr = IPropertyStore_SetValue(store, &pkey, &propvar); + ok(hr == S_OK, "SetValue: hr=%x\n", hr); + + /* and retrieve a string value */ + hr = IPropertyStore_GetValue(store, &pkey, &propvar); + ok(hr == S_FALSE, "GetValue failed, hr=%x\n", hr); + /* WTF: The docs require S_OK here */ + ok(propvar.vt == VT_LPWSTR, "expected VT_LPWSTR, got %d\n", propvar.vt); + ok(!lstrcmpW(propvar.u.pwszVal, hello), + "expected L"hello", got %s\n", wine_dbgstr_w(propvar.u.pwszVal)); + PropVariantClear(&propvar); + + pkey.fmtid = PKEY_WineTest; + pkey.pid = 10; + + /* Get information for field that isn't set yet */ + propvar.vt = VT_I2; + hr = IPropertyStore_GetValue(store, &pkey, &propvar); + ok(hr == S_OK, "GetValue failed, hr=%x\n", hr); + ok(propvar.vt == VT_EMPTY, "expected VT_EMPTY, got %d\n", propvar.vt); + + /* According to MSDN, we can leak memory if we don't delete our properties + * and because we're good citizens, we delete them. Note that wine doesn't + * care, it will clean everything up anyways, like you'd expect. */ + propvar.vt = VT_EMPTY; + pkey.fmtid = PKEY_WineTest; + pkey.pid = 4; + hr = IPropertyStore_SetValue(store, &pkey, &propvar); + ok(hr == S_OK, "SetValue failed, hr=%x\n", hr); + + IPropertyStore_Release(store); + DestroyWindow(hwndMain); +}
#define RESOLVE(hDll, proc) p##proc = (void*)GetProcAddress(hDll, #proc)
@@ -116,6 +265,8 @@ START_TEST(appusermodel)
RESOLVE(hShell32, SetCurrentProcessExplicitAppUserModelID); RESOLVE(hShell32, GetCurrentProcessExplicitAppUserModelID); + RESOLVE(hShell32, SHGetPropertyStoreForWindow);
test_process_aum_id(); + test_window_propstore(); } diff --git a/dlls/shell32/winpropstore.c b/dlls/shell32/winpropstore.c new file mode 100644 index 0000000..df4bd74 --- /dev/null +++ b/dlls/shell32/winpropstore.c @@ -0,0 +1,291 @@ +/* + * IPropertyStore for window-attached properties + * + * 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 "config.h" + +#include <stdarg.h> + +#include "windef.h" +#include "winbase.h" +#include "objbase.h" +#include "rpcproxy.h" +#include "propsys.h" +#include "propvarutil.h" +#include "wine/debug.h" +#include "wine/server.h" +#include "mimeole.h" + +WINE_DEFAULT_DEBUG_CHANNEL(shell); + +typedef struct { + IPropertyStore IPropertyStore_iface; + HWND window; + LONG ref; + HMODULE hPropsys; + HRESULT (WINAPI *pStgSerializePropVariant) + (const PROPVARIANT *pVar, SERIALIZEDPROPERTYVALUE **ppProp, ULONG *pcb); + HRESULT (WINAPI *pStgDeserializePropVariant) + (const SERIALIZEDPROPERTYVALUE *pprop, ULONG cbMax, PROPVARIANT *pvar); +} WindowPropertyStore; + +static inline WindowPropertyStore *impl_from_IPropertyStore(IPropertyStore *iface) +{ + return CONTAINING_RECORD(iface, WindowPropertyStore, IPropertyStore_iface); +} + +static HRESULT WINAPI WindowPropertyStore_QueryInterface(IPropertyStore *iface, REFIID iid, void **ppv) +{ + WindowPropertyStore *This = impl_from_IPropertyStore(iface); + + TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv); + + if (!ppv) return E_INVALIDARG; + + if (IsEqualIID(&IID_IUnknown, iid) || IsEqualIID(&IID_IPropertyStore, iid)) + { + *ppv = &This->IPropertyStore_iface; + } + else + { + FIXME("No interface for %s\n", debugstr_guid(iid)); + *ppv = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} +static ULONG WINAPI WindowPropertyStore_AddRef(IPropertyStore *iface) +{ + WindowPropertyStore *This = impl_from_IPropertyStore(iface); + ULONG ref = InterlockedIncrement(&This->ref); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static ULONG WINAPI WindowPropertyStore_Release(IPropertyStore *iface) +{ + WindowPropertyStore *This = impl_from_IPropertyStore(iface); + ULONG ref = InterlockedDecrement(&This->ref); + + if (ref == 0) + { + FreeLibrary(This->hPropsys); + HeapFree(GetProcessHeap(), 0, This); + } + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static HRESULT WINAPI WindowPropertyStore_GetCount(IPropertyStore *iface, + DWORD *cProps) +{ + TRACE("%p,%p\n", iface, cProps); + + /* the window property store is not enumerable */ + *cProps = 0; + + return E_FAIL; +} + +static HRESULT WINAPI WindowPropertyStore_GetAt(IPropertyStore *iface, + DWORD iProp, PROPERTYKEY *pkey) +{ + TRACE("%p,%d,%p\n", iface, iProp, pkey); + + /* the window property store is not enumerable */ + return E_FAIL; +} + +static HRESULT WINAPI WindowPropertyStore_GetValue(IPropertyStore *iface, + REFPROPERTYKEY key, PROPVARIANT *pv) +{ + WindowPropertyStore *This = impl_from_IPropertyStore(iface); + void *buffer = NULL; + DWORD buffer_size = 10; /*TODO increase*/ + DWORD prop_size = 0; + HRESULT hr = S_OK; + + TRACE("%p,%p,%p\n", iface, key, pv); + + if (!pv || !key) + return E_POINTER; + + PropVariantInit(pv); + + /* try until we received a full property */ + for (;;) + { + buffer = HeapAlloc(GetProcessHeap(), 0, buffer_size); + + if (!buffer) + { + return E_OUTOFMEMORY; + } + + SERVER_START_REQ(get_sh_window_property) + { + req->window = wine_server_user_handle(This->window); + req->fmtid_data1 = key->fmtid.Data1; + req->fmtid_data2 = key->fmtid.Data2; + req->fmtid_data3 = key->fmtid.Data3; + req->fmtid_data4_0 = key->fmtid.Data4[0]; + req->fmtid_data4_1 = key->fmtid.Data4[1]; + req->fmtid_data4_2 = key->fmtid.Data4[2]; + req->fmtid_data4_3 = key->fmtid.Data4[3]; + req->fmtid_data4_4 = key->fmtid.Data4[4]; + req->fmtid_data4_5 = key->fmtid.Data4[5]; + req->fmtid_data4_6 = key->fmtid.Data4[6]; + req->fmtid_data4_7 = key->fmtid.Data4[7]; + req->pid = key->pid; + + wine_server_set_reply(req, buffer, buffer_size); + + if (!wine_server_call(req)) + { + prop_size = reply->property_size; + } + } + SERVER_END_REQ; + + if (prop_size <= buffer_size) + break; + + buffer_size = prop_size; + HeapFree(GetProcessHeap(), 0, buffer); + } + + /* size zero == VT_EMPTY, no need to do work */ + if (prop_size > 0) + { + hr = This->pStgDeserializePropVariant((void*)buffer, prop_size, pv); + + /* HACK: Disregarding MSDN docs and common sense, windows always returns S_FALSE */ + if (hr == S_OK) + hr = S_FALSE; + } + + HeapFree(GetProcessHeap(), 0, buffer); + + return hr; +} + +static HRESULT WINAPI WindowPropertyStore_SetValue(IPropertyStore *iface, + REFPROPERTYKEY key, REFPROPVARIANT propvar) +{ + WindowPropertyStore *This = impl_from_IPropertyStore(iface); + HRESULT hr = S_OK; + SERIALIZEDPROPERTYVALUE *buffer = NULL; + DWORD size; + + TRACE("%p,%p,%p\n", iface, key, propvar); + + hr = This->pStgSerializePropVariant(propvar, &buffer, &size); + if (FAILED(hr)) + { + WARN("Failed to serialize property of type %d: hr=%08x\n", propvar->vt, hr); + return hr; + } + + /* send it to the server */ + SERVER_START_REQ(set_sh_window_property) + { + req->window = wine_server_user_handle(This->window); + req->fmtid_data1 = key->fmtid.Data1; + req->fmtid_data2 = key->fmtid.Data2; + req->fmtid_data3 = key->fmtid.Data3; + req->fmtid_data4_0 = key->fmtid.Data4[0]; + req->fmtid_data4_1 = key->fmtid.Data4[1]; + req->fmtid_data4_2 = key->fmtid.Data4[2]; + req->fmtid_data4_3 = key->fmtid.Data4[3]; + req->fmtid_data4_4 = key->fmtid.Data4[4]; + req->fmtid_data4_5 = key->fmtid.Data4[5]; + req->fmtid_data4_6 = key->fmtid.Data4[6]; + req->fmtid_data4_7 = key->fmtid.Data4[7]; + req->pid = key->pid; + + wine_server_add_data(req, buffer, size); + + wine_server_call(req); + } + SERVER_END_REQ; + + CoTaskMemFree(buffer); + + return hr; +} + +static HRESULT WINAPI WindowPropertyStore_Commit(IPropertyStore *iface) +{ + TRACE("%pn", iface); + + /* MSDN claims it to be a no-op that always succeeds, so that's what we do */ + return S_OK; +} + +static const IPropertyStoreVtbl WindowPropertyStore_Vtbl = { + WindowPropertyStore_QueryInterface, + WindowPropertyStore_AddRef, + WindowPropertyStore_Release, + WindowPropertyStore_GetCount, + WindowPropertyStore_GetAt, + WindowPropertyStore_GetValue, + WindowPropertyStore_SetValue, + WindowPropertyStore_Commit, +}; + +HRESULT WINAPI SHGetPropertyStoreForWindow(HWND hwnd, REFIID iid, void** ppv) +{ + WindowPropertyStore *This; + HRESULT hr; + + TRACE("(%p,%s,%p)\n", hwnd, debugstr_guid(iid), ppv); + + *ppv = NULL; + + This = HeapAlloc(GetProcessHeap(), 0, sizeof(WindowPropertyStore)); + if (!This) + return E_OUTOFMEMORY; + + This->hPropsys = LoadLibraryA("propsys.dll"); + if (!This->hPropsys) + { + ERR("WTF: propsys.dll not available\n"); + HeapFree(GetProcessHeap(), 0, This); + return E_FAIL; + } + + This->pStgDeserializePropVariant = (void*)GetProcAddress(This->hPropsys, "StgDeserializePropVariant"); + This->pStgSerializePropVariant = (void*)GetProcAddress(This->hPropsys, "StgSerializePropVariant"); + + This->IPropertyStore_iface.lpVtbl = &WindowPropertyStore_Vtbl; + This->window = hwnd; + This->ref = 1; + + hr = IPropertyStore_QueryInterface(&This->IPropertyStore_iface, iid, ppv); + IPropertyStore_Release(&This->IPropertyStore_iface); + + return hr; +} diff --git a/include/shellapi.h b/include/shellapi.h index 8492155..f13cc6b 100644 --- a/include/shellapi.h +++ b/include/shellapi.h @@ -647,6 +647,7 @@ HRESULT WINAPI SHEnumerateUnreadMailAccountsA(HKEY,DWORD,LPSTR,INT); HRESULT WINAPI SHEnumerateUnreadMailAccountsW(HKEY,DWORD,LPWSTR,INT); #define SHEnumerateUnreadMailAccounts WINELIB_NAME_AW(SHEnumerateUnreadMailAccounts)
+HRESULT WINAPI SHGetPropertyStoreForWindow(HWND hwnd,REFIID riid,void **ppv);
#ifdef __cplusplus } /* extern "C" */ diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h index 8b186fa..bb5fecc 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -3782,6 +3782,57 @@ struct get_window_properties_reply };
+struct set_sh_window_property_request +{ + struct request_header __header; + user_handle_t window; + unsigned int fmtid_data1; + unsigned short fmtid_data2; + unsigned short fmtid_data3; + unsigned char fmtid_data4_0; + unsigned char fmtid_data4_1; + unsigned char fmtid_data4_2; + unsigned char fmtid_data4_3; + unsigned char fmtid_data4_4; + unsigned char fmtid_data4_5; + unsigned char fmtid_data4_6; + unsigned char fmtid_data4_7; + unsigned int pid; + /* VARARG(property,bytes); */ + char __pad_36[4]; +}; +struct set_sh_window_property_reply +{ + struct reply_header __header; +}; + + +struct get_sh_window_property_request +{ + struct request_header __header; + user_handle_t window; + unsigned int fmtid_data1; + unsigned short fmtid_data2; + unsigned short fmtid_data3; + unsigned char fmtid_data4_0; + unsigned char fmtid_data4_1; + unsigned char fmtid_data4_2; + unsigned char fmtid_data4_3; + unsigned char fmtid_data4_4; + unsigned char fmtid_data4_5; + unsigned char fmtid_data4_6; + unsigned char fmtid_data4_7; + unsigned int pid; + char __pad_36[4]; +}; +struct get_sh_window_property_reply +{ + struct reply_header __header; + unsigned int property_size; + /* VARARG(property,bytes); */ + char __pad_12[4]; +}; +
struct create_winstation_request { @@ -5460,6 +5511,8 @@ enum request REQ_remove_window_property, REQ_get_window_property, REQ_get_window_properties, + REQ_set_sh_window_property, + REQ_get_sh_window_property, REQ_create_winstation, REQ_open_winstation, REQ_close_winstation, @@ -5733,6 +5786,8 @@ union generic_request struct remove_window_property_request remove_window_property_request; struct get_window_property_request get_window_property_request; struct get_window_properties_request get_window_properties_request; + struct set_sh_window_property_request set_sh_window_property_request; + struct get_sh_window_property_request get_sh_window_property_request; struct create_winstation_request create_winstation_request; struct open_winstation_request open_winstation_request; struct close_winstation_request close_winstation_request; @@ -6004,6 +6059,8 @@ union generic_reply struct remove_window_property_reply remove_window_property_reply; struct get_window_property_reply get_window_property_reply; struct get_window_properties_reply get_window_properties_reply; + struct set_sh_window_property_reply set_sh_window_property_reply; + struct get_sh_window_property_reply get_sh_window_property_reply; struct create_winstation_reply create_winstation_reply; struct open_winstation_reply open_winstation_reply; struct close_winstation_reply close_winstation_reply; @@ -6096,6 +6153,6 @@ union generic_reply struct terminate_job_reply terminate_job_reply; };
-#define SERVER_PROTOCOL_VERSION 481 +#define SERVER_PROTOCOL_VERSION 482
#endif /* __WINE_WINE_SERVER_PROTOCOL_H */ diff --git a/server/protocol.def b/server/protocol.def index 0ff1a6b..a64c318 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -2710,6 +2710,44 @@ enum coords_relative VARARG(props,properties); /* list of properties */ @END
+/* Set a shell property on a window */ +@REQ(set_sh_window_property) + user_handle_t window; + unsigned int fmtid_data1; /* key: fmtid */ + unsigned short fmtid_data2; + unsigned short fmtid_data3; + unsigned char fmtid_data4_0; + unsigned char fmtid_data4_1; + unsigned char fmtid_data4_2; + unsigned char fmtid_data4_3; + unsigned char fmtid_data4_4; + unsigned char fmtid_data4_5; + unsigned char fmtid_data4_6; + unsigned char fmtid_data4_7; + unsigned int pid; /* key: pid */ + VARARG(property,bytes); +@REPLY +@END + +/* Get a shell property from a window */ +@REQ(get_sh_window_property) + user_handle_t window; /* the window */ + unsigned int fmtid_data1; /* key: fmtid */ + unsigned short fmtid_data2; + unsigned short fmtid_data3; + unsigned char fmtid_data4_0; + unsigned char fmtid_data4_1; + unsigned char fmtid_data4_2; + unsigned char fmtid_data4_3; + unsigned char fmtid_data4_4; + unsigned char fmtid_data4_5; + unsigned char fmtid_data4_6; + unsigned char fmtid_data4_7; + unsigned int pid; /* key: pid */ +@REPLY + unsigned int property_size; + VARARG(property,bytes); +@END
/* Create a window station */ @REQ(create_winstation) diff --git a/server/request.h b/server/request.h index 760466c..3795017 100644 --- a/server/request.h +++ b/server/request.h @@ -282,6 +282,8 @@ DECL_HANDLER(set_window_property); DECL_HANDLER(remove_window_property); DECL_HANDLER(get_window_property); DECL_HANDLER(get_window_properties); +DECL_HANDLER(set_sh_window_property); +DECL_HANDLER(get_sh_window_property); DECL_HANDLER(create_winstation); DECL_HANDLER(open_winstation); DECL_HANDLER(close_winstation); @@ -554,6 +556,8 @@ static const req_handler req_handlers[REQ_NB_REQUESTS] = (req_handler)req_remove_window_property, (req_handler)req_get_window_property, (req_handler)req_get_window_properties, + (req_handler)req_set_sh_window_property, + (req_handler)req_get_sh_window_property, (req_handler)req_create_winstation, (req_handler)req_open_winstation, (req_handler)req_close_winstation, @@ -1756,6 +1760,37 @@ C_ASSERT( FIELD_OFFSET(struct get_window_properties_request, window) == 12 ); C_ASSERT( sizeof(struct get_window_properties_request) == 16 ); C_ASSERT( FIELD_OFFSET(struct get_window_properties_reply, total) == 8 ); C_ASSERT( sizeof(struct get_window_properties_reply) == 16 ); +C_ASSERT( FIELD_OFFSET(struct set_sh_window_property_request, window) == 12 ); +C_ASSERT( FIELD_OFFSET(struct set_sh_window_property_request, fmtid_data1) == 16 ); +C_ASSERT( FIELD_OFFSET(struct set_sh_window_property_request, fmtid_data2) == 20 ); +C_ASSERT( FIELD_OFFSET(struct set_sh_window_property_request, fmtid_data3) == 22 ); +C_ASSERT( FIELD_OFFSET(struct set_sh_window_property_request, fmtid_data4_0) == 24 ); +C_ASSERT( FIELD_OFFSET(struct set_sh_window_property_request, fmtid_data4_1) == 25 ); +C_ASSERT( FIELD_OFFSET(struct set_sh_window_property_request, fmtid_data4_2) == 26 ); +C_ASSERT( FIELD_OFFSET(struct set_sh_window_property_request, fmtid_data4_3) == 27 ); +C_ASSERT( FIELD_OFFSET(struct set_sh_window_property_request, fmtid_data4_4) == 28 ); +C_ASSERT( FIELD_OFFSET(struct set_sh_window_property_request, fmtid_data4_5) == 29 ); +C_ASSERT( FIELD_OFFSET(struct set_sh_window_property_request, fmtid_data4_6) == 30 ); +C_ASSERT( FIELD_OFFSET(struct set_sh_window_property_request, fmtid_data4_7) == 31 ); +C_ASSERT( FIELD_OFFSET(struct set_sh_window_property_request, pid) == 32 ); +C_ASSERT( sizeof(struct set_sh_window_property_request) == 40 ); +C_ASSERT( sizeof(struct set_sh_window_property_reply) == 8 ); +C_ASSERT( FIELD_OFFSET(struct get_sh_window_property_request, window) == 12 ); +C_ASSERT( FIELD_OFFSET(struct get_sh_window_property_request, fmtid_data1) == 16 ); +C_ASSERT( FIELD_OFFSET(struct get_sh_window_property_request, fmtid_data2) == 20 ); +C_ASSERT( FIELD_OFFSET(struct get_sh_window_property_request, fmtid_data3) == 22 ); +C_ASSERT( FIELD_OFFSET(struct get_sh_window_property_request, fmtid_data4_0) == 24 ); +C_ASSERT( FIELD_OFFSET(struct get_sh_window_property_request, fmtid_data4_1) == 25 ); +C_ASSERT( FIELD_OFFSET(struct get_sh_window_property_request, fmtid_data4_2) == 26 ); +C_ASSERT( FIELD_OFFSET(struct get_sh_window_property_request, fmtid_data4_3) == 27 ); +C_ASSERT( FIELD_OFFSET(struct get_sh_window_property_request, fmtid_data4_4) == 28 ); +C_ASSERT( FIELD_OFFSET(struct get_sh_window_property_request, fmtid_data4_5) == 29 ); +C_ASSERT( FIELD_OFFSET(struct get_sh_window_property_request, fmtid_data4_6) == 30 ); +C_ASSERT( FIELD_OFFSET(struct get_sh_window_property_request, fmtid_data4_7) == 31 ); +C_ASSERT( FIELD_OFFSET(struct get_sh_window_property_request, pid) == 32 ); +C_ASSERT( sizeof(struct get_sh_window_property_request) == 40 ); +C_ASSERT( FIELD_OFFSET(struct get_sh_window_property_reply, property_size) == 8 ); +C_ASSERT( sizeof(struct get_sh_window_property_reply) == 16 ); C_ASSERT( FIELD_OFFSET(struct create_winstation_request, flags) == 12 ); C_ASSERT( FIELD_OFFSET(struct create_winstation_request, access) == 16 ); C_ASSERT( FIELD_OFFSET(struct create_winstation_request, attributes) == 20 ); diff --git a/server/trace.c b/server/trace.c index 42cff99..3d1259f 100644 --- a/server/trace.c +++ b/server/trace.c @@ -3214,6 +3214,47 @@ static void dump_get_window_properties_reply( const struct get_window_properties dump_varargs_properties( ", props=", cur_size ); }
+static void dump_set_sh_window_property_request( const struct set_sh_window_property_request *req ) +{ + fprintf( stderr, " window=%08x", req->window ); + fprintf( stderr, ", fmtid_data1=%08x", req->fmtid_data1 ); + fprintf( stderr, ", fmtid_data2=%04x", req->fmtid_data2 ); + fprintf( stderr, ", fmtid_data3=%04x", req->fmtid_data3 ); + fprintf( stderr, ", fmtid_data4_0=%02x", req->fmtid_data4_0 ); + fprintf( stderr, ", fmtid_data4_1=%02x", req->fmtid_data4_1 ); + fprintf( stderr, ", fmtid_data4_2=%02x", req->fmtid_data4_2 ); + fprintf( stderr, ", fmtid_data4_3=%02x", req->fmtid_data4_3 ); + fprintf( stderr, ", fmtid_data4_4=%02x", req->fmtid_data4_4 ); + fprintf( stderr, ", fmtid_data4_5=%02x", req->fmtid_data4_5 ); + fprintf( stderr, ", fmtid_data4_6=%02x", req->fmtid_data4_6 ); + fprintf( stderr, ", fmtid_data4_7=%02x", req->fmtid_data4_7 ); + fprintf( stderr, ", pid=%08x", req->pid ); + dump_varargs_bytes( ", property=", cur_size ); +} + +static void dump_get_sh_window_property_request( const struct get_sh_window_property_request *req ) +{ + fprintf( stderr, " window=%08x", req->window ); + fprintf( stderr, ", fmtid_data1=%08x", req->fmtid_data1 ); + fprintf( stderr, ", fmtid_data2=%04x", req->fmtid_data2 ); + fprintf( stderr, ", fmtid_data3=%04x", req->fmtid_data3 ); + fprintf( stderr, ", fmtid_data4_0=%02x", req->fmtid_data4_0 ); + fprintf( stderr, ", fmtid_data4_1=%02x", req->fmtid_data4_1 ); + fprintf( stderr, ", fmtid_data4_2=%02x", req->fmtid_data4_2 ); + fprintf( stderr, ", fmtid_data4_3=%02x", req->fmtid_data4_3 ); + fprintf( stderr, ", fmtid_data4_4=%02x", req->fmtid_data4_4 ); + fprintf( stderr, ", fmtid_data4_5=%02x", req->fmtid_data4_5 ); + fprintf( stderr, ", fmtid_data4_6=%02x", req->fmtid_data4_6 ); + fprintf( stderr, ", fmtid_data4_7=%02x", req->fmtid_data4_7 ); + fprintf( stderr, ", pid=%08x", req->pid ); +} + +static void dump_get_sh_window_property_reply( const struct get_sh_window_property_reply *req ) +{ + fprintf( stderr, " property_size=%08x", req->property_size ); + dump_varargs_bytes( ", property=", cur_size ); +} + static void dump_create_winstation_request( const struct create_winstation_request *req ) { fprintf( stderr, " flags=%08x", req->flags ); @@ -4421,6 +4462,8 @@ static const dump_func req_dumpers[REQ_NB_REQUESTS] = { (dump_func)dump_remove_window_property_request, (dump_func)dump_get_window_property_request, (dump_func)dump_get_window_properties_request, + (dump_func)dump_set_sh_window_property_request, + (dump_func)dump_get_sh_window_property_request, (dump_func)dump_create_winstation_request, (dump_func)dump_open_winstation_request, (dump_func)dump_close_winstation_request, @@ -4690,6 +4733,8 @@ static const dump_func reply_dumpers[REQ_NB_REQUESTS] = { (dump_func)dump_remove_window_property_reply, (dump_func)dump_get_window_property_reply, (dump_func)dump_get_window_properties_reply, + NULL, + (dump_func)dump_get_sh_window_property_reply, (dump_func)dump_create_winstation_reply, (dump_func)dump_open_winstation_reply, NULL, @@ -4959,6 +5004,8 @@ static const char * const req_names[REQ_NB_REQUESTS] = { "remove_window_property", "get_window_property", "get_window_properties", + "set_sh_window_property", + "get_sh_window_property", "create_winstation", "open_winstation", "close_winstation", diff --git a/server/window.c b/server/window.c index d089149..226ac2a 100644 --- a/server/window.c +++ b/server/window.c @@ -39,6 +39,8 @@ #include "user.h" #include "unicode.h"
+#define MIN(a, b) ((a) < (b) ? (a) : (b)) + /* a window property */ struct property { @@ -54,6 +56,25 @@ enum property_type PROP_TYPE_ATOM /* plain atom */ };
+typedef struct +{ + struct { + unsigned int data1; + unsigned short data2; + unsigned short data3; + unsigned char data4[8]; + } fmtid; + unsigned int pid; +} propkey_t; + +/* a shell property */ +struct sh_property +{ + struct list entry; /* list */ + propkey_t key; /* key (GUID+fmtid) */ + unsigned int value_size; /* size of the data */ + unsigned char value[1]; /* data */ +};
struct window { @@ -89,6 +110,7 @@ struct window int prop_inuse; /* number of in-use window properties */ int prop_alloc; /* number of allocated window properties */ struct property *properties; /* window properties array */ + struct list sh_properties; /* list of struct sh_property */ int nb_extra_bytes; /* number of extra bytes */ char extra_bytes[1]; /* extra bytes storage */ }; @@ -380,6 +402,83 @@ static inline void destroy_properties( struct window *win ) free( win->properties ); }
+static inline int propkey_equal(const propkey_t *a, const propkey_t *b) +{ + return a->fmtid.data1 == b->fmtid.data1 + && a->fmtid.data2 == b->fmtid.data2 + && a->fmtid.data3 == b->fmtid.data3 + && !memcmp(a->fmtid.data4, b->fmtid.data4, 8) + && a->pid == b->pid; +} + +/* set a shell property on the window */ +static inline void set_sh_property( struct window *win, const propkey_t *key, + unsigned int value_size, const void *value ) +{ + struct sh_property *cursor; + struct sh_property *cursor2; + + /* first, remove any old properties (hopefully just one) with the same key */ + LIST_FOR_EACH_ENTRY_SAFE( cursor, cursor2, &win->sh_properties, struct sh_property, entry ) + { + if (!propkey_equal( key, &cursor->key )) + continue; + + list_remove( &cursor->entry ); + free( cursor ); + } + + /* size zero means delete, in that case we're already done */ + if (value_size == 0) + return; + + /* otherwise, we now allocate space and save it */ + cursor = mem_alloc( sizeof(*cursor) + value_size ); + if (!cursor) + return; + + cursor->key = *key; + cursor->value_size = value_size; + memcpy( cursor->value, value, value_size ); + + list_add_head( &win->sh_properties, &cursor->entry ); +} + +/* get a shell property from the window */ +static inline void get_sh_property( struct window *win, const propkey_t *key, + unsigned int buffer_size, void *buffer, + unsigned int *property_size) +{ + struct sh_property *cursor; + + /* default if no property is found */ + *property_size = 0; + + /* find it */ + LIST_FOR_EACH_ENTRY( cursor, &win->sh_properties, struct sh_property, entry ) + { + if (!propkey_equal( key, &cursor->key )) + continue; + + *property_size = cursor->value_size; + memcpy(buffer, cursor->value, MIN(cursor->value_size, buffer_size)); + break; + } +} + +/* free all shell properties */ +static inline void destroy_sh_properties( struct window *win ) +{ + struct sh_property *cursor; + struct sh_property *cursor2; + + LIST_FOR_EACH_ENTRY_SAFE( cursor, cursor2, &win->sh_properties, struct sh_property, entry ) + { + list_remove( &cursor->entry ); + free( cursor ); + } +} + /* detach a window from its owner thread but keep the window around */ static void detach_window_thread( struct window *win ) { @@ -496,6 +595,7 @@ static struct window *create_window( struct window *parent, struct window *owner memset( win->extra_bytes, 0, extra_bytes ); list_init( &win->children ); list_init( &win->unlinked ); + list_init( &win->sh_properties );
/* if parent belongs to a different thread and the window isn't */ /* top-level, attach the two threads */ @@ -1857,6 +1957,7 @@ void destroy_window( struct window *win ) free_hotkeys( win->desktop, win->handle ); free_user_handle( win->handle ); destroy_properties( win ); + destroy_sh_properties( win ); list_remove( &win->entry ); if (is_desktop_window(win)) { @@ -2717,6 +2818,53 @@ DECL_HANDLER(get_window_property) } }
+/* set a shell property */ +DECL_HANDLER(set_sh_window_property) +{ + struct window *win = get_window( req->window ); + propkey_t key = { + { + req->fmtid_data1, + req->fmtid_data2, + req->fmtid_data3, + { req->fmtid_data4_0, req->fmtid_data4_1, + req->fmtid_data4_2, req->fmtid_data4_3, + req->fmtid_data4_4, req->fmtid_data4_5, + req->fmtid_data4_6, req->fmtid_data4_7 } + }, + req->pid + }; + + if (win) + { + set_sh_property(win, &key, get_req_data_size(), get_req_data()); + } +} + +/* get a shell property */ +DECL_HANDLER(get_sh_window_property) +{ + struct window *win = get_window( req->window ); + propkey_t key = { + { + req->fmtid_data1, + req->fmtid_data2, + req->fmtid_data3, + { req->fmtid_data4_0, req->fmtid_data4_1, + req->fmtid_data4_2, req->fmtid_data4_3, + req->fmtid_data4_4, req->fmtid_data4_5, + req->fmtid_data4_6, req->fmtid_data4_7 } + }, + req->pid + }; + + if (win) + { + get_sh_property(win, &key, get_reply_max_size(), + set_reply_data_size( get_reply_max_size() ), + &reply->property_size); + } +}
/* get the list of properties of a window */ DECL_HANDLER(get_window_properties)
If the window has an AppUserModelID set, it will be exported as WM_CLASS. As a fallback, the process-wide explicit AppUserModelID is used, and if that isn't present either, the full path of the executable will be utilized.
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/shell32/winpropstore.c | 12 ++++ dlls/user32/message.c | 2 +- dlls/user32/user_private.h | 5 +- dlls/winex11.drv/window.c | 160 +++++++++++++++++++++++++++++++++++--------- 4 files changed, 144 insertions(+), 35 deletions(-)
diff --git a/dlls/shell32/winpropstore.c b/dlls/shell32/winpropstore.c index df4bd74..6aea596 100644 --- a/dlls/shell32/winpropstore.c +++ b/dlls/shell32/winpropstore.c @@ -33,8 +33,14 @@ #include "wine/server.h" #include "mimeole.h"
+#include "initguid.h" +#include "propkey.h" + WINE_DEFAULT_DEBUG_CHANNEL(shell);
+/* keep in sync with dlls/user32/user_private.h */ +#define WM_WINE_APPUSERMODEL_ID_CHANGED 0x80002000 + typedef struct { IPropertyStore IPropertyStore_iface; HWND window; @@ -234,6 +240,12 @@ static HRESULT WINAPI WindowPropertyStore_SetValue(IPropertyStore *iface,
CoTaskMemFree(buffer);
+ /* If we recognize the property, we notify the window */ + if (IsEqualGUID(key, &PKEY_AppUserModel_ID)) + { + PostMessageW(This->window, WM_WINE_APPUSERMODEL_ID_CHANGED, 0, 0); + } + return hr; }
diff --git a/dlls/user32/message.c b/dlls/user32/message.c index d122980..c29455f 100644 --- a/dlls/user32/message.c +++ b/dlls/user32/message.c @@ -1889,7 +1889,7 @@ static LRESULT handle_internal_message( HWND hwnd, UINT msg, WPARAM wparam, LPAR } return USER_Driver->pClipCursor( NULL ); default: - if (msg >= WM_WINE_FIRST_DRIVER_MSG && msg <= WM_WINE_LAST_DRIVER_MSG) + if (msg >= WM_WINE_FIRST_DRIVER_MSG && msg <= WM_WINE_LAST_DRIVER_NOTIFY_MSG) return USER_Driver->pWindowMessage( hwnd, msg, wparam, lparam ); FIXME( "unknown internal message %x\n", msg ); return 0; diff --git a/dlls/user32/user_private.h b/dlls/user32/user_private.h index d3affb0..2226df6 100644 --- a/dlls/user32/user_private.h +++ b/dlls/user32/user_private.h @@ -54,7 +54,10 @@ enum wine_internal_message WM_WINE_MOUSE_LL_HOOK, WM_WINE_CLIPCURSOR, WM_WINE_FIRST_DRIVER_MSG = 0x80001000, /* range of messages reserved for the USER driver */ - WM_WINE_LAST_DRIVER_MSG = 0x80001fff + WM_WINE_LAST_DRIVER_MSG = 0x80001ff, + WM_WINE_FIRST_DRIVER_NOTIFY_MSG = 0x80002000, /* range of defined messages to notify the driver */ + WM_WINE_APPUSERMODEL_ID_CHANGED = WM_WINE_FIRST_DRIVER_NOTIFY_MSG, + WM_WINE_LAST_DRIVER_NOTIFY_MSG = 0x80002fff };
typedef struct tagUSER_DRIVER { diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index 50c3290..b181200 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -22,6 +22,9 @@
#include "config.h"
+#define NONAMELESSUNION +#define COBJMACROS + #include <stdarg.h> #include <stdlib.h> #include <stdio.h> @@ -42,11 +45,21 @@ #include "winuser.h" #include "wine/unicode.h"
+/* rpcasync.h declares ULONG Status; Xlib.h does #define Status int */ +#undef Status +#include "shobjidl.h" +#include "propsys.h" +#include "propvarutil.h" +#include "shellapi.h" + #include "x11drv.h" #include "wine/debug.h" #include "wine/server.h" #include "mwm.h"
+#include "initguid.h" +#include "propkey.h" + WINE_DEFAULT_DEBUG_CHANNEL(x11drv);
#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 @@ -65,6 +78,9 @@ WINE_DEFAULT_DEBUG_CHANNEL(x11drv); #define _NET_WM_STATE_ADD 1 #define _NET_WM_STATE_TOGGLE 2
+/* keep in sync with dlls/user32/user_private.h */ +#define WM_WINE_APPUSERMODEL_ID_CHANGED 0x80002000 + static const unsigned int net_wm_state_atoms[NB_NET_WM_STATES] = { XATOM__NET_WM_STATE_FULLSCREEN, @@ -797,34 +813,105 @@ 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(HWND hWindow) { - static char *name; + WCHAR *explicit_id = NULL; + WCHAR module[MAX_PATH]; + DWORD len; + IPropertyStore *store = NULL; + HRESULT hr;
- if (!name) + /* Get the window-specific AppUserModelID */ + hr = SHGetPropertyStoreForWindow( hWindow, &IID_IPropertyStore, (void**)&store ); + if (SUCCEEDED( hr )) { - WCHAR module[MAX_PATH]; - DWORD len = GetModuleFileNameW( 0, module, MAX_PATH ); - if (len && len < MAX_PATH) - { - char *ptr; - WCHAR *p, *appname = module; + PROPVARIANT v; + char *ptr = NULL; + DWORD len; + + PropVariantInit(&v);
- 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 ); + hr = IPropertyStore_GetValue( store, &PKEY_AppUserModel_ID, &v ); + if (SUCCEEDED( hr ) && v.vt == VT_LPWSTR) + { + len = WideCharToMultiByte( CP_UTF8, 0, v.u.pwszVal, -1, NULL, 0, NULL, NULL ); if ((ptr = HeapAlloc( GetProcessHeap(), 0, len ))) { - WideCharToMultiByte( CP_UNIXCP, 0, appname, -1, ptr, len, NULL, NULL ); - name = ptr; + WideCharToMultiByte( CP_UTF8, 0, v.u.pwszVal, -1, ptr, len, NULL, NULL ); } } + + IPropertyStore_Release( store ); + PropVariantClear( &v ); + + if ( ptr ) + return ptr; + } + + /* A process-wide explicit AppUserModelID might be available */ + if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID( &explicit_id ))) + { + char *ptr; + DWORD len; + + len = WideCharToMultiByte( CP_UTF8, 0, explicit_id, -1, NULL, 0, NULL, NULL ); + if ((ptr = HeapAlloc( GetProcessHeap(), 0, len ))) + { + WideCharToMultiByte( CP_UTF8, 0, explicit_id, -1, ptr, len, NULL, NULL ); + + CoTaskMemFree( explicit_id ); + + return ptr; + } + + CoTaskMemFree( explicit_id ); + return NULL; + } + + /* Employ a fallback algorithm */ + + /* The Windows taskbar displays the same exe in two different directories + * as two different icons => the full path is used */ + len = GetModuleFileNameW( 0, module, MAX_PATH ); + if (len && len < MAX_PATH) + { + char *ptr; + + len = WideCharToMultiByte( CP_UTF8, 0, module, -1, NULL, 0, NULL, NULL ); + if ((ptr = HeapAlloc( GetProcessHeap(), 0, len ))) + { + WideCharToMultiByte( CP_UTF8, 0, module, -1, + ptr, len, NULL, NULL ); + + return ptr; + } + } + + return NULL; +} + +static void set_class_hints ( Display *display, Window window, HWND hwnd ) +{ + XClassHint *class_hints; + + /* class hints */ + if ((class_hints = XAllocClassHint())) + { + static char wine[] = "Wine"; + char *id = get_appusermodel_id( hwnd ); + + class_hints->res_name = wine; + class_hints->res_class = id; + XSetClassHint( display, window, class_hints ); + XFree( class_hints ); + + HeapFree( GetProcessHeap(), 0, id ); } - return name; }
@@ -833,13 +920,11 @@ static char *get_process_name(void) * * Set the window manager hints that don't change over the lifetime of a window. */ -static void set_initial_wm_hints( Display *display, Window window ) +static void set_initial_wm_hints( Display *display, Window window, HWND hWnd ) { long i; Atom protocols[3]; Atom dndVersion = WINE_XDND_VERSION; - XClassHint *class_hints; - char *process_name = get_process_name();
/* wm protocols */ i = 0; @@ -849,16 +934,7 @@ static void set_initial_wm_hints( Display *display, Window window ) XChangeProperty( display, window, x11drv_atom(WM_PROTOCOLS), XA_ATOM, 32, PropModeReplace, (unsigned char *)protocols, i );
- /* class hints */ - if ((class_hints = XAllocClassHint())) - { - static char wine[] = "Wine"; - - class_hints->res_name = process_name; - class_hints->res_class = wine; - XSetClassHint( display, window, class_hints ); - XFree( class_hints ); - } + set_class_hints( display, window, hWnd );
/* set the WM_CLIENT_MACHINE and WM_LOCALE_NAME properties */ XSetWMProperties(display, window, NULL, NULL, NULL, 0, NULL, NULL, NULL); @@ -1505,7 +1581,7 @@ static void create_whole_window( struct x11drv_win_data *data ) data->vis.visual, mask, &attr ); if (!data->whole_window) goto done;
- set_initial_wm_hints( data->display, data->whole_window ); + set_initial_wm_hints( data->display, data->whole_window, data->hwnd ); set_wm_hints( data );
XSaveContext( data->display, data->whole_window, winContext, (char *)data->hwnd ); @@ -1577,7 +1653,6 @@ static void destroy_whole_window( struct x11drv_win_data *data, BOOL already_des RemovePropA( data->hwnd, whole_window_prop ); }
- /********************************************************************** * set_window_visual * @@ -1717,7 +1792,7 @@ BOOL create_desktop_win_data( Window win ) data->whole_window = win; data->managed = TRUE; SetPropA( data->hwnd, whole_window_prop, (HANDLE)win ); - set_initial_wm_hints( display, win ); + set_initial_wm_hints( display, win, data->hwnd ); release_win_data( data ); if (thread_data->clip_window) XReparentWindow( display, thread_data->clip_window, win, 0, 0 ); return TRUE; @@ -2662,6 +2737,25 @@ LRESULT CDECL X11DRV_WindowMessage( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ) return 0; case WM_X11DRV_CLIP_CURSOR: return clip_cursor_notify( hwnd, (HWND)lp ); + case WM_WINE_APPUSERMODEL_ID_CHANGED: + { + if ((data = get_win_data( hwnd ))) + { + /* The ICCCM spec says we have to withdraw the window to change WM_CLASS. + * Gnome works fine without doing that, but KDE requires it. */ + + ShowWindow( hwnd, SW_HIDE ); + + if ( data->display && data->whole_window ) + set_class_hints( data->display, data->whole_window, hwnd ); + + ShowWindow( hwnd, SW_SHOW ); + + release_win_data( data ); + } + + return 0; + } default: FIXME( "got window msg %x hwnd %p wp %lx lp %lx\n", msg, hwnd, wp, lp ); return 0;
+ /* The ICCCM spec says we have to withdraw the window to change WM_CLASS. + * Gnome works fine without doing that, but KDE requires it. */ + + ShowWindow( hwnd, SW_HIDE ); + + if ( data->display && data->whole_window ) + set_class_hints( data->display, data->whole_window, hwnd ); + + ShowWindow( hwnd, SW_SHOW );
Ouch, that's not going to work. We don't want to do this through win32 because then the application can see its window being hidden/shown (also, there doesn't seem to be any check here for whether it's already shown?).
Maybe we can check data->mapped and if so use map_window and unmap_window for this?
Propsys.dll now contains property description information for some builtin windows properties and exposes this via IPropertyDescription. This allows to resolve some property names. MSI will need this in the future.
There's still a long way to go, but a rough infrastructure is there for adding more properties and other features as needed. --- dlls/propsys/Makefile.in | 4 +- dlls/propsys/propdesc.c | 537 +++++++++++++++++++++++++++++++++++ dlls/propsys/propdesc_builtin_data.c | 85 ++++++ dlls/propsys/propsys.spec | 2 +- dlls/propsys/propsys_main.c | 6 - dlls/propsys/propsys_private.h | 23 ++ dlls/propsys/tests/propsys.c | 252 ++++++++++++++++ include/propsys.idl | 1 + 8 files changed, 902 insertions(+), 8 deletions(-) create mode 100644 dlls/propsys/propdesc.c create mode 100644 dlls/propsys/propdesc_builtin_data.c
diff --git a/dlls/propsys/Makefile.in b/dlls/propsys/Makefile.in index a445922..9231f48 100644 --- a/dlls/propsys/Makefile.in +++ b/dlls/propsys/Makefile.in @@ -5,6 +5,8 @@ IMPORTS = ole32 oleaut32 uuid C_SRCS = \ propstore.c \ propsys_main.c \ - propvar.c + propvar.c \ + propdesc.c \ + propdesc_builtin_data.c
IDL_SRCS = propsys_classes.idl diff --git a/dlls/propsys/propdesc.c b/dlls/propsys/propdesc.c new file mode 100644 index 0000000..0658fa8 --- /dev/null +++ b/dlls/propsys/propdesc.c @@ -0,0 +1,537 @@ +/* + * propsys IPropertyDescription implementation + * + * 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 +#define NONAMELESSUNION + +#include "propsys.h" +#include "propvarutil.h" + +#include "wine/debug.h" +#include "wine/unicode.h" + +#include "propsys_private.h" + +WINE_DEFAULT_DEBUG_CHANNEL(propsys); + +typedef struct { + IPropertyDescription2 IPropertyDescription2_iface; + LONG ref; + const struct propdesc_builtin_record *record; +} PropertyDescription; + +static inline PropertyDescription *impl_from_IPropertyDescription(IPropertyDescription2 *iface) +{ + return CONTAINING_RECORD(iface, PropertyDescription, IPropertyDescription2_iface); +} + +static HRESULT WINAPI PropertyDescription_QueryInterface(IPropertyDescription2 *iface, REFIID iid, void **ppv) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + + TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv); + + if (!ppv) return E_INVALIDARG; + + if (IsEqualIID(&IID_IUnknown, iid) || IsEqualIID(&IID_IPropertyDescription, iid) + || IsEqualIID(&IID_IPropertyDescription2, iid)) + { + *ppv = &This->IPropertyDescription2_iface; + } + else + { + FIXME("No interface for %s\n", debugstr_guid(iid)); + *ppv = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} +static ULONG WINAPI PropertyDescription_AddRef(IPropertyDescription2 *iface) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + ULONG ref = InterlockedIncrement(&This->ref); + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static ULONG WINAPI PropertyDescription_Release(IPropertyDescription2 *iface) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + ULONG ref = InterlockedDecrement(&This->ref); + + if (ref == 0) + { + HeapFree(GetProcessHeap(), 0, This); + } + + TRACE("(%p) refcount=%u\n", iface, ref); + + return ref; +} + +static HRESULT WINAPI PropertyDescription_GetPropertyKey(IPropertyDescription2 *iface, PROPERTYKEY *pkey) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + + TRACE("%p %p\n", iface, pkey); + + if (!pkey) + return E_POINTER; + + *pkey = This->record->key; + return S_OK; +} + +static inline HRESULT com_wcsdup(const WCHAR *str, WCHAR **out) +{ + DWORD len; + + if (!out || !str) + return E_POINTER; + + len = lstrlenW(str); + *out = CoTaskMemAlloc((len + 1) * sizeof(WCHAR)); + if (!*out) + return E_OUTOFMEMORY; + + memcpy(*out, str, (len+1) * sizeof(WCHAR)); + + return len ? S_OK : S_FALSE; +} + +static HRESULT WINAPI PropertyDescription_GetCanonicalName(IPropertyDescription2 *iface, LPWSTR *ppszName) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + + TRACE("%p %p\n", iface, ppszName); + + return com_wcsdup(This->record->canonicalName, ppszName); +} + +static HRESULT WINAPI PropertyDescription_GetPropertyType(IPropertyDescription2 *iface, VARTYPE *pvartype) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + + TRACE("%p %p\n", iface, pvartype); + + if (!pvartype) + return E_POINTER; + + *pvartype = This->record->type; + return S_OK; +} + +static HRESULT WINAPI PropertyDescription_GetDisplayName(IPropertyDescription2 *iface, LPWSTR *ppszName) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + + TRACE("%p %p\n", iface, ppszName); + + return com_wcsdup(This->record->displayName, ppszName); +} + +static HRESULT WINAPI PropertyDescription_GetEditInvitation(IPropertyDescription2 *iface, LPWSTR *ppszName) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + + TRACE("%p %p\n", iface, ppszName); + + return com_wcsdup(This->record->editInvitation, ppszName); +} + +static HRESULT WINAPI PropertyDescription_GetTypeFlags(IPropertyDescription2 *iface, + PROPDESC_TYPE_FLAGS mask, PROPDESC_TYPE_FLAGS *ppdtFlags) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + + TRACE("%p %x %p\n", iface, mask, ppdtFlags); + + if (!ppdtFlags) + return E_POINTER; + + *ppdtFlags = This->record->typeFlags & mask; + return S_OK; +} + +static HRESULT WINAPI PropertyDescription_GetViewFlags(IPropertyDescription2 *iface, PROPDESC_VIEW_FLAGS *ppdvFlags) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + + TRACE("%p %p\n", iface, ppdvFlags); + + if (!ppdvFlags) + return E_POINTER; + + *ppdvFlags = This->record->viewFlags; + return S_OK; +} + +static HRESULT WINAPI PropertyDescription_GetDefaultColumnWidth(IPropertyDescription2 *iface, UINT *p) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + + TRACE("%p %p\n", iface, p); + + if (!p) + return E_POINTER; + + *p = This->record->displayWidth; + return S_OK; +} + +static HRESULT WINAPI PropertyDescription_GetDisplayType(IPropertyDescription2 *iface, PROPDESC_DISPLAYTYPE *p) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + + TRACE("%p %p\n", iface, p); + + if (!p) + return E_POINTER; + + *p = This->record->displayType; + return S_OK; +} + +static HRESULT WINAPI PropertyDescription_GetColumnState(IPropertyDescription2 *iface, SHCOLSTATEF *p) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + + TRACE("%p %p\n", iface, p); + + if (!p) + return E_POINTER; + + *p = This->record->columnState; + return S_OK; +} + +static HRESULT WINAPI PropertyDescription_GetGroupingRange(IPropertyDescription2 *iface, PROPDESC_GROUPING_RANGE *p) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + + TRACE("%p %p\n", iface, p); + + if (!p) + return E_POINTER; + + *p = This->record->groupingRange; + return S_OK; +} + +static HRESULT WINAPI PropertyDescription_GetRelativeDescriptionType(IPropertyDescription2 *iface, PROPDESC_RELATIVEDESCRIPTION_TYPE *prdt) +{ + FIXME("%p %p: stub\n", iface, prdt); + + return E_NOTIMPL; +} + +static HRESULT WINAPI PropertyDescription_GetRelativeDescription(IPropertyDescription2 *iface, + REFPROPVARIANT propvar1, REFPROPVARIANT propvar2, LPWSTR *ppszDesc1, LPWSTR *ppszDesc2) +{ + FIXME("%p %p %p %p %p: stub\n", iface, propvar1, propvar2, ppszDesc1, ppszDesc2); + + return E_NOTIMPL; +} + +static HRESULT WINAPI PropertyDescription_GetSortDescription(IPropertyDescription2 *iface, PROPDESC_SORTDESCRIPTION *p) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + + TRACE("%p %p\n", iface, p); + + if (!p) + return E_POINTER; + + *p = This->record->sortDescription; + return S_OK; +} + +static HRESULT WINAPI PropertyDescription_GetSortDescriptionLabel(IPropertyDescription2 *iface, BOOL fDescending, LPWSTR *ppszDescription) +{ + FIXME("%p %d %p: stub\n", iface, fDescending, ppszDescription); + + return E_NOTIMPL; +} + +static HRESULT WINAPI PropertyDescription_GetAggregationType(IPropertyDescription2 *iface, PROPDESC_AGGREGATION_TYPE *p) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + + TRACE("%p %p\n", iface, p); + + if (!p) + return E_POINTER; + + *p = This->record->aggregationType; + return S_OK; +} + +static HRESULT WINAPI PropertyDescription_GetConditionType(IPropertyDescription2 *iface, + PROPDESC_CONDITION_TYPE *pcontype, CONDITION_OPERATION *popDefault) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + + TRACE("%p %p %p\n", iface, pcontype, popDefault); + + if (!pcontype || !popDefault) + return E_POINTER; + + *pcontype = This->record->conditionType; + *popDefault = This->record->conditionOperation; + + return S_OK; +} + +static HRESULT WINAPI PropertyDescription_GetEnumTypeList(IPropertyDescription2 *iface, + REFIID riid, void **ppv) +{ + FIXME("%p %p %p: stub\n", iface, riid, ppv); + + return E_NOTIMPL; +} + +static inline DWORD canonicalize_wstr(WCHAR *str, DWORD len) +{ + DWORD whitespaceFront = 0; + DWORD whitespaceBack = 0; + + /* count whitespace at the front */ + while (whitespaceFront < len && isspaceW(str[whitespaceFront])) + ++whitespaceFront; + + /* and remove it */ + if (whitespaceFront) + { + len -= whitespaceFront; + memmove(str, &str[whitespaceFront], (len+1) * sizeof(WCHAR)); + } + + /* count whitespace at the back */ + while (whitespaceBack < len && isspaceW(str[len - 1 - whitespaceBack])) + ++whitespaceBack; + + /* and remove it */ + str[len - whitespaceBack] = 0; + return len - whitespaceBack; +} + +static HRESULT WINAPI PropertyDescription_CoerceToCanonicalValue(IPropertyDescription2 *iface, PROPVARIANT *propvar) +{ + PropertyDescription *This = impl_from_IPropertyDescription(iface); + HRESULT hr = S_OK; + + FIXME("%p %p: semi-stub\n", iface, propvar); + + if (!propvar) + return E_POINTER; + + /* Step 1: Convert empty stuff to VT_EMPTY */ + if (propvar->vt == VT_EMPTY || propvar->vt == VT_NULL || + (propvar->vt == VT_LPWSTR && !propvar->u.pwszVal) || + (propvar->vt == VT_BSTR && !propvar->u.bstrVal) || + (propvar->vt == VT_LPSTR && !propvar->u.pszVal)) + /* TODO: check for empty/whitespace-only strings */ + { + PropVariantInit(propvar); + + /* VT_EMPTY is always canonical, exit here */ + return S_OK; + } + + /* Step 2: Convert to the required type */ + if (propvar->vt != This->record->type) + { + PROPVARIANT tmp; + PropVariantInit(&tmp); + + hr = PropVariantChangeType(&tmp, propvar, 0, This->record->type); + + PropVariantClear(propvar); + memcpy(propvar, &tmp, sizeof(PROPVARIANT)); + + if (FAILED(hr)) + return hr; + } + + /* Step 3: Type-specific canonicalizations */ + if (propvar->vt == VT_LPWSTR) + { + DWORD oldlen = lstrlenW(propvar->u.pwszVal); + DWORD len = canonicalize_wstr(propvar->u.pwszVal, oldlen); + + if (len == 0) + PropVariantClear(propvar); + + if (len < oldlen) + hr = INPLACE_S_TRUNCATED; + } + + /* TODO: Step4: Check property description type enumeration */ + + return hr; + +} + +static HRESULT WINAPI PropertyDescription_FormatForDisplay(IPropertyDescription2 *iface, + REFPROPVARIANT propvar, PROPDESC_FORMAT_FLAGS flags, LPWSTR *ppszDisplay) +{ + FIXME("%p %p %x %p: semi-stub\n", iface, propvar, flags, ppszDisplay); + + return PropVariantToStringAlloc(propvar, ppszDisplay); +} + +static HRESULT WINAPI PropertyDescription_IsValueCanonical(IPropertyDescription2 *iface, + REFPROPVARIANT propvar) +{ + PROPVARIANT canonical; + HRESULT hr; + + FIXME("%p %p: semi-stub\n", iface, propvar); + + if (!propvar) + return E_POINTER; + + /* TODO: do it better */ + hr = PropVariantCopy(&canonical, propvar); + if (FAILED(hr)) + return S_OK; /* fake it */ + + /* CoerceToCanonicalValue will clear the variant if it fails */ + hr = IPropertyDescription2_CoerceToCanonicalValue(iface, &canonical); + if (FAILED(hr)) + return S_OK; + + hr = canonical.vt == propvar->vt + ? (PropVariantCompare(&canonical, propvar) ? S_FALSE : S_OK) + : S_FALSE; + PropVariantClear(&canonical); + + return hr; +} + +static HRESULT WINAPI PropertyDescription_GetImageReferenceForValue(IPropertyDescription2 *iface, + REFPROPVARIANT propvar, LPWSTR *ppszImageRes) +{ + FIXME("%p %p %p: stub\n", iface, propvar, ppszImageRes); + + return E_NOTIMPL; +} + +static IPropertyDescription2Vtbl PropertyDescription_Vtbl = { + PropertyDescription_QueryInterface, + PropertyDescription_AddRef, + PropertyDescription_Release, + PropertyDescription_GetPropertyKey, + PropertyDescription_GetCanonicalName, + PropertyDescription_GetPropertyType, + PropertyDescription_GetDisplayName, + PropertyDescription_GetEditInvitation, + PropertyDescription_GetTypeFlags, + PropertyDescription_GetViewFlags, + PropertyDescription_GetDefaultColumnWidth, + PropertyDescription_GetDisplayType, + PropertyDescription_GetColumnState, + PropertyDescription_GetGroupingRange, + PropertyDescription_GetRelativeDescriptionType, + PropertyDescription_GetRelativeDescription, + PropertyDescription_GetSortDescription, + PropertyDescription_GetSortDescriptionLabel, + PropertyDescription_GetAggregationType, + PropertyDescription_GetConditionType, + PropertyDescription_GetEnumTypeList, + PropertyDescription_CoerceToCanonicalValue, + PropertyDescription_FormatForDisplay, + PropertyDescription_IsValueCanonical, + PropertyDescription_GetImageReferenceForValue +}; + +static HRESULT ConstructFromBuiltinData(const struct propdesc_builtin_record *record, REFIID iid, void **ppv) +{ + PropertyDescription *This; + HRESULT ret; + + TRACE("(%p,%s,%p)\n", record, debugstr_guid(iid), ppv); + + *ppv = NULL; + + This = HeapAlloc(GetProcessHeap(), 0, sizeof(PropertyDescription)); + if (!This) return E_OUTOFMEMORY; + + This->IPropertyDescription2_iface.lpVtbl = &PropertyDescription_Vtbl; + This->ref = 1; + This->record = record; + + ret = IPropertyDescription2_QueryInterface(&This->IPropertyDescription2_iface, iid, ppv); + IPropertyDescription2_Release(&This->IPropertyDescription2_iface); + + return ret; +} + +HRESULT WINAPI PSGetPropertyDescription(REFPROPERTYKEY propkey, REFIID riid, void **ppv) +{ + HRESULT hr = TYPE_E_ELEMENTNOTFOUND; + size_t i; + + TRACE("%p, %p, %p\n", propkey, riid, ppv); + + if (!propkey || !ppv || !riid) + return E_INVALIDARG; + + for (i = 0; i < propdesc_builtin_count; ++i) + { + const struct propdesc_builtin_record *rec = &propdesc_builtin_data[i]; + + if (IsEqualGUID(&rec->key.fmtid, &propkey->fmtid) && propkey->pid == rec->key.pid) + { + hr = ConstructFromBuiltinData(rec, riid, ppv); + break; + } + } + + return hr; +} + +HRESULT WINAPI PSGetPropertyDescriptionByName(LPCWSTR name, REFIID riid, void **ppv) +{ + HRESULT hr = TYPE_E_ELEMENTNOTFOUND; + size_t i; + + TRACE("%s, %p, %p\n", debugstr_w(name), riid, ppv); + + if (!name || !ppv || !riid) + return E_INVALIDARG; + + for (i = 0; i < propdesc_builtin_count; ++i) + { + const struct propdesc_builtin_record *rec = &propdesc_builtin_data[i]; + + if (!lstrcmpW(name, rec->canonicalName)) + { + hr = ConstructFromBuiltinData(rec, riid, ppv); + break; + } + } + + return hr; +} diff --git a/dlls/propsys/propdesc_builtin_data.c b/dlls/propsys/propdesc_builtin_data.c new file mode 100644 index 0000000..9aa87b0 --- /dev/null +++ b/dlls/propsys/propdesc_builtin_data.c @@ -0,0 +1,85 @@ +/* + * Description data for builtin properties + * + * Copyright 2015 Jonas Kümmerlin + * FIXME: Is copyright applicable here? + * + * 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 + */ + +#include "propidl.h" +#include "propsys.h" +#include "propsys_private.h" + +#include "initguid.h" +#include "propkey.h" + +struct propdesc_builtin_record propdesc_builtin_data[] = { + { + { { 0, 0, 0, { 0,0,0,0,0,0,0,0 } }, 0 }, /* key */ + VT_NULL, + { 'S','y','s','t','e','m','.','N','u','l','l',0 }, /* canonical name */ + { 0 }, /* display name */ + { 0 }, /* edit invitation */ + PDDT_STRING, /* display type */ + 20, /* display width */ + PDAT_DEFAULT, /* aggregation type */ + SHCOLSTATE_DEFAULT, /* column state */ + PDCOT_NONE, /* condition type */ + COP_IMPLICIT, /* condition operation */ + PDGR_DISCRETE, /* grouping range */ + PDSD_GENERAL, /* sort description */ + PDTF_DEFAULT, /* type flags */ + PDVF_DEFAULT /* view flags */ + }, { + {{0xf29f85e0,0x4ff9,0x1068,{0xab,0x91,0x08,0x00,0x2b,0x27,0xb3,0xd9}},2 }, /* key */ + VT_LPWSTR, + { 'S','y','s','t','e','m','.','T','i','t','l','e',0 }, /* canonical name */ + { 'T','i','t','l','e',0 }, /* display name */ + { 'A','d','d',' ','a',' ','t','i','t','l','e',0 }, /* edit invitation */ + PDDT_STRING, /* display type */ + 20, /* display width */ + PDAT_DEFAULT, /* aggregation type */ + SHCOLSTATE_TYPE_STR, /* column state */ + PDCOT_STRING, /* condition type */ + COP_WORD_EQUAL, /* condition operation */ + PDGR_ALPHANUMERIC, /* grouping range */ + PDSD_A_Z, /* sort description */ + PDTF_ISSYSTEMPROPERTY|PDTF_CANBEPURGED|PDTF_ISVIEWABLE|PDTF_CANSTACKBY|PDTF_CANGROUPBY, /* type flags */ + PDVF_DEFAULT /* view flags */ + }, { + {{0x9f4c2855,0x9f79,0x4B39,{0xa8,0xd0,0xe1,0xd4,0x2d,0xe1,0xd5,0xf3}},5}, /* key */ + VT_LPWSTR, + { 'S','y','s','t','e','m','.', + 'A','p','p','U','s','e','r','M','o','d','e','l','.', + 'I','D', 0 }, /* canonical name */ + { 'A','p','p','p','U','s','e','r','M','o','d','e','l',0 }, /* display name */ + { 0 }, /* edit invitation */ + PDDT_STRING, /* display type */ + 20, /* display width */ + PDAT_DEFAULT, /* aggregation type */ + SHCOLSTATE_TYPE_STR, /* column state */ + PDCOT_STRING, /* condition type */ + COP_WORD_EQUAL, /* condition operation */ + PDGR_DISCRETE, /* grouping range */ + PDSD_LOWEST_HIGHEST, /* sort description */ + PDTF_ISSYSTEMPROPERTY|PDTF_CANBEPURGED|PDTF_CANSTACKBY|PDTF_CANGROUPBY, /* type flags */ + PDVF_DEFAULT /* view flags */ + } + + /*TODO: add more properties */ +}; + +size_t propdesc_builtin_count = sizeof(propdesc_builtin_data)/sizeof(*propdesc_builtin_data); diff --git a/dlls/propsys/propsys.spec b/dlls/propsys/propsys.spec index 970b5c0..055a17a 100644 --- a/dlls/propsys/propsys.spec +++ b/dlls/propsys/propsys.spec @@ -80,7 +80,7 @@ @ stub PSGetNameFromPropertyKey @ stub PSGetNamedPropertyFromPropertyStorage @ stdcall PSGetPropertyDescription(ptr ptr ptr) -@ stub PSGetPropertyDescriptionByName +@ stdcall PSGetPropertyDescriptionByName(ptr ptr ptr) @ stdcall PSGetPropertyDescriptionListFromString(ptr ptr ptr) @ stub PSGetPropertyFromPropertyStorage @ stub PSGetPropertyKeyFromName diff --git a/dlls/propsys/propsys_main.c b/dlls/propsys/propsys_main.c index 7631023..c5a8c27 100644 --- a/dlls/propsys/propsys_main.c +++ b/dlls/propsys/propsys_main.c @@ -256,12 +256,6 @@ HRESULT WINAPI PSUnregisterPropertySchema(PCWSTR path) return E_NOTIMPL; }
-HRESULT WINAPI PSGetPropertyDescription(REFPROPERTYKEY propkey, REFIID riid, void **ppv) -{ - FIXME("%p, %p, %p\n", propkey, riid, ppv); - return E_NOTIMPL; -} - HRESULT WINAPI PSGetPropertyDescriptionListFromString(LPCWSTR proplist, REFIID riid, void **ppv) { FIXME("%s, %p, %p\n", debugstr_w(proplist), riid, ppv); diff --git a/dlls/propsys/propsys_private.h b/dlls/propsys/propsys_private.h index 35bbb4d..329f165 100644 --- a/dlls/propsys/propsys_private.h +++ b/dlls/propsys/propsys_private.h @@ -19,3 +19,26 @@ */
HRESULT PropertyStore_CreateInstance(IUnknown *outer, REFIID riid, void **ppv) DECLSPEC_HIDDEN; + +struct propdesc_builtin_record +{ + PROPERTYKEY key; + VARTYPE type; + WCHAR canonicalName[32]; /* extend the string lengths as needed */ + WCHAR displayName[32]; + WCHAR editInvitation[32]; + PROPDESC_DISPLAYTYPE displayType; + DWORD displayWidth; + PROPDESC_AGGREGATION_TYPE aggregationType; + SHCOLSTATEF columnState; + PROPDESC_CONDITION_TYPE conditionType; + CONDITION_OPERATION conditionOperation; + PROPDESC_GROUPING_RANGE groupingRange; + PROPDESC_SORTDESCRIPTION sortDescription; + PROPDESC_TYPE_FLAGS typeFlags; + PROPDESC_VIEW_FLAGS viewFlags; + /* TODO: add more info */ +}; + +extern struct propdesc_builtin_record propdesc_builtin_data[]; +extern size_t propdesc_builtin_count; diff --git a/dlls/propsys/tests/propsys.c b/dlls/propsys/tests/propsys.c index 7be320d..098bd55 100644 --- a/dlls/propsys/tests/propsys.c +++ b/dlls/propsys/tests/propsys.c @@ -35,6 +35,7 @@ #include "wine/test.h"
#include "initguid.h" +#include "propkey.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);
@@ -430,6 +431,256 @@ static void test_PSPropertyKeyFromString(void) } }
+static void test_propdesc(void) +{ + HRESULT hr; + WCHAR *str; + IPropertyDescription *desc; + PROPDESC_AGGREGATION_TYPE aggtype; + SHCOLSTATEF colflags; + PROPDESC_CONDITION_TYPE condtype; + CONDITION_OPERATION condop; + PROPDESC_DISPLAYTYPE displaytype; + PROPDESC_GROUPING_RANGE groupingrange; + PROPDESC_SORTDESCRIPTION sortdesc; + PROPDESC_TYPE_FLAGS typeflags; + PROPDESC_VIEW_FLAGS viewflags; + DWORD dw; + PROPERTYKEY key; + VARTYPE type; + PROPVARIANT pv; + WCHAR system_title[] = { 'S','y','s','t','e','m','.','T','i','t','l','e',0 }; + WCHAR system_appusermodel_id[] = {'S','y','s','t','e','m','.','A','p','p','U','s','e','r','M','o','d','e','l','.','I','D',0 }; + WCHAR noncanonical_title[] = { ' ',' ','H','e','l','l','o',' ','\n',0 }; + WCHAR canonical_title[] = { 'H','e','l','l','o',0 }; + WCHAR num_str[] = { '1','2','3','4','5',0 }; + WCHAR whitespace_str[] = { ' ', ' ', '\r', ' ', '\n', 0 }; + WCHAR empty_str[] = { 0 }; + + CoInitialize(NULL); + + hr = PSGetPropertyDescription(&PKEY_Title, &IID_IPropertyDescription, (void**)&desc); + ok(hr == S_OK, "Failed to retrieve description for PKEY_Title, hr=%08x\n", hr); + + hr = IPropertyDescription_GetPropertyKey(desc, &key); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(IsEqualGUID(&key.fmtid, &PKEY_Title.fmtid) && key.pid == PKEY_Title.pid, + "Unexpected property key %s,%u\n", wine_dbgstr_guid(&key.fmtid), key.pid); + + hr = IPropertyDescription_GetPropertyType(desc, &type); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(type == VT_LPWSTR, "Unexpected variant type %d\n", type); + + hr = IPropertyDescription_GetCanonicalName(desc, &str); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(!lstrcmpW(system_title, str), "Unexpected canonical name %s\n", wine_dbgstr_w(str)); + CoTaskMemFree(str); + + hr = IPropertyDescription_GetAggregationType(desc, &aggtype); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(aggtype == PDAT_DEFAULT, "Unexpected aggregation type %d\n", aggtype); + + hr = IPropertyDescription_GetColumnState(desc, &colflags); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(colflags == SHCOLSTATE_TYPE_STR, "Unexpected column state %d\n", colflags); + + hr = IPropertyDescription_GetConditionType(desc, &condtype, &condop); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(condtype == PDCOT_STRING, "Unexpected condition type %d\n", condtype); + ok(condop == COP_WORD_EQUAL, "Unexpected condition operation %d\n", condop); + + hr = IPropertyDescription_GetDefaultColumnWidth(desc, &dw); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(dw == 20, "Unexpected column width %u\n", dw); + + hr = IPropertyDescription_GetDisplayName(desc, &str); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + /* might be localized, can't compare */ + CoTaskMemFree(str); + + hr = IPropertyDescription_GetDisplayType(desc, &displaytype); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(displaytype == PDDT_STRING, "Unexpected display type %d\n", displaytype); + + hr = IPropertyDescription_GetEditInvitation(desc, &str); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + /* might be localized, can't compare */ + CoTaskMemFree(str); + + hr = IPropertyDescription_GetGroupingRange(desc, &groupingrange); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(groupingrange == PDGR_ALPHANUMERIC, "Unexpected grouping range %d\n", groupingrange); + + hr = IPropertyDescription_GetSortDescription(desc, &sortdesc); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(sortdesc == PDSD_A_Z, "Unexpected sort description %d\n", sortdesc); + + hr = IPropertyDescription_GetTypeFlags(desc, PDTF_MASK_ALL, &typeflags); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(typeflags == (PDTF_ISSYSTEMPROPERTY|PDTF_CANBEPURGED|PDTF_ISVIEWABLE|PDTF_CANSTACKBY|PDTF_CANGROUPBY), + "Unexpected type flags %x\n", typeflags); + + hr = IPropertyDescription_GetViewFlags(desc, &viewflags); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(viewflags == PDVF_DEFAULT, "Unexpected view flags %d\n", viewflags); + + /* test canonicalization */ + + /* noncanonical string */ + hr = InitPropVariantFromString(noncanonical_title, &pv); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + + hr = IPropertyDescription_IsValueCanonical(desc, &pv); + ok(hr == S_FALSE, "Unexpected hr=%08x\n", hr); + + hr = IPropertyDescription_CoerceToCanonicalValue(desc, &pv); + ok(hr == INPLACE_S_TRUNCATED, "Unexpected hr=%08x\n", hr); + ok(pv.vt == VT_LPWSTR, "Unexpected variant type %d\n", pv.vt); + ok(!lstrcmpW(pv.u.pwszVal, canonical_title), "Unexpected canonical title %s\n", wine_dbgstr_w(pv.u.pwszVal)); + + hr = IPropertyDescription_IsValueCanonical(desc, &pv); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + + PropVariantClear(&pv); + + /* string consisting entirely of whitespace */ + hr = InitPropVariantFromString(whitespace_str, &pv); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + + hr = IPropertyDescription_IsValueCanonical(desc, &pv); + ok(hr == S_FALSE, "Unexpected hr=%08x\n", hr); + + hr = IPropertyDescription_CoerceToCanonicalValue(desc, &pv); + ok(hr == INPLACE_S_TRUNCATED, "Unexpected hr=%08x\n", hr); + ok(pv.vt == VT_EMPTY, "Unexpected variant type %d\n", pv.vt); + + hr = IPropertyDescription_IsValueCanonical(desc, &pv); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + + PropVariantClear(&pv); + + /* empty string */ + hr = InitPropVariantFromString(empty_str, &pv); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + + hr = IPropertyDescription_IsValueCanonical(desc, &pv); + ok(hr == S_FALSE, "Unexpected hr=%08x\n", hr); + + hr = IPropertyDescription_CoerceToCanonicalValue(desc, &pv); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(pv.vt == VT_EMPTY, "Unexpected variant type %d\n", pv.vt); + + hr = IPropertyDescription_IsValueCanonical(desc, &pv); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + + PropVariantClear(&pv); + + /* null string */ + pv.vt = VT_LPWSTR; + pv.u.pwszVal = NULL; + + hr = IPropertyDescription_IsValueCanonical(desc, &pv); + ok(hr == S_FALSE, "Unexpected hr=%08x\n", hr); + + hr = IPropertyDescription_CoerceToCanonicalValue(desc, &pv); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(pv.vt == VT_EMPTY, "Unexpected variant type %d\n", pv.vt); + + hr = IPropertyDescription_IsValueCanonical(desc, &pv); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + + PropVariantClear(&pv); + + /* integer value */ + pv.vt = VT_I4; + pv.u.lVal = 12345; + + hr = IPropertyDescription_IsValueCanonical(desc, &pv); + ok(hr == S_FALSE, "Unexpected hr=%08x\n", hr); + + hr = IPropertyDescription_CoerceToCanonicalValue(desc, &pv); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(pv.vt == VT_LPWSTR, "Unexpected variant type %d\n", pv.vt); + ok(!lstrcmpW(pv.u.pwszVal, num_str), "Unexpected canonical title %s\n", wine_dbgstr_w(pv.u.pwszVal)); + + hr = IPropertyDescription_IsValueCanonical(desc, &pv); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + PropVariantClear(&pv); + + IPropertyDescription_Release(desc); + + /* Test another property, retrieve it by name this time */ + hr = PSGetPropertyDescriptionByName(system_appusermodel_id, &IID_IPropertyDescription, (void**)&desc); + ok(hr == S_OK, "Failed to retrieve description for 'System.AppUserModel.ID', hr=%08x\n", hr); + + hr = IPropertyDescription_GetPropertyKey(desc, &key); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(IsEqualGUID(&key.fmtid, &PKEY_AppUserModel_ID.fmtid) && key.pid == PKEY_AppUserModel_ID.pid, + "Unexpected property key %s,%u\n", wine_dbgstr_guid(&key.fmtid), key.pid); + + hr = IPropertyDescription_GetPropertyType(desc, &type); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(type == VT_LPWSTR, "Unexpected variant type %d\n", type); + + hr = IPropertyDescription_GetCanonicalName(desc, &str); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(!lstrcmpW(system_appusermodel_id, str), "Unexpected canonical name %s\n", wine_dbgstr_w(str)); + CoTaskMemFree(str); + + hr = IPropertyDescription_GetAggregationType(desc, &aggtype); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(aggtype == PDAT_DEFAULT, "Unexpected aggregation type %d\n", aggtype); + + hr = IPropertyDescription_GetColumnState(desc, &colflags); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(colflags == SHCOLSTATE_TYPE_STR, "Unexpected column state %d\n", colflags); + + hr = IPropertyDescription_GetConditionType(desc, &condtype, &condop); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(condtype == PDCOT_STRING, "Unexpected condition type %d\n", condtype); + ok(condop == COP_WORD_EQUAL, "Unexpected condition operation %d\n", condop); + + hr = IPropertyDescription_GetDefaultColumnWidth(desc, &dw); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(dw == 20, "Unexpected column width %u\n", dw); + + hr = IPropertyDescription_GetDisplayName(desc, &str); + ok(hr == S_OK || broken(hr == E_FAIL) /* win7 */, "Unexpected hr=%08x\n", hr); + /* might be localized, can't compare */ + if (SUCCEEDED(hr)) + CoTaskMemFree(str); + + hr = IPropertyDescription_GetDisplayType(desc, &displaytype); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(displaytype == PDDT_STRING, "Unexpected display type %d\n", displaytype); + + hr = IPropertyDescription_GetEditInvitation(desc, &str); + ok(hr == S_FALSE, "Unexpected hr=%08x\n", hr); + /* might be localized, can't compare */ + CoTaskMemFree(str); + + hr = IPropertyDescription_GetGroupingRange(desc, &groupingrange); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(groupingrange == PDGR_DISCRETE, "Unexpected grouping range %d\n", groupingrange); + + hr = IPropertyDescription_GetSortDescription(desc, &sortdesc); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(sortdesc == PDSD_LOWEST_HIGHEST /* win8 */ || sortdesc == PDSD_A_Z /* win7 */, + "Unexpected sort description %d\n", sortdesc); + + hr = IPropertyDescription_GetTypeFlags(desc, PDTF_MASK_ALL, &typeflags); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(typeflags == (PDTF_ISSYSTEMPROPERTY|PDTF_CANBEPURGED|PDTF_CANSTACKBY|PDTF_CANGROUPBY), + "Unexpected type flags %x\n", typeflags); + + hr = IPropertyDescription_GetViewFlags(desc, &viewflags); + ok(hr == S_OK, "Unexpected hr=%08x\n", hr); + ok(viewflags == PDVF_DEFAULT, "Unexpected view flags %d\n", viewflags); + + IPropertyDescription_Release(desc); + CoUninitialize(); +} + static void test_PSRefreshPropertySchema(void) { HRESULT ret; @@ -1327,4 +1578,5 @@ START_TEST(propsys) test_stringify(); test_intconversions(); test_serialization(); + test_propdesc(); } diff --git a/include/propsys.idl b/include/propsys.idl index 04d3fbf..fddf49a 100644 --- a/include/propsys.idl +++ b/include/propsys.idl @@ -802,6 +802,7 @@ cpp_quote("#define PKEYSTR_MAX (GUIDSTRING_MAX + 1 + PKEY_PIDSTR_MAX)") cpp_quote("HRESULT WINAPI PSStringFromPropertyKey(REFPROPERTYKEY,LPWSTR,UINT);") cpp_quote("HRESULT WINAPI PSPropertyKeyFromString(LPCWSTR,PROPERTYKEY*);") cpp_quote("HRESULT WINAPI PSGetPropertyDescription(REFPROPERTYKEY,REFIID,void **);") +cpp_quote("HRESULT WINAPI PSGetPropertyDescriptionByName(LPCWSTR,REFIID,void **);") cpp_quote("HRESULT WINAPI PSGetPropertyDescriptionListFromString(LPCWSTR,REFIID,void **);") cpp_quote("HRESULT WINAPI PSRefreshPropertySchema(void);") cpp_quote("HRESULT WINAPI PSRegisterPropertySchema(LPCWSTR);")
Msi now evaluates the MsiShortcutProperty table and applies the properties to the created shortcuts. --- dlls/msi/Makefile.in | 2 +- dlls/msi/action.c | 120 ++++++++++++++++++++++++++++++++++++++++++++ dlls/msi/tests/Makefile.in | 2 +- dlls/msi/tests/action.c | 75 +++++++++++++++++++++++++-- dlls/msi/tests/automation.c | 1 - 5 files changed, 194 insertions(+), 6 deletions(-)
diff --git a/dlls/msi/Makefile.in b/dlls/msi/Makefile.in index 79704ad..8df4b26 100644 --- a/dlls/msi/Makefile.in +++ b/dlls/msi/Makefile.in @@ -1,7 +1,7 @@ MODULE = msi.dll IMPORTLIB = msi IMPORTS = uuid urlmon wininet comctl32 shell32 shlwapi cabinet oleaut32 ole32 version user32 gdi32 advapi32 -DELAYIMPORTS = odbccp32 wintrust crypt32 imagehlp mspatcha +DELAYIMPORTS = odbccp32 wintrust crypt32 imagehlp mspatcha propsys
C_SRCS = \ action.c \ diff --git a/dlls/msi/action.c b/dlls/msi/action.c index c0ab23d..88f0c34 100644 --- a/dlls/msi/action.c +++ b/dlls/msi/action.c @@ -39,6 +39,7 @@ #include "imagehlp.h" #include "wine/unicode.h" #include "winver.h" +#include "propvarutil.h"
#define REG_PROGRESS_VALUE 13200 #define COMPONENT_PROGRESS_VALUE 24000 @@ -3888,6 +3889,122 @@ WCHAR *msi_build_icon_path( MSIPACKAGE *package, const WCHAR *icon_name ) return path; }
+struct AddShortcutProperties_closure +{ + MSIPACKAGE *package; + IShellLinkW *link; +}; +static UINT ITERATE_AddShortcutProperties(MSIRECORD *row, LPVOID param) +{ + struct AddShortcutProperties_closure *closure = param; + IShellLinkW *link = closure->link; + MSIPACKAGE *package = closure->package; + IPropertyStore *store = NULL; + IPropertyDescription *desc = NULL; + WCHAR *name = NULL, *value = NULL; + PROPVARIANT v; + PROPERTYKEY key; + HRESULT hr; + + PropVariantInit(&v); + + hr = IShellLinkW_QueryInterface(link, &IID_IPropertyStore, (void**)&store); + if (FAILED(hr)) + { + ERR("QueryInterface(IID_IPropertyStore): %x\n", hr); + return ERROR_SUCCESS; + } + + deformat_string(package, MSI_RecordGetString(row, 3), &name); + deformat_string(package, MSI_RecordGetString(row, 4), &value); + + hr = PSGetPropertyDescriptionByName(name, &IID_IPropertyDescription, (void**)&desc); + if (FAILED(hr)) + { + WARN("Unknown property %s, ignoring\n", debugstr_w(name)); + goto out; + } + + hr = InitPropVariantFromString(value, &v); + if (FAILED(hr)) + { + ERR("InitPropVariantFromString hr=%x\n", hr); + goto out; + } + + hr = IPropertyDescription_GetPropertyKey(desc, &key); + if (FAILED(hr)) + { + ERR("IPropertyDescription::GetPropertyKey hr=%x\n", hr); + goto out; + } + + hr = IPropertyDescription_CoerceToCanonicalValue(desc, &v); + if (FAILED(hr)) + { + ERR("IPropertyDescription::CoerceToCanonicalValue hr=%x\n", hr); + goto out; + } + + hr = IPropertyStore_SetValue(store, &key, &v); + if (FAILED(hr)) + { + ERR("IPropertyStore::SetValue hr=%x\n", hr); + } + +out: + PropVariantClear(&v); + if (store) IPropertyStore_Release(store); + if (desc) IPropertyDescription_Release(desc); + + msi_free(name); + msi_free(value); + + return ERROR_SUCCESS; /* fake it if necessary */ +} + +static UINT AddShortcutProperties(MSIPACKAGE *package, const WCHAR *shortcut, IShellLinkW *link) +{ + static const WCHAR queryTemplate[] = { + 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ', + '`','M','s','i','S','h','o','r','t','c','u','t','P','r','o','p','e','r','t','y','`',' ', + 'W','H','E','R','E',' ','`','S','h','o','r','t','c','u','t','_','`', + ' ','=',' ',''','%','s',''', 0}; + WCHAR *query; + MSIQUERY *view; + HRESULT res; + UINT rc; + struct AddShortcutProperties_closure closure = { + package, link + }; + + query = msi_alloc(sizeof(queryTemplate) + lstrlenW(shortcut)*sizeof(WCHAR)); + if (!query) + { + ERR("No memory while querying shortcut properties\n"); + return ERROR_SUCCESS; /* fake it */ + } + + sprintfW(query, queryTemplate, shortcut); + + rc = MSI_DatabaseOpenViewW(package->db, query, &view); + if (rc != ERROR_SUCCESS) + { + msi_free(query); + return ERROR_SUCCESS; + } + + res = CoInitialize( NULL ); + + rc = MSI_IterateRecords(view, NULL, ITERATE_AddShortcutProperties, &closure); + msiobj_release(&view->hdr); + + if (SUCCEEDED(res)) CoUninitialize(); + + msi_free(query); + return rc; +} + static UINT ITERATE_CreateShortcuts(MSIRECORD *row, LPVOID param) { MSIPACKAGE *package = param; @@ -3985,6 +4102,9 @@ static UINT ITERATE_CreateShortcuts(MSIRECORD *row, LPVOID param) full_path = msi_get_target_folder( package, wkdir ); if (full_path) IShellLinkW_SetWorkingDirectory( sl, full_path ); } + + AddShortcutProperties(package, MSI_RecordGetString(row, 1), sl); + link_file = get_link_file(package, row);
TRACE("Writing shortcut to %s\n", debugstr_w(link_file)); diff --git a/dlls/msi/tests/Makefile.in b/dlls/msi/tests/Makefile.in index 66f8abb..6ea5f9a 100644 --- a/dlls/msi/tests/Makefile.in +++ b/dlls/msi/tests/Makefile.in @@ -1,5 +1,5 @@ TESTDLL = msi.dll -IMPORTS = cabinet msi shell32 ole32 oleaut32 user32 advapi32 version +IMPORTS = cabinet msi shell32 ole32 oleaut32 user32 advapi32 version uuid
C_SRCS = \ action.c \ diff --git a/dlls/msi/tests/action.c b/dlls/msi/tests/action.c index 60562f1..f1cafbd 100644 --- a/dlls/msi/tests/action.c +++ b/dlls/msi/tests/action.c @@ -23,6 +23,8 @@ #include <stdio.h> #include <stdlib.h>
+#define COBJMACROS + #include <windows.h> #include <msiquery.h> #include <msidefs.h> @@ -30,9 +32,14 @@ #include <fci.h> #include <srrestoreptapi.h> #include <wtypes.h> +#include <shobjidl.h> #include <shellapi.h> +#include <propsys.h> #include <winsvc.h>
+#include <initguid.h> +#include <propkey.h> + #include "wine/test.h"
static UINT (WINAPI *pMsiQueryComponentStateA) @@ -879,6 +886,12 @@ static const char crs_shortcut_dat[] = "Shortcut\tShortcut\n" "shortcut\tMSITESTDIR\tshortcut\tshortcut\t[MSITESTDIR]target.txt\t\t\t\t\t\t\t\n";
+static const char crs_shortcut_prop_dat[] = + "MsiShortcutProperty\tShortcut_\tPropertyKey\tPropVariantValue\n" + "s255\ts72\ts255\ts255\n" + "MsiShortcutProperty\tMsiShortcutProperty\n" + "shortcut_prop\tshortcut\tSystem.AppUserModel.ID\tWine.Test.Blub\n"; + static const char crs_install_exec_seq_dat[] = "Action\tCondition\tSequence\n" "s72\tS255\tI2\n" @@ -1882,6 +1895,7 @@ static const msi_table crs_tables[] = ADD_TABLE(crs_feature_comp), ADD_TABLE(crs_file), ADD_TABLE(crs_shortcut), + ADD_TABLE(crs_shortcut_prop), ADD_TABLE(crs_install_exec_seq), ADD_TABLE(media), ADD_TABLE(property) @@ -2671,13 +2685,18 @@ static BOOL file_exists(LPCSTR file) return GetFileAttributesA(file) != INVALID_FILE_ATTRIBUTES; }
+static void pf_fullname(LPCSTR file, CHAR buffer[]) +{ + lstrcpyA(buffer, PROG_FILES_DIR); + lstrcatA(buffer, "\"); + lstrcatA(buffer, file); +} + static BOOL pf_exists(LPCSTR file) { CHAR path[MAX_PATH];
- lstrcpyA(path, PROG_FILES_DIR); - lstrcatA(path, "\"); - lstrcatA(path, file); + pf_fullname(file, path);
return file_exists(path); } @@ -5825,6 +5844,56 @@ static void test_create_remove_shortcut(void) ok(pf_exists("msitest\target.txt"), "file not created\n"); ok(pf_exists("msitest\shortcut.lnk"), "file not created\n");
+ /* analyze whether the shortcut properties have been written properly */ + { + CHAR lnkpath[MAX_PATH]; + WCHAR lnkpathW[MAX_PATH]; + IShellLinkW *link = NULL; + IPersistFile *file = NULL; + IPropertyStore *store = NULL; + HRESULT hr; + + CoInitialize(NULL); + + pf_fullname("msitest\shortcut.lnk", lnkpath); + MultiByteToWideChar(CP_ACP, 0, lnkpath, -1, lnkpathW, MAX_PATH); + + hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + &IID_IShellLinkW, (void**)&link); + ok(hr == S_OK, "CoCreateInstance() failed, hr=%x\n", hr); + + hr = IShellLinkW_QueryInterface(link, &IID_IPersistFile, (void**)&file); + ok(hr == S_OK, "QueryInterface, hr=%x\n", hr); + + hr = IPersistFile_Load(file, lnkpathW, STGM_READ); + ok(hr == S_OK, "IPersistFile::Load hr=%x\n", hr); + + hr = IShellLinkW_QueryInterface(link, &IID_IPropertyStore, (void**)&store); + if (SUCCEEDED(hr)) + { + PROPVARIANT v; + WCHAR expected[] = {'W','i','n','e','.','T','e','s','t','.','B','l','u','b',0}; + + hr = IPropertyStore_GetValue(store, &PKEY_AppUserModel_ID, &v); + ok(hr == S_OK, "IPropertyStore::GetValue hr=%x\n", hr); + + ok(v.vt == VT_LPWSTR, "Unexpected variant type %d\n", v.vt); + ok(!lstrcmpW(expected, U(v).pwszVal), "Unexpected value %s\n", wine_dbgstr_w(U(v).pwszVal)); + + PropVariantClear(&v); + IPropertyStore_Release(store); + } + else + { + win_skip("IPropertyStore not implemented for shell links\n"); + } + + IPersistFile_Release(file); + IShellLinkW_Release(link); + + CoUninitialize(); + } + r = MsiInstallProductA(msifile, "REMOVE=ALL"); ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
diff --git a/dlls/msi/tests/automation.c b/dlls/msi/tests/automation.c index 016b1a2..84a3ae4 100644 --- a/dlls/msi/tests/automation.c +++ b/dlls/msi/tests/automation.c @@ -23,7 +23,6 @@
#include <stdio.h>
-#include <initguid.h> #include <windows.h> #include <msiquery.h> #include <msidefs.h>
--- programs/winemenubuilder/winemenubuilder.c | 123 ++++++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 10 deletions(-)
diff --git a/programs/winemenubuilder/winemenubuilder.c b/programs/winemenubuilder/winemenubuilder.c index 76d8f3a..2e258a9 100644 --- a/programs/winemenubuilder/winemenubuilder.c +++ b/programs/winemenubuilder/winemenubuilder.c @@ -90,6 +90,8 @@ #include <shlwapi.h> #include <initguid.h> #include <wincodec.h> +#include <propsys.h> +#include <propvarutil.h>
#include "wine/unicode.h" #include "wine/debug.h" @@ -97,6 +99,9 @@ #include "wine/list.h" #include "wine/rbtree.h"
+#include <initguid.h> +#include <propkey.h> + WINE_DEFAULT_DEBUG_CHANNEL(menubuilder);
#define in_desktop_dir(csidl) ((csidl)==CSIDL_DESKTOPDIRECTORY || \ @@ -1474,7 +1479,7 @@ static DWORD register_menus_entry(const char *unix_file, const char *windows_fil
static BOOL write_desktop_entry(const char *unix_link, const char *location, const char *linkname, const char *path, const char *args, const char *descr, - const char *workdir, const char *icon) + const char *workdir, const char *icon, const char *appusermodelid) { FILE *file;
@@ -1498,6 +1503,8 @@ static BOOL write_desktop_entry(const char *unix_link, const char *location, con fprintf(file, "Path=%s\n", workdir); if (icon && lstrlenA(icon)) fprintf(file, "Icon=%s\n", icon); + if (appusermodelid && lstrlenA(appusermodelid)) + fprintf(file, "StartupWMClass=%s\n", appusermodelid);
fclose(file);
@@ -1643,7 +1650,7 @@ end: }
static BOOL write_menu_entry(const char *unix_link, const char *link, const char *path, const char *args, - const char *descr, const char *workdir, const char *icon) + const char *descr, const char *workdir, const char *icon, const char *appusermodelid) { const char *linkname; char *desktopPath = NULL; @@ -1651,9 +1658,9 @@ static BOOL write_menu_entry(const char *unix_link, const char *link, const char char *filename = NULL; BOOL ret = TRUE;
- WINE_TRACE("(%s, %s, %s, %s, %s, %s, %s)\n", wine_dbgstr_a(unix_link), wine_dbgstr_a(link), + WINE_TRACE("(%s, %s, %s, %s, %s, %s, %s, %s)\n", wine_dbgstr_a(unix_link), wine_dbgstr_a(link), wine_dbgstr_a(path), wine_dbgstr_a(args), wine_dbgstr_a(descr), - wine_dbgstr_a(workdir), wine_dbgstr_a(icon)); + wine_dbgstr_a(workdir), wine_dbgstr_a(icon), wine_dbgstr_a(appusermodelid));
linkname = strrchr(link, '/'); if (linkname == NULL) @@ -1677,7 +1684,7 @@ static BOOL write_menu_entry(const char *unix_link, const char *link, const char goto end; } *desktopDir = '/'; - if (!write_desktop_entry(unix_link, desktopPath, linkname, path, args, descr, workdir, icon)) + if (!write_desktop_entry(unix_link, desktopPath, linkname, path, args, descr, workdir, icon, appusermodelid)) { WINE_WARN("couldn't make desktop entry %s\n", wine_dbgstr_a(desktopPath)); ret = FALSE; @@ -1756,6 +1763,45 @@ end: return utf8_string; }
+/* escapes strings for normal keys (i.e. everything but Exec */ +static char *escape_string(const char *source) +{ + char *dst = HeapAlloc(GetProcessHeap(), 0, lstrlenA(source)*2+1); + char *d = dst; + const char *s = source; + + if (!dst) + return NULL; + + for (; *s; ++s) + { + switch (*s) + { + case '\r': + *d++ = '\'; + *d++ = 'r'; + break; + case '\n': + *d++ = '\'; + *d++ = 'n'; + break; + case '\t': + *d++ = '\'; + *d++ = 't'; + break; + case '\': + *d++ = '\'; + *d++ = '\'; + break; + default: + *d++ = *s; + } + } + *d = 0; + + return dst; +} + /* Return a heap-allocated copy of the unix format difference between the two * Windows-format paths. * locn is the owning location @@ -2811,6 +2857,58 @@ static char* escape_unix_link_arg(LPCSTR unix_link) return ret; }
+static char *get_appusermodel_id( IShellLinkW *link ) +{ + char *id = NULL; + IPropertyStore *store = NULL; + WCHAR buffer[MAX_PATH]; + HRESULT hr; + + /* search an explicit id */ + hr = IShellLinkW_QueryInterface(link, &IID_IPropertyStore, (void**)&store); + if (SUCCEEDED(hr)) + { + PROPVARIANT v; + PropVariantInit(&v); + + hr = IPropertyStore_GetValue(store, &PKEY_AppUserModel_ID, &v); + if (SUCCEEDED(hr) && v.vt == VT_LPWSTR) + { + DWORD u8len = WideCharToMultiByte(CP_UTF8, 0, v.u.pwszVal, -1, NULL, 0, NULL, NULL); + if (u8len && (id = HeapAlloc(GetProcessHeap(), 0, u8len))) + { + WideCharToMultiByte(CP_UTF8, 0, v.u.pwszVal, -1, id, u8len, NULL, NULL); + } + } + + PropVariantClear(&v); + IPropertyStore_Release(store); + } + + /* use the link target */ + if (!id) + { + hr = IShellLinkW_GetPath(link, buffer, MAX_PATH, NULL, 0); + if (SUCCEEDED(hr)) + { + DWORD u8len = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, NULL, 0, NULL, NULL); + if (u8len && (id = HeapAlloc(GetProcessHeap(), 0, u8len))) + { + WideCharToMultiByte(CP_UTF8, 0, buffer, -1, id, u8len, NULL, NULL); + } + } + } + + if (id) + { + char *tmp = escape_string(id); + HeapFree(GetProcessHeap(), 0, id); + return tmp; + } + + return NULL; +} + static BOOL InvokeShellLinker( IShellLinkW *sl, LPCWSTR link, BOOL bWait ) { static const WCHAR startW[] = {'\','c','o','m','m','a','n','d', @@ -2825,6 +2923,7 @@ static BOOL InvokeShellLinker( IShellLinkW *sl, LPCWSTR link, BOOL bWait ) HANDLE hsem = NULL; char *unix_link = NULL; char *start_path = NULL; + char *appusermodelid = NULL;
if ( !link ) { @@ -2864,6 +2963,9 @@ static BOOL InvokeShellLinker( IShellLinkW *sl, LPCWSTR link, BOOL bWait ) ExpandEnvironmentStringsW(szTmp, szIconPath, MAX_PATH); WINE_TRACE("icon file : %s\n", wine_dbgstr_w(szIconPath) );
+ appusermodelid = get_appusermodel_id( sl ); + WINE_TRACE("AppUserModelID : %s\n", wine_dbgstr_a(appusermodelid)); + if( !szPath[0] ) { LPITEMIDLIST pidl = NULL; @@ -2982,12 +3084,12 @@ static BOOL InvokeShellLinker( IShellLinkW *sl, LPCWSTR link, BOOL bWait ) if (link_arg) { r = !write_desktop_entry(unix_link, location, lastEntry, - start_path, link_arg, description, work_dir, icon_name); + start_path, link_arg, description, work_dir, icon_name, appusermodelid); HeapFree(GetProcessHeap(), 0, link_arg); } } else - r = !write_desktop_entry(NULL, location, lastEntry, escaped_path, escaped_args, description, work_dir, icon_name); + r = !write_desktop_entry(NULL, location, lastEntry, escaped_path, escaped_args, description, work_dir, icon_name, appusermodelid); if (r == 0) chmod(location, 0755); HeapFree(GetProcessHeap(), 0, location); @@ -2998,7 +3100,7 @@ static BOOL InvokeShellLinker( IShellLinkW *sl, LPCWSTR link, BOOL bWait ) char *link_arg = escape_unix_link_arg(unix_link); if (link_arg) { - r = !write_menu_entry(unix_link, link_name, start_path, link_arg, description, work_dir, icon_name); + r = !write_menu_entry(unix_link, link_name, start_path, link_arg, description, work_dir, icon_name, appusermodelid); HeapFree(GetProcessHeap(), 0, link_arg); } } @@ -3015,6 +3117,7 @@ cleanup: HeapFree( GetProcessHeap(), 0, description ); HeapFree( GetProcessHeap(), 0, unix_link ); HeapFree( GetProcessHeap(), 0, start_path ); + HeapFree( GetProcessHeap(), 0, appusermodelid );
if (r && !bWait) WINE_ERR("failed to build the menu\n" ); @@ -3148,14 +3251,14 @@ static BOOL InvokeShellLinkerForURL( IUniformResourceLocatorW *url, LPCWSTR link location = heap_printf("%s/%s.desktop", xdg_desktop_dir, lastEntry); if (location) { - r = !write_desktop_entry(NULL, location, lastEntry, start_path, escaped_urlPath, NULL, NULL, icon_name); + r = !write_desktop_entry(NULL, location, lastEntry, start_path, escaped_urlPath, NULL, NULL, icon_name, NULL); if (r == 0) chmod(location, 0755); HeapFree(GetProcessHeap(), 0, location); } } else - r = !write_menu_entry(unix_link, link_name, start_path, escaped_urlPath, NULL, NULL, icon_name); + r = !write_menu_entry(unix_link, link_name, start_path, escaped_urlPath, NULL, NULL, icon_name, NULL); ret = (r == 0); ReleaseSemaphore(hSem, 1, NULL);