2 * Gameux library coclass GameStatistics implementation
4 * Copyright (C) 2010 Mariusz PluciĆski
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
31 #include "gameux_private.h"
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(gameux
);
38 * constant definitions
40 #define MAX_CATEGORY_LENGTH 60
41 #define MAX_NAME_LENGTH 30
42 #define MAX_VALUE_LENGTH 30
43 #define MAX_CATEGORIES 10
44 #define MAX_STATS_PER_CATEGORY 10
45 /*******************************************************************************
46 * Game statistics helper components
48 /*******************************************************************************
51 * set of structures for containing game's data
53 struct GAMEUX_STATS_STAT
55 WCHAR sName
[MAX_NAME_LENGTH
+1];
56 WCHAR sValue
[MAX_VALUE_LENGTH
+1];
58 struct GAMEUX_STATS_CATEGORY
60 WCHAR sName
[MAX_CATEGORY_LENGTH
+1];
61 struct GAMEUX_STATS_STAT stats
[MAX_STATS_PER_CATEGORY
];
65 WCHAR sStatsFile
[MAX_PATH
];
66 struct GAMEUX_STATS_CATEGORY categories
[MAX_CATEGORIES
];
68 /*******************************************************************************
69 * GAMEUX_createStatsDirectory
71 * Helper function, creates directory to store game statistics
74 * path [I] path to game statistics file.
75 * base directory of this file will
76 * be created if it doesn't exists
78 static HRESULT
GAMEUX_createStatsDirectory(LPCWSTR lpFilePath
)
81 WCHAR lpDirectoryPath
[MAX_PATH
];
84 lpEnd
= StrRChrW(lpFilePath
, NULL
, '\\');
85 lstrcpynW(lpDirectoryPath
, lpFilePath
, lpEnd
-lpFilePath
+1);
87 hr
= HRESULT_FROM_WIN32(SHCreateDirectoryExW(NULL
, lpDirectoryPath
, NULL
));
89 if(hr
== HRESULT_FROM_WIN32(ERROR_FILE_EXISTS
) ||
90 hr
== HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS
))
95 /*******************************************************************
96 * GAMEUX_updateStatisticsFile
98 * Helper function updating data stored in statistics file
101 * data [I] pointer to struct containing
104 static HRESULT
GAMEUX_updateStatisticsFile(struct GAMEUX_STATS
*stats
)
106 static const WCHAR sStatistics
[] = {'S','t','a','t','i','s','t','i','c','s',0};
107 static const WCHAR sCategory
[] = {'C','a','t','e','g','o','r','y',0};
108 static const WCHAR sIndex
[] = {'I','n','d','e','x',0};
109 static const WCHAR sStatistic
[] = {'S','t','a','t','i','s','t','i','c',0};
110 static const WCHAR sName
[] = {'N','a','m','e',0};
111 static const WCHAR sValue
[] = {'V','a','l','u','e',0};
114 IXMLDOMDocument
*document
;
115 IXMLDOMElement
*root
, *categoryElement
, *statisticsElement
;
116 IXMLDOMNode
*categoryNode
, *statisticsNode
;
117 VARIANT vStatsFilePath
, vValue
;
118 BSTR bstrStatistics
= NULL
, bstrCategory
= NULL
, bstrIndex
= NULL
,
119 bstrStatistic
= NULL
, bstrName
= NULL
, bstrValue
= NULL
;
122 TRACE("(%p)\n", stats
);
124 V_VT(&vStatsFilePath
) = VT_BSTR
;
125 V_BSTR(&vStatsFilePath
) = SysAllocString(stats
->sStatsFile
);
126 if(!V_BSTR(&vStatsFilePath
))
130 hr
= CoCreateInstance(&CLSID_DOMDocument
, NULL
, CLSCTX_INPROC_SERVER
,
131 &IID_IXMLDOMDocument
, (void**)&document
);
135 bstrStatistics
= SysAllocString(sStatistics
);
141 hr
= IXMLDOMDocument_createElement(document
, bstrStatistics
, &root
);
145 bstrCategory
= SysAllocString(sCategory
);
152 bstrIndex
= SysAllocString(sIndex
);
159 bstrStatistic
= SysAllocString(sStatistic
);
166 bstrName
= SysAllocString(sName
);
173 bstrValue
= SysAllocString(sValue
);
181 for(i
=0; i
<MAX_CATEGORIES
; ++i
)
183 if(lstrlenW(stats
->categories
[i
].sName
)==0)
186 V_VT(&vValue
) = VT_INT
;
187 V_INT(&vValue
) = NODE_ELEMENT
;
189 hr
= IXMLDOMDocument_createNode(document
, vValue
, bstrCategory
, NULL
, &categoryNode
);
192 hr
= IXMLDOMNode_QueryInterface(categoryNode
, &IID_IXMLDOMElement
, (LPVOID
*)&categoryElement
);
196 hr
= IXMLDOMElement_setAttribute(categoryElement
, bstrIndex
, vValue
);
200 V_VT(&vValue
) = VT_BSTR
;
201 V_BSTR(&vValue
) = SysAllocString(stats
->categories
[i
].sName
);
208 TRACE("storing category %d: %s\n", i
, debugstr_w(V_BSTR(&vValue
)));
209 hr
= IXMLDOMElement_setAttribute(categoryElement
, bstrName
, vValue
);
212 SysFreeString(V_BSTR(&vValue
));
216 for(j
=0; j
<MAX_STATS_PER_CATEGORY
; ++j
)
218 if(lstrlenW(stats
->categories
[i
].stats
[j
].sName
)==0)
221 V_VT(&vValue
) = VT_INT
;
222 V_INT(&vValue
) = NODE_ELEMENT
;
224 hr
= IXMLDOMDocument_createNode(document
, vValue
, bstrStatistic
, NULL
, &statisticsNode
);
227 hr
= IXMLDOMNode_QueryInterface(statisticsNode
, &IID_IXMLDOMElement
, (LPVOID
*)&statisticsElement
);
231 hr
= IXMLDOMElement_setAttribute(statisticsElement
, bstrIndex
, vValue
);
235 V_VT(&vValue
) = VT_BSTR
;
236 V_BSTR(&vValue
) = SysAllocString(stats
->categories
[i
].stats
[j
].sName
);
243 TRACE(" storing statistic %d: name: %s\n", j
, debugstr_w(V_BSTR(&vValue
)));
244 hr
= IXMLDOMElement_setAttribute(statisticsElement
, bstrName
, vValue
);
247 SysFreeString(V_BSTR(&vValue
));
251 V_VT(&vValue
) = VT_BSTR
;
252 V_BSTR(&vValue
) = SysAllocString(stats
->categories
[i
].stats
[j
].sValue
);
259 TRACE(" storing statistic %d: name: %s\n", j
, debugstr_w(V_BSTR(&vValue
)));
260 hr
= IXMLDOMElement_setAttribute(statisticsElement
, bstrValue
, vValue
);
263 SysFreeString(V_BSTR(&vValue
));
266 hr
= IXMLDOMElement_appendChild(categoryNode
, statisticsNode
, &statisticsNode
);
268 IXMLDOMElement_Release(statisticsElement
);
269 IXMLDOMNode_Release(statisticsNode
);
274 hr
= IXMLDOMElement_appendChild(root
, categoryNode
, &categoryNode
);
276 IXMLDOMElement_Release(categoryElement
);
277 IXMLDOMNode_Release(categoryNode
);
284 hr
= IXMLDOMDocument_putref_documentElement(document
, root
);
286 IXMLDOMElement_Release(root
);
288 TRACE("saving game statistics in %s file\n", debugstr_w(stats
->sStatsFile
));
290 hr
= GAMEUX_createStatsDirectory(stats
->sStatsFile
);
293 hr
= IXMLDOMDocument_save(document
, vStatsFilePath
);
295 IXMLDOMDocument_Release(document
);
297 SysFreeString(bstrValue
);
298 SysFreeString(bstrName
);
299 SysFreeString(bstrStatistic
);
300 SysFreeString(bstrIndex
);
301 SysFreeString(bstrCategory
);
302 SysFreeString(bstrStatistics
);
303 SysFreeString(V_BSTR(&vStatsFilePath
));
304 TRACE("ret=0x%x\n", hr
);
307 /*******************************************************************************
308 * GAMEUX_buildStatisticsFilePath
309 * Creates path to file contaning statistics of game with given id.
312 * lpApplicationId [I] application id of game,
314 * lpStatisticsFile [O] array where path will be
315 * stored. It's size must be
318 static HRESULT
GAMEUX_buildStatisticsFilePath(
319 LPCWSTR lpApplicationId
,
320 LPWSTR lpStatisticsFile
)
322 static const WCHAR sBackslash
[] = {'\\',0};
323 static const WCHAR sStatisticsDir
[] = {'\\','M','i','c','r','o','s','o','f','t',
324 '\\','W','i','n','d','o','w','s','\\','G','a','m','e','E','x','p',
325 'l','o','r','e','r','\\','G','a','m','e','S','t','a','t','i','s',
327 static const WCHAR sDotGamestats
[] = {'.','g','a','m','e','s','t','a','t','s',0};
331 hr
= SHGetFolderPathW(NULL
, CSIDL_LOCAL_APPDATA
, NULL
, SHGFP_TYPE_CURRENT
, lpStatisticsFile
);
335 lstrcatW(lpStatisticsFile
, sStatisticsDir
);
336 lstrcatW(lpStatisticsFile
, lpApplicationId
);
337 lstrcatW(lpStatisticsFile
, sBackslash
);
338 lstrcatW(lpStatisticsFile
, lpApplicationId
);
339 lstrcatW(lpStatisticsFile
, sDotGamestats
);
344 /*******************************************************************************
345 * GAMEUX_getAppIdFromGDFPath
347 * Loads application identifier associated with given GDF binary.
348 * Routine reads identifier from registry, so will fail if game
352 * GDFBinaryPath [I] path to gdf binary
353 * lpApplicationId [O] place to store application id.
354 * must be at least 49 characters
355 * to store guid and termination 0
357 static HRESULT
GAMEUX_getAppIdFromGDFPath(
358 LPCWSTR GDFBinaryPath
,
359 LPWSTR lpApplicationId
)
361 static const WCHAR sApplicationId
[] =
362 {'A','p','p','l','i','c','a','t','i','o','n','I','d',0};
365 GAME_INSTALL_SCOPE installScope
;
367 LPWSTR lpRegistryPath
;
368 DWORD dwLength
= 49*sizeof(WCHAR
);/* place for GUID */
370 TRACE("(%s, %p)\n", debugstr_w(GDFBinaryPath
), lpApplicationId
);
375 installScope
= GIS_CURRENT_USER
;
376 hr
= GAMEUX_FindGameInstanceId(GDFBinaryPath
, installScope
, &instanceId
);
380 installScope
= GIS_ALL_USERS
;
381 hr
= GAMEUX_FindGameInstanceId(GDFBinaryPath
, installScope
, &instanceId
);
385 /* game not registered, so statistics cannot be used */
389 /* game is registered, let's read it's application id from registry */
390 hr
= GAMEUX_buildGameRegistryPath(installScope
, &instanceId
, &lpRegistryPath
);
393 hr
= HRESULT_FROM_WIN32(RegGetValueW(HKEY_LOCAL_MACHINE
,
394 lpRegistryPath
, sApplicationId
, RRF_RT_REG_SZ
,
395 NULL
, lpApplicationId
, &dwLength
));
397 HeapFree(GetProcessHeap(), 0, lpRegistryPath
);
399 TRACE("found app id: %s, return: %#x\n", debugstr_w(lpApplicationId
), hr
);
402 /*******************************************************************
403 * IGameStatistics implementation
405 typedef struct _GameStatisticsImpl
407 const struct IGameStatisticsVtbl
*lpVtbl
;
409 struct GAMEUX_STATS stats
;
410 } GameStatisticsImpl
;
412 static inline GameStatisticsImpl
*impl_from_IGameStatistics( IGameStatistics
*iface
)
414 return (GameStatisticsImpl
*)((char*)iface
- FIELD_OFFSET(GameStatisticsImpl
, lpVtbl
));
416 static inline IGameStatistics
*IGameStatistics_from_impl( GameStatisticsImpl
* This
)
418 return (struct IGameStatistics
*)&This
->lpVtbl
;
422 static HRESULT WINAPI
GameStatisticsImpl_QueryInterface(
423 IGameStatistics
*iface
,
427 GameStatisticsImpl
*This
= impl_from_IGameStatistics( iface
);
429 TRACE("%p %s %p\n", This
, debugstr_guid( riid
), ppvObject
);
433 if ( IsEqualGUID( riid
, &IID_IUnknown
) ||
434 IsEqualGUID( riid
, &IID_IGameStatistics
) )
440 FIXME("interface %s not implemented\n", debugstr_guid(riid
));
441 return E_NOINTERFACE
;
444 IGameStatistics_AddRef( iface
);
448 static ULONG WINAPI
GameStatisticsImpl_AddRef(IGameStatistics
*iface
)
450 GameStatisticsImpl
*This
= impl_from_IGameStatistics( iface
);
453 ref
= InterlockedIncrement(&This
->ref
);
455 TRACE("(%p): ref=%d\n", This
, ref
);
459 static ULONG WINAPI
GameStatisticsImpl_Release(IGameStatistics
*iface
)
461 GameStatisticsImpl
*This
= impl_from_IGameStatistics( iface
);
464 ref
= InterlockedDecrement( &This
->ref
);
465 TRACE("(%p): ref=%d\n", This
, ref
);
469 TRACE("freeing IGameStatistics\n");
470 HeapFree( GetProcessHeap(), 0, This
);
476 static HRESULT WINAPI
GameStatisticsImpl_GetMaxCategoryLength(
477 IGameStatistics
*iface
,
480 TRACE("(%p, %p)\n", iface
, cch
);
484 *cch
= MAX_CATEGORY_LENGTH
;
488 static HRESULT WINAPI
GameStatisticsImpl_GetMaxNameLength(
489 IGameStatistics
*iface
,
492 TRACE("(%p, %p)\n", iface
, cch
);
496 *cch
= MAX_NAME_LENGTH
;
500 static HRESULT WINAPI
GameStatisticsImpl_GetMaxValueLength(
501 IGameStatistics
*iface
,
504 TRACE("(%p, %p)\n", iface
, cch
);
508 *cch
= MAX_VALUE_LENGTH
;
512 static HRESULT WINAPI
GameStatisticsImpl_GetMaxCategories(
513 IGameStatistics
*iface
,
516 TRACE("(%p, %p)\n", iface
, pMax
);
520 *pMax
= MAX_CATEGORIES
;
524 static HRESULT WINAPI
GameStatisticsImpl_GetMaxStatsPerCategory(
525 IGameStatistics
*iface
,
528 TRACE("(%p, %p)\n", iface
, pMax
);
532 *pMax
= MAX_STATS_PER_CATEGORY
;
536 static HRESULT WINAPI
GameStatisticsImpl_SetCategoryTitle(
537 IGameStatistics
*iface
,
543 GameStatisticsImpl
*This
= impl_from_IGameStatistics(iface
);
545 TRACE("(%p, %d, %s)\n", This
, categoryIndex
, debugstr_w(title
));
547 if(!title
|| categoryIndex
>= MAX_CATEGORIES
)
550 dwLength
= lstrlenW(title
);
552 if(dwLength
> MAX_CATEGORY_LENGTH
)
555 dwLength
= MAX_CATEGORY_LENGTH
;
558 lstrcpynW(This
->stats
.categories
[categoryIndex
].sName
,
564 static HRESULT WINAPI
GameStatisticsImpl_GetCategoryTitle(
565 IGameStatistics
*iface
,
573 static HRESULT WINAPI
GameStatisticsImpl_GetStatistic(
574 IGameStatistics
*iface
,
584 static HRESULT WINAPI
GameStatisticsImpl_SetStatistic(
585 IGameStatistics
*iface
,
592 DWORD dwNameLen
, dwValueLen
;
593 GameStatisticsImpl
*This
= impl_from_IGameStatistics(iface
);
595 TRACE("(%p, %d, %d, %s, %s)\n", This
, categoryIndex
, statIndex
,
596 debugstr_w(name
), debugstr_w(value
));
601 if(categoryIndex
>= MAX_CATEGORIES
|| statIndex
>= MAX_STATS_PER_CATEGORY
)
604 dwNameLen
= lstrlenW(name
);
606 if(dwNameLen
> MAX_NAME_LENGTH
)
609 dwNameLen
= MAX_NAME_LENGTH
;
612 lstrcpynW(This
->stats
.categories
[categoryIndex
].stats
[statIndex
].sName
,
617 dwValueLen
= lstrlenW(value
);
619 if(dwValueLen
> MAX_VALUE_LENGTH
)
622 dwValueLen
= MAX_VALUE_LENGTH
;
625 lstrcpynW(This
->stats
.categories
[categoryIndex
].stats
[statIndex
].sValue
,
626 value
, dwValueLen
+1);
629 /* Windows allows to pass NULL as value */
630 This
->stats
.categories
[categoryIndex
].stats
[statIndex
].sValue
[0] = 0;
635 static HRESULT WINAPI
GameStatisticsImpl_Save(
636 IGameStatistics
*iface
,
639 GameStatisticsImpl
*This
= impl_from_IGameStatistics(iface
);
642 TRACE("(%p, %d)\n", This
, trackChanges
);
644 if(trackChanges
== TRUE
)
645 FIXME("tracking changes not yet implemented\n");
647 hr
= GAMEUX_updateStatisticsFile(&This
->stats
);
652 static HRESULT WINAPI
GameStatisticsImpl_SetLastPlayedCategory(
653 IGameStatistics
*iface
,
660 static HRESULT WINAPI
GameStatisticsImpl_GetLastPlayedCategory(
661 IGameStatistics
*iface
,
662 UINT
*pCategoryIndex
)
668 static const struct IGameStatisticsVtbl GameStatisticsImplVtbl
=
670 GameStatisticsImpl_QueryInterface
,
671 GameStatisticsImpl_AddRef
,
672 GameStatisticsImpl_Release
,
673 GameStatisticsImpl_GetMaxCategoryLength
,
674 GameStatisticsImpl_GetMaxNameLength
,
675 GameStatisticsImpl_GetMaxValueLength
,
676 GameStatisticsImpl_GetMaxCategories
,
677 GameStatisticsImpl_GetMaxStatsPerCategory
,
678 GameStatisticsImpl_SetCategoryTitle
,
679 GameStatisticsImpl_GetCategoryTitle
,
680 GameStatisticsImpl_GetStatistic
,
681 GameStatisticsImpl_SetStatistic
,
682 GameStatisticsImpl_Save
,
683 GameStatisticsImpl_SetLastPlayedCategory
,
684 GameStatisticsImpl_GetLastPlayedCategory
688 HRESULT
create_IGameStatistics(GameStatisticsImpl
** ppStats
)
690 TRACE("(%p)\n", ppStats
);
692 *ppStats
= HeapAlloc( GetProcessHeap(), 0, sizeof(**ppStats
));
694 return E_OUTOFMEMORY
;
696 (*ppStats
)->lpVtbl
= &GameStatisticsImplVtbl
;
699 TRACE("returning coclass: %p\n", *ppStats
);
703 /*******************************************************************************
704 * IGameStatisticsMgr implementation
706 typedef struct _GameStatisticsMgrImpl
708 const struct IGameStatisticsMgrVtbl
*lpVtbl
;
710 } GameStatisticsMgrImpl
;
712 static inline GameStatisticsMgrImpl
*impl_from_IGameStatisticsMgr( IGameStatisticsMgr
*iface
)
714 return (GameStatisticsMgrImpl
*)((char*)iface
- FIELD_OFFSET(GameStatisticsMgrImpl
, lpVtbl
));
718 static HRESULT WINAPI
GameStatisticsMgrImpl_QueryInterface(
719 IGameStatisticsMgr
*iface
,
723 GameStatisticsMgrImpl
*This
= impl_from_IGameStatisticsMgr( iface
);
725 TRACE("%p %s %p\n", This
, debugstr_guid( riid
), ppvObject
);
729 if(IsEqualGUID(riid
, &IID_IUnknown
) ||
730 IsEqualGUID(riid
, &IID_IGameStatisticsMgr
) )
736 FIXME("interface %s not implemented\n", debugstr_guid(riid
));
737 return E_NOINTERFACE
;
740 IGameStatisticsMgr_AddRef( iface
);
744 static ULONG WINAPI
GameStatisticsMgrImpl_AddRef(IGameStatisticsMgr
*iface
)
746 GameStatisticsMgrImpl
*This
= impl_from_IGameStatisticsMgr( iface
);
749 ref
= InterlockedIncrement(&This
->ref
);
751 TRACE("(%p): ref=%d\n", This
, ref
);
755 static ULONG WINAPI
GameStatisticsMgrImpl_Release(IGameStatisticsMgr
*iface
)
757 GameStatisticsMgrImpl
*This
= impl_from_IGameStatisticsMgr( iface
);
760 ref
= InterlockedDecrement(&This
->ref
);
761 TRACE("(%p): ref=%d\n", This
, ref
);
765 TRACE("freeing GameStatistics object\n");
766 HeapFree( GetProcessHeap(), 0, This
);
772 static HRESULT STDMETHODCALLTYPE
GameStatisticsMgrImpl_GetGameStatistics(
773 IGameStatisticsMgr
* iface
,
774 LPCWSTR GDFBinaryPath
,
775 GAMESTATS_OPEN_TYPE openType
,
776 GAMESTATS_OPEN_RESULT
*pOpenResult
,
777 IGameStatistics
**ppiStats
)
780 WCHAR lpApplicationId
[49];
781 GameStatisticsImpl
*statisticsImpl
;
783 TRACE("(%p, %s, 0x%x, %p, %p)\n", iface
, debugstr_w(GDFBinaryPath
), openType
, pOpenResult
, ppiStats
);
785 hr
= GAMEUX_getAppIdFromGDFPath(GDFBinaryPath
, lpApplicationId
);
788 hr
= create_IGameStatistics(&statisticsImpl
);
792 *ppiStats
= IGameStatistics_from_impl(statisticsImpl
);
793 hr
= GAMEUX_buildStatisticsFilePath(lpApplicationId
, statisticsImpl
->stats
.sStatsFile
);
798 FIXME("loading game statistics not yet implemented\n");
805 static HRESULT STDMETHODCALLTYPE
GameStatisticsMgrImpl_RemoveGameStatistics(
806 IGameStatisticsMgr
* iface
,
807 LPCWSTR GDFBinaryPath
)
810 WCHAR lpApplicationId
[49];
811 WCHAR sStatsFile
[MAX_PATH
];
813 TRACE("(%p, %s)\n", iface
, debugstr_w(GDFBinaryPath
));
815 hr
= GAMEUX_getAppIdFromGDFPath(GDFBinaryPath
, lpApplicationId
);
818 hr
= GAMEUX_buildStatisticsFilePath(lpApplicationId
, sStatsFile
);
821 hr
= (DeleteFileW(sStatsFile
)==TRUE
? S_OK
: HRESULT_FROM_WIN32(GetLastError()));
826 static const struct IGameStatisticsMgrVtbl GameStatisticsMgrImplVtbl
=
828 GameStatisticsMgrImpl_QueryInterface
,
829 GameStatisticsMgrImpl_AddRef
,
830 GameStatisticsMgrImpl_Release
,
831 GameStatisticsMgrImpl_GetGameStatistics
,
832 GameStatisticsMgrImpl_RemoveGameStatistics
,
835 HRESULT
GameStatistics_create(
839 GameStatisticsMgrImpl
*pGameStatistics
;
841 TRACE("(%p, %p)\n", pUnkOuter
, ppObj
);
843 pGameStatistics
= HeapAlloc( GetProcessHeap(), 0, sizeof (*pGameStatistics
) );
845 if( !pGameStatistics
)
846 return E_OUTOFMEMORY
;
848 pGameStatistics
->lpVtbl
= &GameStatisticsMgrImplVtbl
;
849 pGameStatistics
->ref
= 1;
851 *ppObj
= (IUnknown
*)(&pGameStatistics
->lpVtbl
);
853 TRACE("returning iface %p\n", *ppObj
);