diff --git a/dlls/ole32/dictionary.c b/dlls/ole32/dictionary.c index 34890df5d78..b64f24f53ea 100644 --- a/dlls/ole32/dictionary.c +++ b/dlls/ole32/dictionary.c @@ -22,6 +22,9 @@ #include "windef.h" #include "winbase.h" #include "dictionary.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(storage); struct dictionary_entry { @@ -36,12 +39,14 @@ struct dictionary destroyfunc destroy; void *extra; struct dictionary_entry *head; + UINT num_entries; }; struct dictionary *dictionary_create(comparefunc c, destroyfunc d, void *extra) { struct dictionary *ret; + TRACE("(%p, %p, %p)\n", c, d, extra); if (!c) return NULL; ret = HeapAlloc(GetProcessHeap(), 0, sizeof(struct dictionary)); @@ -51,12 +56,15 @@ struct dictionary *dictionary_create(comparefunc c, destroyfunc d, void *extra) ret->destroy = d; ret->extra = extra; ret->head = NULL; + ret->num_entries = 0; } + TRACE("returning %p\n", ret); return ret; } void dictionary_destroy(struct dictionary *d) { + TRACE("(%p)\n", d); if (d) { struct dictionary_entry *p; @@ -74,6 +82,11 @@ void dictionary_destroy(struct dictionary *d) } } +UINT dictionary_num_entries(struct dictionary *d) +{ + return d ? d->num_entries : 0; +} + /* Returns the address of the pointer to the node containing k. (It returns * the address of either h->head or the address of the next member of the * prior node. It's useful when you want to delete.) @@ -101,6 +114,7 @@ void dictionary_insert(struct dictionary *d, const void *k, const void *v) { struct dictionary_entry **prior; + TRACE("(%p, %p, %p)\n", d, k, v); if (!d) return; if ((prior = dictionary_find_internal(d, k))) @@ -121,6 +135,7 @@ void dictionary_insert(struct dictionary *d, const void *k, const void *v) elem->value = (void *)v; elem->next = d->head; d->head = elem; + d->num_entries++; } } @@ -129,6 +144,7 @@ BOOL dictionary_find(struct dictionary *d, const void *k, void **value) struct dictionary_entry **prior; BOOL ret = FALSE; + TRACE("(%p, %p, %p)\n", d, k, value); if (!d) return FALSE; if (!value) @@ -138,6 +154,7 @@ BOOL dictionary_find(struct dictionary *d, const void *k, void **value) *value = (*prior)->value; ret = TRUE; } + TRACE("returning %d (%p)\n", ret, *value); return ret; } @@ -145,6 +162,7 @@ void dictionary_remove(struct dictionary *d, const void *k) { struct dictionary_entry **prior, *temp; + TRACE("(%p, %p)\n", d, k); if (!d) return; if ((prior = dictionary_find_internal(d, k))) @@ -154,17 +172,20 @@ void dictionary_remove(struct dictionary *d, const void *k) d->destroy((*prior)->key, (*prior)->value, d->extra); *prior = (*prior)->next; HeapFree(GetProcessHeap(), 0, temp); + d->num_entries--; } } -void dictionary_enumerate(struct dictionary *d, enumeratefunc e) +void dictionary_enumerate(struct dictionary *d, enumeratefunc e, void *closure) { struct dictionary_entry *p; + TRACE("(%p, %p, %p)\n", d, e, closure); if (!d) return; if (!e) return; for (p = d->head; p; p = p->next) - e(p->key, p->value, d->extra); + if (!e(p->key, p->value, d->extra, closure)) + break; } diff --git a/dlls/ole32/dictionary.h b/dlls/ole32/dictionary.h index 2dfc955b220..db648343b92 100644 --- a/dlls/ole32/dictionary.h +++ b/dlls/ole32/dictionary.h @@ -42,7 +42,8 @@ typedef void (*destroyfunc)(void *k, void *v, void *extra); /* Called for each element in the dictionary. Return FALSE if you don't want * to enumerate any more. */ -typedef BOOL (*enumeratefunc)(const void *k, const void *d, void *extra); +typedef BOOL (*enumeratefunc)(const void *k, const void *v, void *extra, + void *closure); /* Constructs a dictionary, using c as a comparison function for keys. * If d is not NULL, it will be called whenever an item is about to be removed @@ -56,6 +57,11 @@ struct dictionary *dictionary_create(comparefunc c, destroyfunc d, void *extra); /* Assumes d is not NULL. */ void dictionary_destroy(struct dictionary *d); +/* Returns how many entries have been stored in the dictionary. If two values + * with the same key are inserted, only one is counted. + */ +UINT dictionary_num_entries(struct dictionary *d); + /* Sets an element with key k and value v to the dictionary. If a value * already exists with key k, its value is replaced, and the destroyfunc (if * set) is called for the previous item. @@ -82,6 +88,6 @@ BOOL dictionary_find(struct dictionary *d, const void *k, void **v); */ void dictionary_remove(struct dictionary *d, const void *k); -void dictionary_enumerate(struct dictionary *d, enumeratefunc e); +void dictionary_enumerate(struct dictionary *d, enumeratefunc e, void *closure); #endif /* ndef __DICTIONARY_H__ */ diff --git a/dlls/ole32/stg_prop.c b/dlls/ole32/stg_prop.c index 37d14204eaf..90fbef53a96 100644 --- a/dlls/ole32/stg_prop.c +++ b/dlls/ole32/stg_prop.c @@ -79,6 +79,8 @@ WINE_DEFAULT_DEBUG_CHANNEL(storage); #define PROPSETHDR_OSVER_KIND_MAC 1 #define PROPSETHDR_OSVER_KIND_WIN32 2 +#define CP_UNICODE 1200 + /* The format version (and what it implies) is described here: * http://msdn.microsoft.com/library/en-us/stg/stg/format_version.asp */ @@ -106,7 +108,7 @@ typedef struct tagPROPERTYSECTIONHEADER typedef struct tagPROPERTYIDOFFSET { DWORD propid; - DWORD dwOffset; + DWORD dwOffset; /* from beginning of section */ } PROPERTYIDOFFSET; /* Initializes the property storage from the stream (and undoes any uncommitted @@ -209,6 +211,7 @@ static PROPVARIANT *PropertyStorage_FindProperty(PropertyStorage_impl *This, if (!This) return NULL; dictionary_find(This->propid_to_prop, (void *)propid, (void **)&ret); + TRACE("returning %p\n", ret); return ret; } @@ -222,6 +225,7 @@ static PROPVARIANT *PropertyStorage_FindPropertyByName( return NULL; if (dictionary_find(This->name_to_propid, name, (void **)&propid)) ret = PropertyStorage_FindProperty(This, (PROPID)propid); + TRACE("returning %p\n", ret); return ret; } @@ -233,6 +237,7 @@ static LPWSTR PropertyStorage_FindPropertyNameById(PropertyStorage_impl *This, if (!This) return NULL; dictionary_find(This->propid_to_name, (void *)propid, (void **)&ret); + TRACE("returning %p\n", ret); return ret; } @@ -246,7 +251,7 @@ static HRESULT WINAPI IPropertyStorage_fnReadMultiple( PROPVARIANT rgpropvar[]) { PropertyStorage_impl *This = (PropertyStorage_impl *)iface; - HRESULT hr = S_OK; + HRESULT hr = S_FALSE; ULONG i; TRACE("(%p, %ld, %p, %p)\n", iface, cpspec, rgpspec, rgpropvar); @@ -298,6 +303,8 @@ static HRESULT PropertyStorage_StorePropWithId(PropertyStorage_impl *This, { PropVariantCopy(prop, propvar); dictionary_insert(This->propid_to_prop, (void *)propid, prop); + if (propid > This->highestProp) + This->highestProp = propid; } else hr = STG_E_INSUFFICIENTMEMORY; @@ -341,6 +348,9 @@ static HRESULT WINAPI IPropertyStorage_fnWriteMultiple( PropVariantCopy(prop, &rgpropvar[i]); else { + /* Note that I don't do the special cases here that I do below, + * because naming the special PIDs isn't supported. + */ if (propidNameFirst < PID_FIRST_USABLE || propidNameFirst >= PID_MIN_READONLY) hr = STG_E_INVALIDPARAMETER; @@ -352,11 +362,12 @@ static HRESULT WINAPI IPropertyStorage_fnWriteMultiple( len * sizeof(WCHAR)); strcpyW(name, rgpspec[i].u.lpwstr); + TRACE("Adding prop name %s, propid %ld\n", debugstr_w(name), + nextId); dictionary_insert(This->name_to_propid, name, (void *)nextId); dictionary_insert(This->propid_to_name, (void *)nextId, name); - This->highestProp = nextId; hr = PropertyStorage_StorePropWithId(This, nextId, &rgpropvar[i]); } @@ -364,12 +375,38 @@ static HRESULT WINAPI IPropertyStorage_fnWriteMultiple( } else { - /* FIXME: certain propid's have special behavior. E.g., you can't - * set propid 0, and setting PID_BEHAVIOR affects the - * case-sensitivity. - */ - hr = PropertyStorage_StorePropWithId(This, rgpspec[i].u.propid, - &rgpropvar[i]); + switch (rgpspec[i].u.propid) + { + case PID_DICTIONARY: + /* Can't set the dictionary */ + hr = STG_E_INVALIDPARAMETER; + break; + case PID_CODEPAGE: + /* Can only set the code page if nothing else has been set */ + if (dictionary_num_entries(This->propid_to_prop) == 0 && + rgpropvar[i].vt == VT_I2) + This->codePage = rgpropvar[i].u.iVal; + else + hr = STG_E_INVALIDPARAMETER; + break; + case PID_LOCALE: + /* Can only set the locale if nothing else has been set */ + if (dictionary_num_entries(This->propid_to_prop) == 0 && + rgpropvar[i].vt == VT_I4) + This->locale = rgpropvar[i].u.lVal; + else + hr = STG_E_INVALIDPARAMETER; + break; + case PID_ILLEGAL: + /* silently ignore like MSDN says */ + break; + default: + if (rgpspec[i].u.propid >= PID_MIN_READONLY) + hr = STG_E_INVALIDPARAMETER; + else + hr = PropertyStorage_StorePropWithId(This, + rgpspec[i].u.propid, &rgpropvar[i]); + } } } LeaveCriticalSection(&This->cs); @@ -391,7 +428,7 @@ static HRESULT WINAPI IPropertyStorage_fnDeleteMultiple( TRACE("(%p, %ld, %p)\n", iface, cpspec, rgpspec); if (!This) return E_INVALIDARG; - if (!rgpspec) + if (cpspec && !rgpspec) return E_INVALIDARG; if (!(This->grfMode & STGM_READWRITE)) return STG_E_ACCESSDENIED; @@ -409,13 +446,12 @@ static HRESULT WINAPI IPropertyStorage_fnDeleteMultiple( } else { - /* FIXME: certain propid's have special meaning. For example, - * removing propid 0 is supposed to remove the dictionary, and - * removing PID_BEHAVIOR should change this to a case-insensitive - * property set. Unknown "read-only" propid's should be ignored. - */ - dictionary_remove(This->propid_to_prop, - (void *)rgpspec[i].u.propid); + if (rgpspec[i].u.propid >= PID_FIRST_USABLE && + rgpspec[i].u.propid < PID_MIN_READONLY) + dictionary_remove(This->propid_to_prop, + (void *)rgpspec[i].u.propid); + else + hr = STG_E_INVALIDPARAMETER; } } LeaveCriticalSection(&This->cs); @@ -433,17 +469,13 @@ static HRESULT WINAPI IPropertyStorage_fnReadPropertyNames( { PropertyStorage_impl *This = (PropertyStorage_impl *)iface; ULONG i; - HRESULT hr; + HRESULT hr = S_FALSE; TRACE("(%p, %ld, %p, %p)\n", iface, cpropid, rgpropid, rglpwstrName); if (!This) return E_INVALIDARG; if (cpropid && (!rgpropid || !rglpwstrName)) return E_INVALIDARG; - /* MSDN says S_FALSE is returned if no strings matching rgpropid are found, - * default to that - */ - hr = S_FALSE; EnterCriticalSection(&This->cs); for (i = 0; i < cpropid && SUCCEEDED(hr); i++) { @@ -664,6 +696,11 @@ static int PropertyStorage_PropNameCompare(const void *a, const void *b, { PropertyStorage_impl *This = (PropertyStorage_impl *)extra; + TRACE("(%s, %s)\n", debugstr_w(a), debugstr_w(b)); + /* FIXME: this assumes property names are always Unicode, but they + * might be ANSI, depending on whether This->grfFlags & PROPSETFLAG_ANSI + * is true. + */ if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) return strcmpW((LPCWSTR)a, (LPCWSTR)b); else @@ -678,6 +715,7 @@ static void PropertyStorage_PropNameDestroy(void *k, void *d, void *extra) static int PropertyStorage_PropCompare(const void *a, const void *b, void *extra) { + TRACE("(%ld, %ld)\n", (PROPID)a, (PROPID)b); return (PROPID)a - (PROPID)b; } @@ -691,6 +729,8 @@ static void PropertyStorage_PropertyDestroy(void *k, void *d, void *extra) * the entries according to the values of This->codePage and This->locale. * FIXME: there isn't any checking whether the read property extends past the * end of the buffer. + * FIXME: this always stores dictionary entries as Unicode, but it should store + * them as ANSI if (This->grfFlags & PROPSETFLAG_ANSI) is true. */ static HRESULT PropertyStorage_ReadDictionary(PropertyStorage_impl *This, BYTE *ptr) @@ -699,10 +739,6 @@ static HRESULT PropertyStorage_ReadDictionary(PropertyStorage_impl *This, HRESULT hr = S_OK; ptr += sizeof(DWORD); - This->name_to_propid = dictionary_create(PropertyStorage_PropNameCompare, - PropertyStorage_PropNameDestroy, This); - This->propid_to_name = dictionary_create(PropertyStorage_PropCompare, NULL, - This); if (This->name_to_propid && This->propid_to_name) { DWORD i; @@ -742,7 +778,7 @@ static HRESULT PropertyStorage_ReadDictionary(PropertyStorage_impl *This, } ptr += sizeof(DWORD) + cbEntry; /* Unicode entries are padded to DWORD boundaries */ - if (This->codePage == 1200 && cbEntry % sizeof(DWORD)) + if (This->codePage == CP_UNICODE && cbEntry % sizeof(DWORD)) ptr += sizeof(DWORD) - (cbEntry % sizeof(DWORD)); } } @@ -863,6 +899,8 @@ static HRESULT PropertyStorage_ReadHeaderFromStream(IStream *stm, ULONG count = 0; HRESULT hr; + assert(stm); + assert(hdr); hr = IStream_Read(stm, buf, sizeof(buf), &count); if (SUCCEEDED(hr)) { @@ -896,6 +934,8 @@ static HRESULT PropertyStorage_ReadFmtIdOffsetFromStream(IStream *stm, ULONG count = 0; HRESULT hr; + assert(stm); + assert(fmt); hr = IStream_Read(stm, buf, sizeof(buf), &count); if (SUCCEEDED(hr)) { @@ -923,6 +963,8 @@ static HRESULT PropertyStorage_ReadSectionHeaderFromStream(IStream *stm, ULONG count = 0; HRESULT hr; + assert(stm); + assert(hdr); hr = IStream_Read(stm, buf, sizeof(buf), &count); if (SUCCEEDED(hr)) { @@ -962,9 +1004,29 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface) This->dirty = FALSE; This->highestProp = 0; dictionary_destroy(This->name_to_propid); - This->name_to_propid = NULL; dictionary_destroy(This->propid_to_name); - This->propid_to_name = NULL; + dictionary_destroy(This->propid_to_prop); + This->name_to_propid = dictionary_create(PropertyStorage_PropNameCompare, + PropertyStorage_PropNameDestroy, This); + if (!This->name_to_propid) + { + hr = STG_E_INSUFFICIENTMEMORY; + goto end; + } + This->propid_to_name = dictionary_create(PropertyStorage_PropCompare, NULL, + This); + if (!This->propid_to_name) + { + hr = STG_E_INSUFFICIENTMEMORY; + goto end; + } + This->propid_to_prop = dictionary_create(PropertyStorage_PropCompare, + PropertyStorage_PropertyDestroy, This); + if (!This->propid_to_prop) + { + hr = STG_E_INSUFFICIENTMEMORY; + goto end; + } hr = IStream_Stat(This->stm, &stat, STATFLAG_NONAME); if (FAILED(hr)) goto end; @@ -1047,9 +1109,6 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface) sizeof(PROPERTYSECTIONHEADER), &count); if (FAILED(hr)) goto end; - dictionary_destroy(This->propid_to_prop); - This->propid_to_prop = dictionary_create(PropertyStorage_PropCompare, - PropertyStorage_PropertyDestroy, This); for (i = 0; SUCCEEDED(hr) && i < sectionHdr.cProperties; i++) { PROPERTYIDOFFSET *idOffset = (PROPERTYIDOFFSET *)(buf + @@ -1064,7 +1123,7 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface) idOffset->propid < PID_MIN_READONLY && idOffset->propid > This->highestProp) This->highestProp = idOffset->propid; - if (idOffset->propid == 0) + if (idOffset->propid == PID_DICTIONARY) { /* Don't read the dictionary yet, its entries depend on the * code page. Just store the offset so we know to read it @@ -1083,10 +1142,6 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface) buf + idOffset->dwOffset - sizeof(PROPERTYSECTIONHEADER) + sizeof(DWORD)))) { - /* FIXME: the PID_CODEPAGE and PID_LOCALE special cases - * aren't really needed, just look them up in - * propid_to_prop when needed - */ switch(idOffset->propid) { case PID_CODEPAGE: @@ -1117,7 +1172,7 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface) if (This->grfFlags & PROPSETFLAG_ANSI) This->codePage = GetACP(); else - This->codePage = 1200; + This->codePage = CP_UNICODE; } if (!This->locale) This->locale = LOCALE_SYSTEM_DEFAULT; @@ -1128,10 +1183,18 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface) end: HeapFree(GetProcessHeap(), 0, buf); + if (FAILED(hr)) + { + dictionary_destroy(This->name_to_propid); + This->name_to_propid = NULL; + dictionary_destroy(This->propid_to_name); + This->propid_to_name = NULL; + dictionary_destroy(This->propid_to_prop); + This->propid_to_prop = NULL; + } return hr; } - static HRESULT PropertyStorage_WriteToStream(IPropertyStorage *iface) { FIXME("\n"); diff --git a/dlls/ole32/tests/.cvsignore b/dlls/ole32/tests/.cvsignore index b1a5fdb668c..3d7f0d76644 100644 --- a/dlls/ole32/tests/.cvsignore +++ b/dlls/ole32/tests/.cvsignore @@ -2,5 +2,6 @@ Makefile marshal.ok moniker.ok propvariant.ok +stg_prop.ok storage32.ok testlist.c diff --git a/dlls/ole32/tests/Makefile.in b/dlls/ole32/tests/Makefile.in index 89bded0d30a..7e426408835 100644 --- a/dlls/ole32/tests/Makefile.in +++ b/dlls/ole32/tests/Makefile.in @@ -10,6 +10,7 @@ CTESTS = \ marshal.c \ moniker.c \ propvariant.c \ + stg_prop.c \ storage32.c @MAKE_TEST_RULES@ diff --git a/dlls/ole32/tests/stg_prop.c b/dlls/ole32/tests/stg_prop.c new file mode 100644 index 00000000000..b7d714f01d3 --- /dev/null +++ b/dlls/ole32/tests/stg_prop.c @@ -0,0 +1,160 @@ +/* IPropertyStorage unit tests + * Copyright 2005 Juan Lang + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#define COBJMACROS +#include "objbase.h" +#include "wine/test.h" + +#ifdef NONAMELESSUNION +# define U(x) (x).u +#else +# define U(x) (x) +#endif + +/* FIXME: this creates an ANSI storage, need try to find conditions under which + * Unicode translation fails + */ +static void testProps(void) +{ + static const WCHAR szDot[] = { '.',0 }; + static const WCHAR szPrefix[] = { 's','t','g',0 }; + static const WCHAR propName[] = { 'p','r','o','p',0 }; + WCHAR filename[MAX_PATH]; + HRESULT hr; + IStorage *storage = NULL; + IPropertySetStorage *propSetStorage = NULL; + IPropertyStorage *propertyStorage = NULL; + PROPSPEC spec; + PROPVARIANT var; + + if(!GetTempFileNameW(szDot, szPrefix, 0, filename)) + return; + + DeleteFileW(filename); + + hr = StgCreateDocfile(filename, + STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE, 0, &storage); + ok(SUCCEEDED(hr), "StgCreateDocfile failed: 0x%08lx\n", hr); + + hr = StgCreatePropSetStg(storage, 0, &propSetStorage); + ok(SUCCEEDED(hr), "StgCreatePropSetStg failed: 0x%08lx\n", hr); + + hr = IPropertySetStorage_Create(propSetStorage, + &FMTID_SummaryInformation, NULL, PROPSETFLAG_ANSI, + STGM_READWRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE, + &propertyStorage); + ok(SUCCEEDED(hr), "QI -> IPropertyStorage failed: 0x%08lx\n", hr); + + hr = IPropertyStorage_WriteMultiple(propertyStorage, 0, NULL, NULL, 0); + ok(SUCCEEDED(hr), "WriteMultiple with 0 args failed: 0x%08lx\n", hr); + hr = IPropertyStorage_WriteMultiple(propertyStorage, 1, NULL, NULL, 0); + ok(hr == E_INVALIDARG, "Expected E_INVALIDARG, got 0x%08lx\n", hr); + + /* test setting one that I can't set */ + spec.ulKind = PRSPEC_PROPID; + U(spec).propid = PID_DICTIONARY; + PropVariantClear(&var); + var.vt = VT_I4; + U(var).lVal = 1; + hr = IPropertyStorage_WriteMultiple(propertyStorage, 1, &spec, &var, 0); + ok(hr == STG_E_INVALIDPARAMETER, + "Expected STG_E_INVALIDPARAMETER, got 0x%08lx\n", hr); + + /* test setting one by name with an invalid propidNameFirst */ + spec.ulKind = PRSPEC_LPWSTR; + U(spec).lpwstr = (LPOLESTR)propName; + hr = IPropertyStorage_WriteMultiple(propertyStorage, 1, &spec, &var, + PID_DICTIONARY); + ok(hr == STG_E_INVALIDPARAMETER, + "Expected STG_E_INVALIDPARAMETER, got 0x%08lx\n", hr); + + /* test setting behavior (case-sensitive) */ + spec.ulKind = PRSPEC_PROPID; + U(spec).propid = PID_BEHAVIOR; + U(var).lVal = 1; + hr = IPropertyStorage_WriteMultiple(propertyStorage, 1, &spec, &var, 0); + ok(hr == STG_E_INVALIDPARAMETER, + "Expected STG_E_INVALIDPARAMETER, got 0x%08lx\n", hr); + + /* set one by value.. */ + spec.ulKind = PRSPEC_PROPID; + U(spec).propid = PID_FIRST_USABLE; + U(var).lVal = 1; + hr = IPropertyStorage_WriteMultiple(propertyStorage, 1, &spec, &var, 0); + ok(SUCCEEDED(hr), "WriteMultiple failed: 0x%08lx\n", hr); + + /* finally, set one by name */ + spec.ulKind = PRSPEC_LPWSTR; + U(spec).lpwstr = (LPOLESTR)propName; + U(var).lVal = 2; + hr = IPropertyStorage_WriteMultiple(propertyStorage, 1, &spec, &var, + PID_FIRST_USABLE); + ok(SUCCEEDED(hr), "WriteMultiple failed: 0x%08lx\n", hr); + + /* check reading */ + hr = IPropertyStorage_ReadMultiple(propertyStorage, 0, NULL, NULL); + ok(SUCCEEDED(hr), "ReadMultiple with 0 args failed: 0x%08lx\n", hr); + hr = IPropertyStorage_ReadMultiple(propertyStorage, 1, NULL, NULL); + ok(hr == E_INVALIDARG, "Expected E_INVALIDARG, got 0x%08lx\n", hr); + /* read by propid */ + spec.ulKind = PRSPEC_PROPID; + U(spec).propid = PID_FIRST_USABLE; + hr = IPropertyStorage_ReadMultiple(propertyStorage, 1, &spec, &var); + ok(SUCCEEDED(hr), "ReadMultiple failed: 0x%08lx\n", hr); + ok(var.vt == VT_I4 && U(var).lVal == 1, + "Didn't get expected type or value for property (got type %d, value %ld)\n", + var.vt, U(var).lVal); + /* read by name */ + spec.ulKind = PRSPEC_LPWSTR; + U(spec).lpwstr = (LPOLESTR)propName; + hr = IPropertyStorage_ReadMultiple(propertyStorage, 1, &spec, &var); + ok(SUCCEEDED(hr), "ReadMultiple failed: 0x%08lx\n", hr); + ok(var.vt == VT_I4 && U(var).lVal == 2, + "Didn't get expected type or value for property (got type %d, value %ld)\n", + var.vt, U(var).lVal); + + /* check deleting */ + hr = IPropertyStorage_DeleteMultiple(propertyStorage, 0, NULL); + ok(SUCCEEDED(hr), "DeleteMultiple with 0 args failed: 0x%08lx\n", hr); + hr = IPropertyStorage_DeleteMultiple(propertyStorage, 1, NULL); + ok(hr == E_INVALIDARG, "Expected E_INVALIDARG, got 0x%08lx\n", hr); + /* contrary to what the docs say, you can't delete the dictionary */ + spec.ulKind = PRSPEC_PROPID; + U(spec).propid = PID_DICTIONARY; + hr = IPropertyStorage_DeleteMultiple(propertyStorage, 1, &spec); + ok(hr == STG_E_INVALIDPARAMETER, + "Expected STG_E_INVALIDPARAMETER, got 0x%08lx\n", hr); + /* now delete the first value.. */ + U(spec).propid = PID_FIRST_USABLE; + hr = IPropertyStorage_DeleteMultiple(propertyStorage, 1, &spec); + ok(SUCCEEDED(hr), "DeleteMultiple failed: 0x%08lx\n", hr); + /* and check that it's no longer readable */ + hr = IPropertyStorage_ReadMultiple(propertyStorage, 1, &spec, &var); + ok(hr == S_FALSE, "Expected S_FALSE, got 0x%08lx\n", hr); + + IPropertyStorage_Release(propertyStorage); + IPropertySetStorage_Release(propSetStorage); + IStorage_Release(storage); + + DeleteFileW(filename); +} + +START_TEST(stg_prop) +{ + testProps(); +}