Add traces, add unit tests for IPropertyStorage, and fix the problems
they caught.
This commit is contained in:
parent
6d83105054
commit
50794ce787
|
@ -22,6 +22,9 @@
|
||||||
#include "windef.h"
|
#include "windef.h"
|
||||||
#include "winbase.h"
|
#include "winbase.h"
|
||||||
#include "dictionary.h"
|
#include "dictionary.h"
|
||||||
|
#include "wine/debug.h"
|
||||||
|
|
||||||
|
WINE_DEFAULT_DEBUG_CHANNEL(storage);
|
||||||
|
|
||||||
struct dictionary_entry
|
struct dictionary_entry
|
||||||
{
|
{
|
||||||
|
@ -36,12 +39,14 @@ struct dictionary
|
||||||
destroyfunc destroy;
|
destroyfunc destroy;
|
||||||
void *extra;
|
void *extra;
|
||||||
struct dictionary_entry *head;
|
struct dictionary_entry *head;
|
||||||
|
UINT num_entries;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct dictionary *dictionary_create(comparefunc c, destroyfunc d, void *extra)
|
struct dictionary *dictionary_create(comparefunc c, destroyfunc d, void *extra)
|
||||||
{
|
{
|
||||||
struct dictionary *ret;
|
struct dictionary *ret;
|
||||||
|
|
||||||
|
TRACE("(%p, %p, %p)\n", c, d, extra);
|
||||||
if (!c)
|
if (!c)
|
||||||
return NULL;
|
return NULL;
|
||||||
ret = HeapAlloc(GetProcessHeap(), 0, sizeof(struct dictionary));
|
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->destroy = d;
|
||||||
ret->extra = extra;
|
ret->extra = extra;
|
||||||
ret->head = NULL;
|
ret->head = NULL;
|
||||||
|
ret->num_entries = 0;
|
||||||
}
|
}
|
||||||
|
TRACE("returning %p\n", ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void dictionary_destroy(struct dictionary *d)
|
void dictionary_destroy(struct dictionary *d)
|
||||||
{
|
{
|
||||||
|
TRACE("(%p)\n", d);
|
||||||
if (d)
|
if (d)
|
||||||
{
|
{
|
||||||
struct dictionary_entry *p;
|
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
|
/* 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
|
* 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.)
|
* 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;
|
struct dictionary_entry **prior;
|
||||||
|
|
||||||
|
TRACE("(%p, %p, %p)\n", d, k, v);
|
||||||
if (!d)
|
if (!d)
|
||||||
return;
|
return;
|
||||||
if ((prior = dictionary_find_internal(d, k)))
|
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->value = (void *)v;
|
||||||
elem->next = d->head;
|
elem->next = d->head;
|
||||||
d->head = elem;
|
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;
|
struct dictionary_entry **prior;
|
||||||
BOOL ret = FALSE;
|
BOOL ret = FALSE;
|
||||||
|
|
||||||
|
TRACE("(%p, %p, %p)\n", d, k, value);
|
||||||
if (!d)
|
if (!d)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
if (!value)
|
if (!value)
|
||||||
|
@ -138,6 +154,7 @@ BOOL dictionary_find(struct dictionary *d, const void *k, void **value)
|
||||||
*value = (*prior)->value;
|
*value = (*prior)->value;
|
||||||
ret = TRUE;
|
ret = TRUE;
|
||||||
}
|
}
|
||||||
|
TRACE("returning %d (%p)\n", ret, *value);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,6 +162,7 @@ void dictionary_remove(struct dictionary *d, const void *k)
|
||||||
{
|
{
|
||||||
struct dictionary_entry **prior, *temp;
|
struct dictionary_entry **prior, *temp;
|
||||||
|
|
||||||
|
TRACE("(%p, %p)\n", d, k);
|
||||||
if (!d)
|
if (!d)
|
||||||
return;
|
return;
|
||||||
if ((prior = dictionary_find_internal(d, k)))
|
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);
|
d->destroy((*prior)->key, (*prior)->value, d->extra);
|
||||||
*prior = (*prior)->next;
|
*prior = (*prior)->next;
|
||||||
HeapFree(GetProcessHeap(), 0, temp);
|
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;
|
struct dictionary_entry *p;
|
||||||
|
|
||||||
|
TRACE("(%p, %p, %p)\n", d, e, closure);
|
||||||
if (!d)
|
if (!d)
|
||||||
return;
|
return;
|
||||||
if (!e)
|
if (!e)
|
||||||
return;
|
return;
|
||||||
for (p = d->head; p; p = p->next)
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
/* Called for each element in the dictionary. Return FALSE if you don't want
|
||||||
* to enumerate any more.
|
* 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.
|
/* 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
|
* 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. */
|
/* Assumes d is not NULL. */
|
||||||
void dictionary_destroy(struct dictionary *d);
|
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
|
/* 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
|
* already exists with key k, its value is replaced, and the destroyfunc (if
|
||||||
* set) is called for the previous item.
|
* 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_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__ */
|
#endif /* ndef __DICTIONARY_H__ */
|
||||||
|
|
|
@ -79,6 +79,8 @@ WINE_DEFAULT_DEBUG_CHANNEL(storage);
|
||||||
#define PROPSETHDR_OSVER_KIND_MAC 1
|
#define PROPSETHDR_OSVER_KIND_MAC 1
|
||||||
#define PROPSETHDR_OSVER_KIND_WIN32 2
|
#define PROPSETHDR_OSVER_KIND_WIN32 2
|
||||||
|
|
||||||
|
#define CP_UNICODE 1200
|
||||||
|
|
||||||
/* The format version (and what it implies) is described here:
|
/* The format version (and what it implies) is described here:
|
||||||
* http://msdn.microsoft.com/library/en-us/stg/stg/format_version.asp
|
* http://msdn.microsoft.com/library/en-us/stg/stg/format_version.asp
|
||||||
*/
|
*/
|
||||||
|
@ -106,7 +108,7 @@ typedef struct tagPROPERTYSECTIONHEADER
|
||||||
typedef struct tagPROPERTYIDOFFSET
|
typedef struct tagPROPERTYIDOFFSET
|
||||||
{
|
{
|
||||||
DWORD propid;
|
DWORD propid;
|
||||||
DWORD dwOffset;
|
DWORD dwOffset; /* from beginning of section */
|
||||||
} PROPERTYIDOFFSET;
|
} PROPERTYIDOFFSET;
|
||||||
|
|
||||||
/* Initializes the property storage from the stream (and undoes any uncommitted
|
/* Initializes the property storage from the stream (and undoes any uncommitted
|
||||||
|
@ -209,6 +211,7 @@ static PROPVARIANT *PropertyStorage_FindProperty(PropertyStorage_impl *This,
|
||||||
if (!This)
|
if (!This)
|
||||||
return NULL;
|
return NULL;
|
||||||
dictionary_find(This->propid_to_prop, (void *)propid, (void **)&ret);
|
dictionary_find(This->propid_to_prop, (void *)propid, (void **)&ret);
|
||||||
|
TRACE("returning %p\n", ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,6 +225,7 @@ static PROPVARIANT *PropertyStorage_FindPropertyByName(
|
||||||
return NULL;
|
return NULL;
|
||||||
if (dictionary_find(This->name_to_propid, name, (void **)&propid))
|
if (dictionary_find(This->name_to_propid, name, (void **)&propid))
|
||||||
ret = PropertyStorage_FindProperty(This, (PROPID)propid);
|
ret = PropertyStorage_FindProperty(This, (PROPID)propid);
|
||||||
|
TRACE("returning %p\n", ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,6 +237,7 @@ static LPWSTR PropertyStorage_FindPropertyNameById(PropertyStorage_impl *This,
|
||||||
if (!This)
|
if (!This)
|
||||||
return NULL;
|
return NULL;
|
||||||
dictionary_find(This->propid_to_name, (void *)propid, (void **)&ret);
|
dictionary_find(This->propid_to_name, (void *)propid, (void **)&ret);
|
||||||
|
TRACE("returning %p\n", ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +251,7 @@ static HRESULT WINAPI IPropertyStorage_fnReadMultiple(
|
||||||
PROPVARIANT rgpropvar[])
|
PROPVARIANT rgpropvar[])
|
||||||
{
|
{
|
||||||
PropertyStorage_impl *This = (PropertyStorage_impl *)iface;
|
PropertyStorage_impl *This = (PropertyStorage_impl *)iface;
|
||||||
HRESULT hr = S_OK;
|
HRESULT hr = S_FALSE;
|
||||||
ULONG i;
|
ULONG i;
|
||||||
|
|
||||||
TRACE("(%p, %ld, %p, %p)\n", iface, cpspec, rgpspec, rgpropvar);
|
TRACE("(%p, %ld, %p, %p)\n", iface, cpspec, rgpspec, rgpropvar);
|
||||||
|
@ -298,6 +303,8 @@ static HRESULT PropertyStorage_StorePropWithId(PropertyStorage_impl *This,
|
||||||
{
|
{
|
||||||
PropVariantCopy(prop, propvar);
|
PropVariantCopy(prop, propvar);
|
||||||
dictionary_insert(This->propid_to_prop, (void *)propid, prop);
|
dictionary_insert(This->propid_to_prop, (void *)propid, prop);
|
||||||
|
if (propid > This->highestProp)
|
||||||
|
This->highestProp = propid;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
hr = STG_E_INSUFFICIENTMEMORY;
|
hr = STG_E_INSUFFICIENTMEMORY;
|
||||||
|
@ -341,6 +348,9 @@ static HRESULT WINAPI IPropertyStorage_fnWriteMultiple(
|
||||||
PropVariantCopy(prop, &rgpropvar[i]);
|
PropVariantCopy(prop, &rgpropvar[i]);
|
||||||
else
|
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 ||
|
if (propidNameFirst < PID_FIRST_USABLE ||
|
||||||
propidNameFirst >= PID_MIN_READONLY)
|
propidNameFirst >= PID_MIN_READONLY)
|
||||||
hr = STG_E_INVALIDPARAMETER;
|
hr = STG_E_INVALIDPARAMETER;
|
||||||
|
@ -352,11 +362,12 @@ static HRESULT WINAPI IPropertyStorage_fnWriteMultiple(
|
||||||
len * sizeof(WCHAR));
|
len * sizeof(WCHAR));
|
||||||
|
|
||||||
strcpyW(name, rgpspec[i].u.lpwstr);
|
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,
|
dictionary_insert(This->name_to_propid, name,
|
||||||
(void *)nextId);
|
(void *)nextId);
|
||||||
dictionary_insert(This->propid_to_name, (void *)nextId,
|
dictionary_insert(This->propid_to_name, (void *)nextId,
|
||||||
name);
|
name);
|
||||||
This->highestProp = nextId;
|
|
||||||
hr = PropertyStorage_StorePropWithId(This, nextId,
|
hr = PropertyStorage_StorePropWithId(This, nextId,
|
||||||
&rgpropvar[i]);
|
&rgpropvar[i]);
|
||||||
}
|
}
|
||||||
|
@ -364,12 +375,38 @@ static HRESULT WINAPI IPropertyStorage_fnWriteMultiple(
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* FIXME: certain propid's have special behavior. E.g., you can't
|
switch (rgpspec[i].u.propid)
|
||||||
* set propid 0, and setting PID_BEHAVIOR affects the
|
{
|
||||||
* case-sensitivity.
|
case PID_DICTIONARY:
|
||||||
*/
|
/* Can't set the dictionary */
|
||||||
hr = PropertyStorage_StorePropWithId(This, rgpspec[i].u.propid,
|
hr = STG_E_INVALIDPARAMETER;
|
||||||
&rgpropvar[i]);
|
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);
|
LeaveCriticalSection(&This->cs);
|
||||||
|
@ -391,7 +428,7 @@ static HRESULT WINAPI IPropertyStorage_fnDeleteMultiple(
|
||||||
TRACE("(%p, %ld, %p)\n", iface, cpspec, rgpspec);
|
TRACE("(%p, %ld, %p)\n", iface, cpspec, rgpspec);
|
||||||
if (!This)
|
if (!This)
|
||||||
return E_INVALIDARG;
|
return E_INVALIDARG;
|
||||||
if (!rgpspec)
|
if (cpspec && !rgpspec)
|
||||||
return E_INVALIDARG;
|
return E_INVALIDARG;
|
||||||
if (!(This->grfMode & STGM_READWRITE))
|
if (!(This->grfMode & STGM_READWRITE))
|
||||||
return STG_E_ACCESSDENIED;
|
return STG_E_ACCESSDENIED;
|
||||||
|
@ -409,13 +446,12 @@ static HRESULT WINAPI IPropertyStorage_fnDeleteMultiple(
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* FIXME: certain propid's have special meaning. For example,
|
if (rgpspec[i].u.propid >= PID_FIRST_USABLE &&
|
||||||
* removing propid 0 is supposed to remove the dictionary, and
|
rgpspec[i].u.propid < PID_MIN_READONLY)
|
||||||
* 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,
|
dictionary_remove(This->propid_to_prop,
|
||||||
(void *)rgpspec[i].u.propid);
|
(void *)rgpspec[i].u.propid);
|
||||||
|
else
|
||||||
|
hr = STG_E_INVALIDPARAMETER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LeaveCriticalSection(&This->cs);
|
LeaveCriticalSection(&This->cs);
|
||||||
|
@ -433,17 +469,13 @@ static HRESULT WINAPI IPropertyStorage_fnReadPropertyNames(
|
||||||
{
|
{
|
||||||
PropertyStorage_impl *This = (PropertyStorage_impl *)iface;
|
PropertyStorage_impl *This = (PropertyStorage_impl *)iface;
|
||||||
ULONG i;
|
ULONG i;
|
||||||
HRESULT hr;
|
HRESULT hr = S_FALSE;
|
||||||
|
|
||||||
TRACE("(%p, %ld, %p, %p)\n", iface, cpropid, rgpropid, rglpwstrName);
|
TRACE("(%p, %ld, %p, %p)\n", iface, cpropid, rgpropid, rglpwstrName);
|
||||||
if (!This)
|
if (!This)
|
||||||
return E_INVALIDARG;
|
return E_INVALIDARG;
|
||||||
if (cpropid && (!rgpropid || !rglpwstrName))
|
if (cpropid && (!rgpropid || !rglpwstrName))
|
||||||
return E_INVALIDARG;
|
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);
|
EnterCriticalSection(&This->cs);
|
||||||
for (i = 0; i < cpropid && SUCCEEDED(hr); i++)
|
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;
|
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)
|
if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE)
|
||||||
return strcmpW((LPCWSTR)a, (LPCWSTR)b);
|
return strcmpW((LPCWSTR)a, (LPCWSTR)b);
|
||||||
else
|
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,
|
static int PropertyStorage_PropCompare(const void *a, const void *b,
|
||||||
void *extra)
|
void *extra)
|
||||||
{
|
{
|
||||||
|
TRACE("(%ld, %ld)\n", (PROPID)a, (PROPID)b);
|
||||||
return (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.
|
* 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
|
* FIXME: there isn't any checking whether the read property extends past the
|
||||||
* end of the buffer.
|
* 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,
|
static HRESULT PropertyStorage_ReadDictionary(PropertyStorage_impl *This,
|
||||||
BYTE *ptr)
|
BYTE *ptr)
|
||||||
|
@ -699,10 +739,6 @@ static HRESULT PropertyStorage_ReadDictionary(PropertyStorage_impl *This,
|
||||||
HRESULT hr = S_OK;
|
HRESULT hr = S_OK;
|
||||||
|
|
||||||
ptr += sizeof(DWORD);
|
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)
|
if (This->name_to_propid && This->propid_to_name)
|
||||||
{
|
{
|
||||||
DWORD i;
|
DWORD i;
|
||||||
|
@ -742,7 +778,7 @@ static HRESULT PropertyStorage_ReadDictionary(PropertyStorage_impl *This,
|
||||||
}
|
}
|
||||||
ptr += sizeof(DWORD) + cbEntry;
|
ptr += sizeof(DWORD) + cbEntry;
|
||||||
/* Unicode entries are padded to DWORD boundaries */
|
/* 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));
|
ptr += sizeof(DWORD) - (cbEntry % sizeof(DWORD));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -863,6 +899,8 @@ static HRESULT PropertyStorage_ReadHeaderFromStream(IStream *stm,
|
||||||
ULONG count = 0;
|
ULONG count = 0;
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
|
assert(stm);
|
||||||
|
assert(hdr);
|
||||||
hr = IStream_Read(stm, buf, sizeof(buf), &count);
|
hr = IStream_Read(stm, buf, sizeof(buf), &count);
|
||||||
if (SUCCEEDED(hr))
|
if (SUCCEEDED(hr))
|
||||||
{
|
{
|
||||||
|
@ -896,6 +934,8 @@ static HRESULT PropertyStorage_ReadFmtIdOffsetFromStream(IStream *stm,
|
||||||
ULONG count = 0;
|
ULONG count = 0;
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
|
assert(stm);
|
||||||
|
assert(fmt);
|
||||||
hr = IStream_Read(stm, buf, sizeof(buf), &count);
|
hr = IStream_Read(stm, buf, sizeof(buf), &count);
|
||||||
if (SUCCEEDED(hr))
|
if (SUCCEEDED(hr))
|
||||||
{
|
{
|
||||||
|
@ -923,6 +963,8 @@ static HRESULT PropertyStorage_ReadSectionHeaderFromStream(IStream *stm,
|
||||||
ULONG count = 0;
|
ULONG count = 0;
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
|
assert(stm);
|
||||||
|
assert(hdr);
|
||||||
hr = IStream_Read(stm, buf, sizeof(buf), &count);
|
hr = IStream_Read(stm, buf, sizeof(buf), &count);
|
||||||
if (SUCCEEDED(hr))
|
if (SUCCEEDED(hr))
|
||||||
{
|
{
|
||||||
|
@ -962,9 +1004,29 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface)
|
||||||
This->dirty = FALSE;
|
This->dirty = FALSE;
|
||||||
This->highestProp = 0;
|
This->highestProp = 0;
|
||||||
dictionary_destroy(This->name_to_propid);
|
dictionary_destroy(This->name_to_propid);
|
||||||
This->name_to_propid = NULL;
|
|
||||||
dictionary_destroy(This->propid_to_name);
|
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);
|
hr = IStream_Stat(This->stm, &stat, STATFLAG_NONAME);
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -1047,9 +1109,6 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface)
|
||||||
sizeof(PROPERTYSECTIONHEADER), &count);
|
sizeof(PROPERTYSECTIONHEADER), &count);
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
goto end;
|
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++)
|
for (i = 0; SUCCEEDED(hr) && i < sectionHdr.cProperties; i++)
|
||||||
{
|
{
|
||||||
PROPERTYIDOFFSET *idOffset = (PROPERTYIDOFFSET *)(buf +
|
PROPERTYIDOFFSET *idOffset = (PROPERTYIDOFFSET *)(buf +
|
||||||
|
@ -1064,7 +1123,7 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface)
|
||||||
idOffset->propid < PID_MIN_READONLY && idOffset->propid >
|
idOffset->propid < PID_MIN_READONLY && idOffset->propid >
|
||||||
This->highestProp)
|
This->highestProp)
|
||||||
This->highestProp = idOffset->propid;
|
This->highestProp = idOffset->propid;
|
||||||
if (idOffset->propid == 0)
|
if (idOffset->propid == PID_DICTIONARY)
|
||||||
{
|
{
|
||||||
/* Don't read the dictionary yet, its entries depend on the
|
/* Don't read the dictionary yet, its entries depend on the
|
||||||
* code page. Just store the offset so we know to read it
|
* 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) +
|
buf + idOffset->dwOffset - sizeof(PROPERTYSECTIONHEADER) +
|
||||||
sizeof(DWORD))))
|
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)
|
switch(idOffset->propid)
|
||||||
{
|
{
|
||||||
case PID_CODEPAGE:
|
case PID_CODEPAGE:
|
||||||
|
@ -1117,7 +1172,7 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface)
|
||||||
if (This->grfFlags & PROPSETFLAG_ANSI)
|
if (This->grfFlags & PROPSETFLAG_ANSI)
|
||||||
This->codePage = GetACP();
|
This->codePage = GetACP();
|
||||||
else
|
else
|
||||||
This->codePage = 1200;
|
This->codePage = CP_UNICODE;
|
||||||
}
|
}
|
||||||
if (!This->locale)
|
if (!This->locale)
|
||||||
This->locale = LOCALE_SYSTEM_DEFAULT;
|
This->locale = LOCALE_SYSTEM_DEFAULT;
|
||||||
|
@ -1128,10 +1183,18 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface)
|
||||||
|
|
||||||
end:
|
end:
|
||||||
HeapFree(GetProcessHeap(), 0, buf);
|
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;
|
return hr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static HRESULT PropertyStorage_WriteToStream(IPropertyStorage *iface)
|
static HRESULT PropertyStorage_WriteToStream(IPropertyStorage *iface)
|
||||||
{
|
{
|
||||||
FIXME("\n");
|
FIXME("\n");
|
||||||
|
|
|
@ -2,5 +2,6 @@ Makefile
|
||||||
marshal.ok
|
marshal.ok
|
||||||
moniker.ok
|
moniker.ok
|
||||||
propvariant.ok
|
propvariant.ok
|
||||||
|
stg_prop.ok
|
||||||
storage32.ok
|
storage32.ok
|
||||||
testlist.c
|
testlist.c
|
||||||
|
|
|
@ -10,6 +10,7 @@ CTESTS = \
|
||||||
marshal.c \
|
marshal.c \
|
||||||
moniker.c \
|
moniker.c \
|
||||||
propvariant.c \
|
propvariant.c \
|
||||||
|
stg_prop.c \
|
||||||
storage32.c
|
storage32.c
|
||||||
|
|
||||||
@MAKE_TEST_RULES@
|
@MAKE_TEST_RULES@
|
||||||
|
|
|
@ -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 <stdio.h>
|
||||||
|
#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();
|
||||||
|
}
|
Loading…
Reference in New Issue