[msi, 3/5, try 6] msi: Add partial, expandable OLE automation
support.
Misha Koshelev
mk144210 at bcm.tmc.edu
Sun Mar 4 16:44:04 CST 2007
As in previous versions, this adds partial, expandable OLE automation
support (returning DISP_E_MEMBERNOTFOUND for functions not implemented
yet and outputting a FIXME with the method name to the user so we can
implement them). No changes from try 5.
Changelog:
* msi: Add partial, expandable OLE automation support.
-------------- next part --------------
From 570afef768ee5f33ba73d80f686cd65bf6f95275 Mon Sep 17 00:00:00 2001
From: Misha Koshelev <mk144210 at bcm.tmc.edu>
Date: Sun, 4 Mar 2007 12:39:17 -0600
Subject: msi: Add partial, expandable OLE automation support.
---
dlls/msi/Makefile.in | 3
dlls/msi/automation.c | 1013 +++++++++++++++++++++++++++++++++++++++++++
dlls/msi/msi_main.c | 6
dlls/msi/msipriv.h | 3
dlls/msi/tests/automation.c | 16 -
5 files changed, 1027 insertions(+), 14 deletions(-)
diff --git a/dlls/msi/Makefile.in b/dlls/msi/Makefile.in
index 5fb0941..3418fb5 100644
--- a/dlls/msi/Makefile.in
+++ b/dlls/msi/Makefile.in
@@ -12,6 +12,7 @@ C_SRCS = \
action.c \
alter.c \
appsearch.c \
+ automation.c \
classes.c \
create.c \
custom.c \
@@ -47,6 +48,8 @@ C_SRCS = \
upgrade.c \
where.c
+IDL_H_SRCS = msiserver.idl
+IDL_I_SRCS = msiserver.idl
IDL_TLB_SRCS = msiserver.idl
BISON_SRCS = \
diff --git a/dlls/msi/automation.c b/dlls/msi/automation.c
new file mode 100644
index 0000000..0978198
--- /dev/null
+++ b/dlls/msi/automation.c
@@ -0,0 +1,1013 @@
+/*
+ * Implementation of OLE Automation for Microsoft Installer (msi.dll)
+ *
+ * Copyright 2007 Misha Koshelev
+ *
+ * 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 <stdarg.h>
+#include "windef.h"
+#include "winbase.h"
+#include "winerror.h"
+#include "winuser.h"
+#include "msidefs.h"
+#include "msipriv.h"
+#include "activscp.h"
+#include "oleauto.h"
+#include "wine/debug.h"
+#include "wine/unicode.h"
+
+#include "msiserver.h"
+#include "msiserver_dispids.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(msi);
+
+/* FIXME: I don't know how big this should be */
+#define MAX_MSI_STRING 1000
+
+/*
+ * AutomationObject - "base" class for all automation objects. For each interface, we implement Invoke function
+ * called from AutomationObject::Invoke, and pass this function to create_automation_object.
+ */
+
+typedef interface AutomationObject AutomationObject;
+
+interface AutomationObject {
+ /*
+ * VTables - We provide IDispatch, IProvideClassInfo, IProvideClassInfo2, IProvideMultipleClassInfo
+ */
+ const IDispatchVtbl *lpVtbl;
+ const IProvideClassInfoVtbl *lpvtblIProvideClassInfo;
+ const IProvideClassInfo2Vtbl *lpvtblIProvideClassInfo2;
+ const IProvideMultipleClassInfoVtbl *lpvtblIProvideMultipleClassInfo;
+
+ /* Object reference count */
+ LONG ref;
+
+ /* Clsid for this class and it's appropriate ITypeInfo object */
+ LPCLSID clsid;
+ ITypeInfo *iTypeInfo;
+
+ /* The MSI handle of the current object */
+ MSIHANDLE msiHandle;
+
+ /* A function that is called from AutomationObject::Invoke, specific to this type of object. */
+ HRESULT (STDMETHODCALLTYPE *funcInvoke)(
+ AutomationObject* This,
+ DISPID dispIdMember,
+ REFIID riid,
+ LCID lcid,
+ WORD wFlags,
+ DISPPARAMS* pDispParams,
+ VARIANT* pVarResult,
+ EXCEPINFO* pExcepInfo,
+ UINT* puArgErr);
+};
+
+/* VTables */
+static const struct IDispatchVtbl AutomationObject_Vtbl;
+static const struct IProvideClassInfoVtbl AutomationObject_IProvideClassInfo_Vtbl;
+static const struct IProvideClassInfo2Vtbl AutomationObject_IProvideClassInfo2_Vtbl;
+static const struct IProvideMultipleClassInfoVtbl AutomationObject_IProvideMultipleClassInfo_Vtbl;
+
+/* Load type info so we don't have to process GetIDsOfNames */
+HRESULT WINAPI LoadTypeInfo(IDispatch *iface, ITypeInfo **pptinfo, REFIID clsid, LCID lcid)
+{
+ HRESULT hr;
+ LPTYPELIB pLib = NULL;
+ LPTYPEINFO pInfo = NULL;
+ WCHAR szMsiServer[] = {'m','s','i','s','e','r','v','e','r','.','t','l','b'};
+
+ TRACE("(%p)->(%s,%d)\n", iface, debugstr_guid(clsid), lcid);
+
+ /* Load registered type library */
+ hr = LoadRegTypeLib(&LIBID_WindowsInstaller, 1, 0, lcid, &pLib);
+ if (FAILED(hr)) {
+ hr = LoadTypeLib(szMsiServer, &pLib);
+ if (FAILED(hr)) {
+ ERR("Could not load msiserver.tlb\n");
+ return hr;
+ }
+ }
+
+ /* Get type information for object */
+ hr = ITypeLib_GetTypeInfoOfGuid(pLib, clsid, &pInfo);
+ ITypeLib_Release(pLib);
+ if (FAILED(hr)) {
+ ERR("Could not load ITypeInfo for %s\n", debugstr_guid(clsid));
+ return hr;
+ }
+ *pptinfo = pInfo;
+ return S_OK;
+}
+
+/* Create the automation object, placing the result in the pointer ppObj. The automation object is created
+ * with the appropriate clsid and invocation function. */
+HRESULT create_automation_object(MSIHANDLE msiHandle, IUnknown *pUnkOuter, LPVOID *ppObj, REFIID clsid,
+ HRESULT (STDMETHODCALLTYPE *funcInvoke)(AutomationObject*,DISPID,REFIID,LCID,WORD,DISPPARAMS*,
+ VARIANT*,EXCEPINFO*,UINT*))
+{
+ AutomationObject *object;
+ HRESULT hr;
+
+ TRACE("(%ld,%p,%p,%s,%p)\n", (unsigned long)msiHandle, pUnkOuter, ppObj, debugstr_guid(clsid), funcInvoke);
+
+ if( pUnkOuter )
+ return CLASS_E_NOAGGREGATION;
+
+ object = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(AutomationObject));
+
+ /* Set all the VTable references */
+ object->lpVtbl = &AutomationObject_Vtbl;
+ object->lpvtblIProvideClassInfo = &AutomationObject_IProvideClassInfo_Vtbl;
+ object->lpvtblIProvideClassInfo2 = &AutomationObject_IProvideClassInfo2_Vtbl;
+ object->lpvtblIProvideMultipleClassInfo = &AutomationObject_IProvideMultipleClassInfo_Vtbl;
+ object->ref = 1;
+
+ /* Store data that was passed */
+ object->msiHandle = msiHandle;
+ object->clsid = (LPCLSID)clsid;
+ object->funcInvoke = funcInvoke;
+
+ /* Load our TypeInfo so we don't have to process GetIDsOfNames */
+ object->iTypeInfo = NULL;
+ hr = LoadTypeInfo((IDispatch *)object, &object->iTypeInfo, clsid, 0x0);
+ if (FAILED(hr)) {
+ HeapFree(GetProcessHeap(), 0, object);
+ return hr;
+ }
+
+ *ppObj = object;
+
+ return S_OK;
+}
+
+/* Macros to get pointer to AutomationObject from the other VTables. */
+static inline AutomationObject *obj_from_IProvideClassInfo( IProvideClassInfo *iface )
+{
+ return (AutomationObject *)((char*)iface - FIELD_OFFSET(AutomationObject, lpvtblIProvideClassInfo));
+}
+
+static inline AutomationObject *obj_from_IProvideClassInfo2( IProvideClassInfo2 *iface )
+{
+ return (AutomationObject *)((char*)iface - FIELD_OFFSET(AutomationObject, lpvtblIProvideClassInfo2));
+}
+
+static inline AutomationObject *obj_from_IProvideMultipleClassInfo( IProvideMultipleClassInfo *iface )
+{
+ return (AutomationObject *)((char*)iface - FIELD_OFFSET(AutomationObject, lpvtblIProvideMultipleClassInfo));
+}
+
+/*
+ * AutomationObject methods
+ */
+
+/*** IUnknown methods ***/
+static HRESULT WINAPI AutomationObject_QueryInterface(IDispatch* iface, REFIID riid, void** ppvObject)
+{
+ AutomationObject *This = (AutomationObject *)iface;
+
+ TRACE("(%p/%p)->(%s,%p)\n", iface, This, debugstr_guid(riid), ppvObject);
+
+ if ( (This==0) || (ppvObject==0) )
+ return E_INVALIDARG;
+
+ *ppvObject = 0;
+
+ if (IsEqualGUID(riid, &IID_IUnknown) || IsEqualGUID(riid, &IID_IDispatch) || IsEqualGUID(riid, This->clsid))
+ *ppvObject = This;
+ else if (IsEqualGUID(riid, &IID_IProvideClassInfo))
+ *ppvObject = (IProvideClassInfo*)&(This->lpvtblIProvideClassInfo);
+ else if (IsEqualGUID(riid, &IID_IProvideClassInfo2))
+ *ppvObject = (IProvideClassInfo2*)&(This->lpvtblIProvideClassInfo2);
+ else if (IsEqualGUID(riid, &IID_IProvideMultipleClassInfo))
+ *ppvObject = (IProvideMultipleClassInfo*)&(This->lpvtblIProvideMultipleClassInfo);
+
+ if ((*ppvObject)==0)
+ {
+ TRACE("() : asking for unsupported interface %s\n",debugstr_guid(riid));
+ return E_NOINTERFACE;
+ }
+
+ /*
+ * Query Interface always increases the reference count by one when it is
+ * successful
+ */
+ IClassFactory_AddRef(iface);
+
+ return S_OK;
+}
+
+static ULONG WINAPI AutomationObject_AddRef(IDispatch* iface)
+{
+ AutomationObject *This = (AutomationObject *)iface;
+
+ TRACE("(%p/%p)\n", iface, This);
+
+ return InterlockedIncrement(&This->ref);
+}
+
+static ULONG WINAPI AutomationObject_Release(IDispatch* iface)
+{
+ AutomationObject *This = (AutomationObject *)iface;
+ ULONG ref = InterlockedDecrement(&This->ref);
+
+ TRACE("(%p/%p)\n", iface, This);
+
+ if (!ref)
+ {
+ MsiCloseHandle(This->msiHandle);
+ HeapFree(GetProcessHeap(), 0, This);
+ }
+
+ return ref;
+}
+
+/*** IDispatch methods ***/
+static HRESULT WINAPI AutomationObject_GetTypeInfoCount(
+ IDispatch* iface,
+ UINT* pctinfo)
+{
+ AutomationObject *This = (AutomationObject *)iface;
+
+ TRACE("(%p/%p)->(%p)\n", iface, This, pctinfo);
+ *pctinfo = 1;
+ return S_OK;
+}
+
+static HRESULT WINAPI AutomationObject_GetTypeInfo(
+ IDispatch* iface,
+ UINT iTInfo,
+ LCID lcid,
+ ITypeInfo** ppTInfo)
+{
+ AutomationObject *This = (AutomationObject *)iface;
+ TRACE("(%p/%p)->(%d,%d,%p)\n", iface, This, iTInfo, lcid, ppTInfo);
+
+ ITypeInfo_AddRef(This->iTypeInfo);
+ *ppTInfo = This->iTypeInfo;
+ return S_OK;
+}
+
+static HRESULT WINAPI AutomationObject_GetIDsOfNames(
+ IDispatch* iface,
+ REFIID riid,
+ LPOLESTR* rgszNames,
+ UINT cNames,
+ LCID lcid,
+ DISPID* rgDispId)
+{
+ AutomationObject *This = (AutomationObject *)iface;
+ TRACE("(%p/%p)->(%p,%p,%d,%d,%p)\n", iface, This, riid, rgszNames, cNames, lcid, rgDispId);
+
+ if (!IsEqualGUID(riid, &IID_NULL)) return E_INVALIDARG;
+ return ITypeInfo_GetIDsOfNames(This->iTypeInfo, rgszNames, cNames, rgDispId);
+}
+
+/* Maximum number of allowed function parameters+1 */
+#define MAX_FUNC_PARAMS 20
+
+/* Some error checking is done here to simplify individual object function invocation */
+static HRESULT WINAPI AutomationObject_Invoke(
+ IDispatch* iface,
+ DISPID dispIdMember,
+ REFIID riid,
+ LCID lcid,
+ WORD wFlags,
+ DISPPARAMS* pDispParams,
+ VARIANT* pVarResult,
+ EXCEPINFO* pExcepInfo,
+ UINT* puArgErr)
+{
+ AutomationObject *This = (AutomationObject *)iface;
+ HRESULT hr;
+ unsigned int uArgErr;
+ VARIANT varResultDummy;
+ BSTR bstrName = NULL;
+
+ TRACE("(%p/%p)->(%d,%p,%d,%d,%p,%p,%p,%p)\n", iface, This, dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
+
+ if (!IsEqualIID(riid, &IID_NULL))
+ {
+ ERR("riid was %s instead of IID_NULL\n", debugstr_guid(riid));
+ return DISP_E_UNKNOWNNAME;
+ }
+
+ if (wFlags & DISPATCH_PROPERTYGET && !pVarResult)
+ {
+ ERR("NULL pVarResult not allowed when DISPATCH_PROPERTYGET specified\n");
+ return DISP_E_PARAMNOTOPTIONAL;
+ }
+
+ /* This simplifies our individual object invocation functions */
+ if (puArgErr == NULL) puArgErr = &uArgErr;
+ if (pVarResult == NULL) pVarResult = &varResultDummy;
+
+ /* Assume return type is void unless determined otherwise */
+ VariantInit(pVarResult);
+
+ /* If we are tracing, we want to see the name of the member we are invoking */
+ if (TRACE_ON(msi))
+ {
+ ITypeInfo_GetDocumentation(This->iTypeInfo, dispIdMember, &bstrName, NULL, NULL, NULL);
+ TRACE("Method %d, %s\n", dispIdMember, debugstr_w(bstrName));
+ }
+
+ hr = This->funcInvoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr);
+
+ if (hr == DISP_E_MEMBERNOTFOUND) {
+ if (bstrName == NULL) ITypeInfo_GetDocumentation(This->iTypeInfo, dispIdMember, &bstrName, NULL, NULL, NULL);
+ FIXME("Method %d, %s wflags %d not implemented, clsid %s\n", dispIdMember, debugstr_w(bstrName), wFlags, debugstr_guid(This->clsid));
+ }
+ else if (pExcepInfo &&
+ (hr == DISP_E_PARAMNOTFOUND ||
+ hr == DISP_E_EXCEPTION)) {
+ static WCHAR szComma[] = { ',',0 };
+ static WCHAR szExceptionSource[] = {'M','s','i',' ','A','P','I',' ','E','r','r','o','r',0};
+ WCHAR szExceptionDescription[MAX_PATH];
+ BSTR bstrParamNames[MAX_FUNC_PARAMS];
+ unsigned namesNo, i;
+ BOOL bFirst = TRUE;
+
+ if (FAILED(ITypeInfo_GetNames(This->iTypeInfo, dispIdMember, bstrParamNames,
+ MAX_FUNC_PARAMS, &namesNo)))
+ {
+ TRACE("Failed to retrieve names for dispIdMember %d\n", dispIdMember);
+ }
+ else
+ {
+ memset(szExceptionDescription, 0, sizeof(szExceptionDescription));
+ for (i=0; i<namesNo; i++)
+ {
+ if (bFirst) bFirst = FALSE;
+ else {
+ lstrcpyW(&szExceptionDescription[lstrlenW(szExceptionDescription)], szComma);
+ }
+ lstrcpyW(&szExceptionDescription[lstrlenW(szExceptionDescription)], bstrParamNames[i]);
+ SysFreeString(bstrParamNames[i]);
+ }
+
+ memset(pExcepInfo, 0, sizeof(EXCEPINFO));
+ pExcepInfo->wCode = 1000;
+ pExcepInfo->bstrSource = szExceptionSource;
+ pExcepInfo->bstrDescription = szExceptionDescription;
+ hr = DISP_E_EXCEPTION;
+ }
+ }
+
+ /* Make sure we free the return variant if it is our dummy variant */
+ if (pVarResult == &varResultDummy) VariantClear(pVarResult);
+
+ /* Free function name if we retrieved it */
+ if (bstrName == NULL) SysFreeString(bstrName);
+
+ TRACE("Returning 0x%08lx, %s\n", (unsigned long int)hr, SUCCEEDED(hr) ? "ok" : "not ok");
+
+ return hr;
+}
+
+static const struct IDispatchVtbl AutomationObject_Vtbl =
+{
+ AutomationObject_QueryInterface,
+ AutomationObject_AddRef,
+ AutomationObject_Release,
+ AutomationObject_GetTypeInfoCount,
+ AutomationObject_GetTypeInfo,
+ AutomationObject_GetIDsOfNames,
+ AutomationObject_Invoke
+};
+
+/*
+ * IProvideClassInfo methods
+ */
+
+static HRESULT WINAPI AutomationObject_IProvideClassInfo_QueryInterface(
+ IProvideClassInfo* iface,
+ REFIID riid,
+ VOID** ppvoid)
+{
+ AutomationObject *This = obj_from_IProvideClassInfo(iface);
+ return AutomationObject_QueryInterface((IDispatch *)This, riid, ppvoid);
+}
+
+static ULONG WINAPI AutomationObject_IProvideClassInfo_AddRef(IProvideClassInfo* iface)
+{
+ AutomationObject *This = obj_from_IProvideClassInfo(iface);
+ return AutomationObject_AddRef((IDispatch *)This);
+}
+
+static ULONG WINAPI AutomationObject_IProvideClassInfo_Release(IProvideClassInfo* iface)
+{
+ AutomationObject *This = obj_from_IProvideClassInfo(iface);
+ return AutomationObject_Release((IDispatch *)This);
+}
+
+static HRESULT WINAPI AutomationObject_GetClassInfo(IProvideClassInfo* iface, ITypeInfo** ppTI)
+{
+ AutomationObject *This = obj_from_IProvideClassInfo(iface);
+
+ TRACE("(%p/%p)->(%p)\n", iface, This, ppTI);
+ return LoadTypeInfo((IDispatch *)This, ppTI, This->clsid, 0);
+}
+
+static const IProvideClassInfoVtbl AutomationObject_IProvideClassInfo_Vtbl =
+{
+ AutomationObject_IProvideClassInfo_QueryInterface,
+ AutomationObject_IProvideClassInfo_AddRef,
+ AutomationObject_IProvideClassInfo_Release,
+ AutomationObject_GetClassInfo
+};
+
+/*
+ * IProvideClassInfo2 methods
+ */
+
+static HRESULT WINAPI AutomationObject_IProvideClassInfo2_QueryInterface(
+ IProvideClassInfo2* iface,
+ REFIID riid,
+ VOID** ppvoid)
+{
+ AutomationObject *This = obj_from_IProvideClassInfo2(iface);
+ return AutomationObject_QueryInterface((IDispatch *)This, riid, ppvoid);
+}
+
+static ULONG WINAPI AutomationObject_IProvideClassInfo2_AddRef(IProvideClassInfo2* iface)
+{
+ AutomationObject *This = obj_from_IProvideClassInfo2(iface);
+ return AutomationObject_AddRef((IDispatch *)This);
+}
+
+static ULONG WINAPI AutomationObject_IProvideClassInfo2_Release(IProvideClassInfo2* iface)
+{
+ AutomationObject *This = obj_from_IProvideClassInfo2(iface);
+ return AutomationObject_Release((IDispatch *)This);
+}
+
+static HRESULT WINAPI AutomationObject_IProvideClassInfo2_GetClassInfo(IProvideClassInfo2* iface, ITypeInfo** ppTI)
+{
+ AutomationObject *This = obj_from_IProvideClassInfo2(iface);
+ return AutomationObject_GetClassInfo((IProvideClassInfo*)&(This->lpvtblIProvideClassInfo), ppTI);
+}
+
+static HRESULT WINAPI AutomationObject_GetGUID(IProvideClassInfo2* iface, DWORD dwGuidKind, GUID* pGUID)
+{
+ AutomationObject *This = obj_from_IProvideClassInfo2(iface);
+ TRACE("(%p/%p)->(%d,%s)\n", iface, This, dwGuidKind, debugstr_guid(pGUID));
+
+ if (dwGuidKind != GUIDKIND_DEFAULT_SOURCE_DISP_IID)
+ return E_INVALIDARG;
+ else {
+ *pGUID = *This->clsid;
+ return S_OK;
+ }
+}
+
+static const IProvideClassInfo2Vtbl AutomationObject_IProvideClassInfo2_Vtbl =
+{
+ AutomationObject_IProvideClassInfo2_QueryInterface,
+ AutomationObject_IProvideClassInfo2_AddRef,
+ AutomationObject_IProvideClassInfo2_Release,
+ AutomationObject_IProvideClassInfo2_GetClassInfo,
+ AutomationObject_GetGUID
+};
+
+/*
+ * IProvideMultipleClassInfo methods
+ */
+
+static HRESULT WINAPI AutomationObject_IProvideMultipleClassInfo_QueryInterface(
+ IProvideMultipleClassInfo* iface,
+ REFIID riid,
+ VOID** ppvoid)
+{
+ AutomationObject *This = obj_from_IProvideMultipleClassInfo(iface);
+ return AutomationObject_QueryInterface((IDispatch *)This, riid, ppvoid);
+}
+
+static ULONG WINAPI AutomationObject_IProvideMultipleClassInfo_AddRef(IProvideMultipleClassInfo* iface)
+{
+ AutomationObject *This = obj_from_IProvideMultipleClassInfo(iface);
+ return AutomationObject_AddRef((IDispatch *)This);
+}
+
+static ULONG WINAPI AutomationObject_IProvideMultipleClassInfo_Release(IProvideMultipleClassInfo* iface)
+{
+ AutomationObject *This = obj_from_IProvideMultipleClassInfo(iface);
+ return AutomationObject_Release((IDispatch *)This);
+}
+
+static HRESULT WINAPI AutomationObject_IProvideMultipleClassInfo_GetClassInfo(IProvideMultipleClassInfo* iface, ITypeInfo** ppTI)
+{
+ AutomationObject *This = obj_from_IProvideMultipleClassInfo(iface);
+ return AutomationObject_GetClassInfo((IProvideClassInfo*)&(This->lpvtblIProvideClassInfo), ppTI);
+}
+
+static HRESULT WINAPI AutomationObject_IProvideMultipleClassInfo_GetGUID(IProvideMultipleClassInfo* iface, DWORD dwGuidKind, GUID* pGUID)
+{
+ AutomationObject *This = obj_from_IProvideMultipleClassInfo(iface);
+ return AutomationObject_GetGUID((IProvideClassInfo2*)&(This->lpvtblIProvideClassInfo2), dwGuidKind, pGUID);
+}
+
+static HRESULT WINAPI AutomationObject_GetMultiTypeInfoCount(IProvideMultipleClassInfo* iface, ULONG* pcti)
+{
+ AutomationObject *This = obj_from_IProvideMultipleClassInfo(iface);
+
+ TRACE("(%p/%p)->(%p)\n", iface, This, pcti);
+ *pcti = 1;
+ return S_OK;
+}
+
+static HRESULT WINAPI AutomationObject_GetInfoOfIndex(IProvideMultipleClassInfo* iface,
+ ULONG iti,
+ DWORD dwFlags,
+ ITypeInfo** pptiCoClass,
+ DWORD* pdwTIFlags,
+ ULONG* pcdispidReserved,
+ IID* piidPrimary,
+ IID* piidSource)
+{
+ AutomationObject *This = obj_from_IProvideMultipleClassInfo(iface);
+
+ TRACE("(%p/%p)->(%d,%d,%p,%p,%p,%p,%p)\n", iface, This, iti, dwFlags, pptiCoClass, pdwTIFlags, pcdispidReserved, piidPrimary, piidSource);
+
+ if (iti != 0)
+ return E_INVALIDARG;
+
+ if (dwFlags & MULTICLASSINFO_GETTYPEINFO)
+ LoadTypeInfo((IDispatch *)This, pptiCoClass, This->clsid, 0);
+
+ if (dwFlags & MULTICLASSINFO_GETNUMRESERVEDDISPIDS)
+ {
+ *pdwTIFlags = 0;
+ *pcdispidReserved = 0;
+ }
+
+ if (dwFlags & MULTICLASSINFO_GETIIDPRIMARY){
+ *piidPrimary = *This->clsid;
+ }
+
+ if (dwFlags & MULTICLASSINFO_GETIIDSOURCE){
+ *piidSource = *This->clsid;
+ }
+
+ return S_OK;
+}
+
+static const IProvideMultipleClassInfoVtbl AutomationObject_IProvideMultipleClassInfo_Vtbl =
+{
+ AutomationObject_IProvideMultipleClassInfo_QueryInterface,
+ AutomationObject_IProvideMultipleClassInfo_AddRef,
+ AutomationObject_IProvideMultipleClassInfo_Release,
+ AutomationObject_IProvideMultipleClassInfo_GetClassInfo,
+ AutomationObject_IProvideMultipleClassInfo_GetGUID,
+ AutomationObject_GetMultiTypeInfoCount,
+ AutomationObject_GetInfoOfIndex
+};
+
+/*
+ * Individual Object Invocation Functions
+ */
+
+HRESULT WINAPI RecordImpl_Invoke(
+ AutomationObject* This,
+ DISPID dispIdMember,
+ REFIID riid,
+ LCID lcid,
+ WORD wFlags,
+ DISPPARAMS* pDispParams,
+ VARIANT* pVarResult,
+ EXCEPINFO* pExcepInfo,
+ UINT* puArgErr)
+{
+ WCHAR szString[MAX_MSI_STRING];
+ DWORD dwLen = MAX_MSI_STRING;
+ UINT ret;
+ VARIANTARG varg0, varg1;
+ HRESULT hr;
+
+ VariantInit(&varg0);
+ VariantInit(&varg1);
+
+ switch (dispIdMember)
+ {
+ case DISPID_RECORD_STRINGDATA:
+ if (wFlags & DISPATCH_PROPERTYGET) {
+ hr = DispGetParam(pDispParams, 0, VT_I4, &varg0, puArgErr);
+ if (FAILED(hr)) return hr;
+ V_VT(pVarResult) = VT_BSTR;
+ if ((ret = MsiRecordGetStringW(This->msiHandle, V_I4(&varg0), szString, &dwLen)) == ERROR_SUCCESS)
+ V_BSTR(pVarResult) = SysAllocString(szString);
+ else
+ {
+ ERR("MsiRecordGetString returned %d\n", ret);
+ V_BSTR(pVarResult) = NULL;
+ }
+ } else if (wFlags & DISPATCH_PROPERTYPUT) {
+ hr = DispGetParam(pDispParams, 0, VT_I4, &varg0, puArgErr);
+ if (FAILED(hr)) return hr;
+ hr = DispGetParam(pDispParams, DISPID_PROPERTYPUT, VT_BSTR, &varg1, puArgErr);
+ if (FAILED(hr)) return hr;
+ if ((ret = MsiRecordSetStringW(This->msiHandle, V_I4(&varg0), V_BSTR(&varg1))) != ERROR_SUCCESS)
+ {
+ ERR("MsiRecordSetString returned %d\n", ret);
+ return DISP_E_EXCEPTION;
+ }
+ }
+ break;
+
+ default:
+ return DISP_E_MEMBERNOTFOUND;
+ }
+
+ return S_OK;
+}
+
+HRESULT WINAPI ViewImpl_Invoke(
+ AutomationObject* This,
+ DISPID dispIdMember,
+ REFIID riid,
+ LCID lcid,
+ WORD wFlags,
+ DISPPARAMS* pDispParams,
+ VARIANT* pVarResult,
+ EXCEPINFO* pExcepInfo,
+ UINT* puArgErr)
+{
+ MSIHANDLE msiHandle;
+ IDispatch *pDispatch = NULL;
+ UINT ret;
+ VARIANTARG varg0, varg1;
+ HRESULT hr;
+
+ VariantInit(&varg0);
+ VariantInit(&varg1);
+
+ switch (dispIdMember)
+ {
+ case DISPID_VIEW_EXECUTE:
+ if (wFlags & DISPATCH_METHOD)
+ {
+ hr = DispGetParam(pDispParams, 0, VT_DISPATCH, &varg0, puArgErr);
+ if (SUCCEEDED(hr) && V_DISPATCH(&varg0) != NULL)
+ MsiViewExecute(This->msiHandle, ((AutomationObject *)V_DISPATCH(&varg0))->msiHandle);
+ else
+ MsiViewExecute(This->msiHandle, 0);
+ }
+ break;
+
+ case DISPID_VIEW_FETCH:
+ if (wFlags & DISPATCH_METHOD)
+ {
+ V_VT(pVarResult) = VT_DISPATCH;
+ if ((ret = MsiViewFetch(This->msiHandle, &msiHandle)) == ERROR_SUCCESS)
+ {
+ if (SUCCEEDED(create_automation_object(msiHandle, NULL, (LPVOID)&pDispatch, &DIID_Record, RecordImpl_Invoke)))
+ {
+ IDispatch_AddRef(pDispatch);
+ V_DISPATCH(pVarResult) = pDispatch;
+ }
+ }
+ else if (ret == ERROR_NO_MORE_ITEMS)
+ V_DISPATCH(pVarResult) = NULL;
+ else
+ {
+ ERR("MsiViewFetch returned %d\n", ret);
+ return DISP_E_EXCEPTION;
+ }
+ }
+ break;
+
+ case DISPID_VIEW_CLOSE:
+ if (wFlags & DISPATCH_METHOD)
+ {
+ MsiViewClose(This->msiHandle);
+ }
+ break;
+
+ default:
+ return DISP_E_MEMBERNOTFOUND;
+ }
+
+ return S_OK;
+}
+
+HRESULT WINAPI DatabaseImpl_Invoke(
+ AutomationObject* This,
+ DISPID dispIdMember,
+ REFIID riid,
+ LCID lcid,
+ WORD wFlags,
+ DISPPARAMS* pDispParams,
+ VARIANT* pVarResult,
+ EXCEPINFO* pExcepInfo,
+ UINT* puArgErr)
+{
+ MSIHANDLE msiHandle;
+ IDispatch *pDispatch = NULL;
+ UINT ret;
+ VARIANTARG varg0, varg1;
+ HRESULT hr;
+
+ VariantInit(&varg0);
+ VariantInit(&varg1);
+
+ switch (dispIdMember)
+ {
+ case DISPID_DATABASE_OPENVIEW:
+ if (wFlags & DISPATCH_METHOD)
+ {
+ hr = DispGetParam(pDispParams, 0, VT_BSTR, &varg0, puArgErr);
+ if (FAILED(hr)) return hr;
+ V_VT(pVarResult) = VT_DISPATCH;
+ if ((ret = MsiDatabaseOpenViewW(This->msiHandle, V_BSTR(&varg0), &msiHandle)) == ERROR_SUCCESS)
+ {
+ if (SUCCEEDED(create_automation_object(msiHandle, NULL, (LPVOID)&pDispatch, &DIID_View, ViewImpl_Invoke)))
+ {
+ IDispatch_AddRef(pDispatch);
+ V_DISPATCH(pVarResult) = pDispatch;
+ }
+ }
+ else
+ {
+ ERR("MsiDatabaseOpenView returned %d\n", ret);
+ return DISP_E_EXCEPTION;
+ }
+ }
+ break;
+
+ default:
+ return DISP_E_MEMBERNOTFOUND;
+ }
+
+ return S_OK;
+}
+
+HRESULT WINAPI SessionImpl_Invoke(
+ AutomationObject* This,
+ DISPID dispIdMember,
+ REFIID riid,
+ LCID lcid,
+ WORD wFlags,
+ DISPPARAMS* pDispParams,
+ VARIANT* pVarResult,
+ EXCEPINFO* pExcepInfo,
+ UINT* puArgErr)
+{
+ WCHAR szString[MAX_MSI_STRING];
+ DWORD dwLen = MAX_MSI_STRING;
+ IDispatch *pDispatch = NULL;
+ MSIHANDLE msiHandle;
+ LANGID langId;
+ UINT ret;
+ INSTALLSTATE iInstalled, iAction;
+ VARIANTARG varg0, varg1;
+ HRESULT hr;
+
+ VariantInit(&varg0);
+ VariantInit(&varg1);
+
+ switch (dispIdMember)
+ {
+ case DISPID_SESSION_PROPERTY:
+ if (wFlags & DISPATCH_PROPERTYGET) {
+ hr = DispGetParam(pDispParams, 0, VT_BSTR, &varg0, puArgErr);
+ if (FAILED(hr)) return hr;
+ V_VT(pVarResult) = VT_BSTR;
+ if (MsiGetPropertyW(This->msiHandle, V_BSTR(&varg0), szString, &dwLen) == ERROR_SUCCESS)
+ V_BSTR(pVarResult) = SysAllocString(szString);
+ else
+ V_BSTR(pVarResult) = NULL;
+ } else if (wFlags & DISPATCH_PROPERTYPUT) {
+ hr = DispGetParam(pDispParams, 0, VT_BSTR, &varg0, puArgErr);
+ if (FAILED(hr)) return hr;
+ hr = DispGetParam(pDispParams, DISPID_PROPERTYPUT, VT_BSTR, &varg1, puArgErr);
+ if (FAILED(hr)) {
+ VariantClear(&varg0);
+ return hr;
+ }
+ if ((ret = MsiSetPropertyW(This->msiHandle, V_BSTR(&varg0), V_BSTR(&varg1))) != ERROR_SUCCESS)
+ {
+ ERR("MsiSetProperty returned %d\n", ret);
+ return DISP_E_EXCEPTION;
+ }
+ }
+ break;
+
+ case DISPID_SESSION_LANGUAGE:
+ if (wFlags & DISPATCH_PROPERTYGET) {
+ langId = MsiGetLanguage(This->msiHandle);
+ V_VT(pVarResult) = VT_I4;
+ V_I4(pVarResult) = langId;
+ }
+ break;
+
+ case DISPID_SESSION_MODE:
+ if (wFlags & DISPATCH_PROPERTYGET) {
+ hr = DispGetParam(pDispParams, 0, VT_I4, &varg0, puArgErr);
+ if (FAILED(hr)) return hr;
+ V_VT(pVarResult) = VT_BOOL;
+ V_BOOL(pVarResult) = MsiGetMode(This->msiHandle, V_I4(&varg0));
+ } else if (wFlags & DISPATCH_PROPERTYPUT) {
+ hr = DispGetParam(pDispParams, 0, VT_I4, &varg0, puArgErr);
+ if (FAILED(hr)) return hr;
+ hr = DispGetParam(pDispParams, DISPID_PROPERTYPUT, VT_BOOL, &varg1, puArgErr);
+ if (FAILED(hr)) return hr;
+ if ((ret = MsiSetMode(This->msiHandle, V_I4(&varg0), V_BOOL(&varg1))) != ERROR_SUCCESS)
+ {
+ ERR("MsiSetMode returned %d\n", ret);
+ return DISP_E_EXCEPTION;
+ }
+ }
+ break;
+
+ case DISPID_SESSION_DATABASE:
+ if (wFlags & DISPATCH_PROPERTYGET) {
+ V_VT(pVarResult) = VT_DISPATCH;
+ if ((msiHandle = MsiGetActiveDatabase(This->msiHandle)))
+ {
+ if (SUCCEEDED(create_automation_object(msiHandle, NULL, (LPVOID)&pDispatch, &DIID_Database, DatabaseImpl_Invoke)))
+ {
+ IDispatch_AddRef(pDispatch);
+ V_DISPATCH(pVarResult) = pDispatch;
+ }
+ }
+ else
+ {
+ ERR("MsiGetActiveDatabase failed\n");
+ return DISP_E_EXCEPTION;
+ }
+ }
+ break;
+
+ case DISPID_SESSION_DOACTION:
+ if (wFlags & DISPATCH_METHOD) {
+ hr = DispGetParam(pDispParams, 0, VT_BSTR, &varg0, puArgErr);
+ if (FAILED(hr)) return hr;
+ ret = MsiDoActionW(This->msiHandle, V_BSTR(&varg0));
+ V_VT(pVarResult) = VT_I4;
+ switch (ret)
+ {
+ case ERROR_FUNCTION_NOT_CALLED:
+ V_I4(pVarResult) = msiDoActionStatusNoAction;
+ break;
+ case ERROR_SUCCESS:
+ V_I4(pVarResult) = msiDoActionStatusSuccess;
+ break;
+ case ERROR_INSTALL_USEREXIT:
+ V_I4(pVarResult) = msiDoActionStatusUserExit;
+ break;
+ case ERROR_INSTALL_FAILURE:
+ V_I4(pVarResult) = msiDoActionStatusFailure;
+ break;
+ case ERROR_INSTALL_SUSPEND:
+ V_I4(pVarResult) = msiDoActionStatusSuspend;
+ break;
+ case ERROR_MORE_DATA:
+ V_I4(pVarResult) = msiDoActionStatusFinished;
+ break;
+ case ERROR_INVALID_HANDLE_STATE:
+ V_I4(pVarResult) = msiDoActionStatusWrongState;
+ break;
+ case ERROR_INVALID_DATA:
+ V_I4(pVarResult) = msiDoActionStatusBadActionData;
+ break;
+ default:
+ FIXME("MsiDoAction returned unhandled value %d\n", ret);
+ return DISP_E_EXCEPTION;
+ }
+ }
+ break;
+
+ case DISPID_SESSION_SETINSTALLLEVEL:
+ hr = DispGetParam(pDispParams, 0, VT_I4, &varg0, puArgErr);
+ if (FAILED(hr)) return hr;
+ if ((ret = MsiSetInstallLevel(This->msiHandle, V_I4(&varg0))) != ERROR_SUCCESS)
+ {
+ ERR("MsiSetInstallLevel returned %d\n", ret);
+ return DISP_E_EXCEPTION;
+ }
+ break;
+
+ case DISPID_SESSION_FEATURECURRENTSTATE:
+ if (wFlags & DISPATCH_PROPERTYGET) {
+ hr = DispGetParam(pDispParams, 0, VT_BSTR, &varg0, puArgErr);
+ if (FAILED(hr)) return hr;
+ V_VT(pVarResult) = VT_I4;
+ if ((ret = MsiGetFeatureStateW(This->msiHandle, V_BSTR(&varg0), &iInstalled, &iAction)) == ERROR_SUCCESS)
+ V_I4(pVarResult) = iInstalled;
+ else
+ {
+ ERR("MsiGetFeatureState returned %d\n", ret);
+ V_I4(pVarResult) = msiInstallStateUnknown;
+ }
+ }
+ break;
+
+ case DISPID_SESSION_FEATUREREQUESTSTATE:
+ if (wFlags & DISPATCH_PROPERTYGET) {
+ hr = DispGetParam(pDispParams, 0, VT_BSTR, &varg0, puArgErr);
+ if (FAILED(hr)) return hr;
+ V_VT(pVarResult) = VT_I4;
+ if ((ret = MsiGetFeatureStateW(This->msiHandle, V_BSTR(&varg0), &iInstalled, &iAction)) == ERROR_SUCCESS)
+ V_I4(pVarResult) = iAction;
+ else
+ {
+ ERR("MsiGetFeatureState returned %d\n", ret);
+ V_I4(pVarResult) = msiInstallStateUnknown;
+ }
+ } else if (wFlags & DISPATCH_PROPERTYPUT) {
+ hr = DispGetParam(pDispParams, 0, VT_BSTR, &varg0, puArgErr);
+ if (FAILED(hr)) return hr;
+ hr = DispGetParam(pDispParams, DISPID_PROPERTYPUT, VT_I4, &varg1, puArgErr);
+ if (FAILED(hr)) {
+ VariantClear(&varg0);
+ return hr;
+ }
+ if ((ret = MsiSetFeatureStateW(This->msiHandle, V_BSTR(&varg0), V_I4(&varg1))) != ERROR_SUCCESS)
+ {
+ ERR("MsiSetFeatureState returned %d\n", ret);
+ return DISP_E_EXCEPTION;
+ }
+ }
+ break;
+
+ default:
+ return DISP_E_MEMBERNOTFOUND;
+ }
+
+ return S_OK;
+}
+
+HRESULT WINAPI InstallerImpl_Invoke(
+ AutomationObject* This,
+ DISPID dispIdMember,
+ REFIID riid,
+ LCID lcid,
+ WORD wFlags,
+ DISPPARAMS* pDispParams,
+ VARIANT* pVarResult,
+ EXCEPINFO* pExcepInfo,
+ UINT* puArgErr)
+{
+ MSIHANDLE msiHandle;
+ IDispatch *pDispatch = NULL;
+ UINT ret;
+ VARIANTARG varg0, varg1;
+ HRESULT hr;
+
+ VariantInit(&varg0);
+ VariantInit(&varg1);
+
+ switch (dispIdMember)
+ {
+ case DISPID_INSTALLER_OPENPACKAGE:
+ if (wFlags & DISPATCH_METHOD)
+ {
+ hr = DispGetParam(pDispParams, 0, VT_BSTR, &varg0, puArgErr);
+ if (FAILED(hr)) return hr;
+ hr = DispGetParam(pDispParams, 1, VT_I4, &varg1, puArgErr);
+ if (FAILED(hr)) return hr;
+ V_VT(pVarResult) = VT_DISPATCH;
+ if ((ret = MsiOpenPackageExW(V_BSTR(&varg0), V_I4(&varg1), &msiHandle)) == ERROR_SUCCESS)
+ {
+ if (SUCCEEDED(create_automation_object(msiHandle, NULL, (LPVOID)&pDispatch, &DIID_Session, SessionImpl_Invoke)))
+ {
+ IDispatch_AddRef(pDispatch);
+ V_DISPATCH(pVarResult) = pDispatch;
+ }
+ }
+ else
+ {
+ ERR("MsiOpenPackageEx returned %d\n", ret);
+ return DISP_E_EXCEPTION;
+ }
+ }
+ break;
+
+ default:
+ return DISP_E_MEMBERNOTFOUND;
+ }
+
+ return S_OK;
+}
+
+/* Wrapper around create_automation_object to create an installer object. */
+HRESULT create_msiserver(IUnknown *pOuter, LPVOID *ppObj)
+{
+ return create_automation_object(0, pOuter, ppObj, &DIID_Installer, InstallerImpl_Invoke);
+}
diff --git a/dlls/msi/msi_main.c b/dlls/msi/msi_main.c
index a3dac7f..402e173 100644
--- a/dlls/msi/msi_main.c
+++ b/dlls/msi/msi_main.c
@@ -115,12 +115,6 @@ ITypeLib *get_msi_typelib( LPWSTR *path
return msi_typelib;
}
-static HRESULT create_msiserver( IUnknown *pOuter, LPVOID *ppObj )
-{
- FIXME("\n");
- return E_FAIL;
-}
-
typedef struct tagIClassFactoryImpl {
const IClassFactoryVtbl *lpVtbl;
HRESULT (*create_object)( IUnknown*, LPVOID* );
diff --git a/dlls/msi/msipriv.h b/dlls/msi/msipriv.h
index 971f71e..16cf75d 100644
--- a/dlls/msi/msipriv.h
+++ b/dlls/msi/msipriv.h
@@ -788,6 +788,9 @@ extern VOID ControlEvent_SubscribeToEven
extern VOID ControlEvent_UnSubscribeToEvent( MSIPACKAGE *package, LPCWSTR event,
LPCWSTR control, LPCWSTR attribute );
+/* OLE automation */
+extern HRESULT create_msiserver(IUnknown *pOuter, LPVOID *ppObj);
+
/* User Interface messages from the actions */
extern void ui_progress(MSIPACKAGE *, int, int, int, int);
extern void ui_actiondata(MSIPACKAGE *, LPCWSTR, MSIRECORD *);
diff --git a/dlls/msi/tests/automation.c b/dlls/msi/tests/automation.c
index c830efd..ecacaf0 100644
--- a/dlls/msi/tests/automation.c
+++ b/dlls/msi/tests/automation.c
@@ -354,14 +354,14 @@ static void test_dispatch()
/* Try with NULL params */
hr = IDispatch_Invoke(pInstaller, dispid, &IID_NULL, LOCALE_NEUTRAL, DISPATCH_METHOD, &dispparams, &varresult, &excepinfo, NULL);
- ok(hr == DISP_E_TYPEMISMATCH, "IDispatch::Invoke returned 0x%08lx\n", (unsigned long int)hr);
+ todo_wine ok(hr == DISP_E_TYPEMISMATCH, "IDispatch::Invoke returned 0x%08lx\n", (unsigned long int)hr);
/* Try one empty parameter */
dispparams.rgvarg = vararg;
dispparams.cArgs = 1;
VariantInit(&vararg[0]);
hr = IDispatch_Invoke(pInstaller, dispid, &IID_NULL, LOCALE_NEUTRAL, DISPATCH_METHOD, &dispparams, &varresult, &excepinfo, NULL);
- ok(hr == DISP_E_TYPEMISMATCH, "IDispatch::Invoke returned 0x%08lx\n", (unsigned long int)hr);
+ todo_wine ok(hr == DISP_E_TYPEMISMATCH, "IDispatch::Invoke returned 0x%08lx\n", (unsigned long int)hr);
/* Try one parameter, function requires two */
VariantInit(&vararg[0]);
@@ -808,16 +808,16 @@ static void test_Session(IDispatch *pSes
/* Session::Mode, get */
hr = Session_ModeGet(pSession, MSIRUNMODE_REBOOTATEND, &bool);
ok(SUCCEEDED(hr), "Session_ModeGet failed, hresult 0x%08lx\n", (unsigned long int)hr);
- ok(!bool, "Reboot at end session mode is %d\n", bool);
+ todo_wine ok(!bool, "Reboot at end session mode is %d\n", bool);
/* Session::Mode, put */
hr = Session_ModePut(pSession, MSIRUNMODE_REBOOTATEND, TRUE);
- ok(SUCCEEDED(hr), "Session_ModePut failed, hresult 0x%08lx\n", (unsigned long int)hr);
+ todo_wine ok(SUCCEEDED(hr), "Session_ModePut failed, hresult 0x%08lx\n", (unsigned long int)hr);
hr = Session_ModeGet(pSession, MSIRUNMODE_REBOOTATEND, &bool);
ok(SUCCEEDED(hr), "Session_ModeGet failed, hresult 0x%08lx\n", (unsigned long int)hr);
ok(bool, "Reboot at end session mode is %d, expected 1\n", bool);
hr = Session_ModePut(pSession, MSIRUNMODE_REBOOTATEND, FALSE); /* set it again so we don't reboot */
- ok(SUCCEEDED(hr), "Session_ModePut failed, hresult 0x%08lx\n", (unsigned long int)hr);
+ todo_wine ok(SUCCEEDED(hr), "Session_ModePut failed, hresult 0x%08lx\n", (unsigned long int)hr);
/* Session::Database, get */
hr = Session_Database(pSession, &pDatabase);
@@ -904,15 +904,15 @@ START_TEST(automation)
hr = CLSIDFromProgID(szProgId, &clsid);
ok (SUCCEEDED(hr), "CLSIDFromProgID returned 0x%08lx\n", (unsigned long int)hr);
hr = CoCreateInstance(&clsid, NULL, CLSCTX_INPROC_SERVER, &IID_IUnknown, (void **)&pUnk);
- todo_wine ok(SUCCEEDED(hr), "CoCreateInstance returned 0x%08lx\n", (unsigned long int)hr);
+ ok(SUCCEEDED(hr), "CoCreateInstance returned 0x%08lx\n", (unsigned long int)hr);
if (pUnk)
{
hr = IUnknown_QueryInterface(pUnk, &IID_IDispatch, (void **)&pInstaller);
ok (SUCCEEDED(hr), "IUnknown::QueryInterface returned 0x%08lx\n", (unsigned long int)hr);
- todo_wine test_dispatch();
- todo_wine test_Installer();
+ test_dispatch();
+ test_Installer();
hr = IUnknown_Release(pUnk);
ok (SUCCEEDED(hr), "IUnknown::Release returned 0x%08lx\n", (unsigned long int)hr);
--
1.4.1
More information about the wine-patches
mailing list