| /* |
| * Implementation of CLSID_SystemDeviceEnum. |
| * Implements IMoniker for CLSID_CDeviceMoniker. |
| * Implements IPropertyBag. (internal) |
| * |
| * Copyright (C) Hidenori TAKESHIMA <hidenori@a2.ctktv.ne.jp> |
| * |
| * 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 "config.h" |
| |
| #include "windef.h" |
| #include "winbase.h" |
| #include "wingdi.h" |
| #include "winuser.h" |
| #include "winreg.h" |
| #include "winerror.h" |
| #include "objidl.h" |
| #include "oleidl.h" |
| #include "ocidl.h" |
| #include "oleauto.h" |
| #include "strmif.h" |
| #include "uuids.h" |
| |
| #include "wine/debug.h" |
| WINE_DEFAULT_DEBUG_CHANNEL(quartz); |
| |
| #include "quartz_private.h" |
| #include "devenum.h" |
| #include "enumunk.h" |
| #include "complist.h" |
| #include "regsvr.h" |
| |
| #ifndef NUMELEMS |
| #define NUMELEMS(elem) (sizeof(elem)/sizeof(elem[0])) |
| #endif /* NUMELEMS */ |
| |
| /*************************************************************************** |
| * |
| * new/delete for CLSID_SystemDeviceEnum |
| * |
| */ |
| |
| /* can I use offsetof safely? - FIXME? */ |
| static QUARTZ_IFEntry CSysDevEnum_IFEntries[] = |
| { |
| { &IID_ICreateDevEnum, offsetof(CSysDevEnum,createdevenum)-offsetof(CSysDevEnum,unk) }, |
| }; |
| |
| |
| static void QUARTZ_DestroySystemDeviceEnum(IUnknown* punk) |
| { |
| CSysDevEnum_THIS(punk,unk); |
| |
| CSysDevEnum_UninitICreateDevEnum( This ); |
| } |
| |
| HRESULT QUARTZ_CreateSystemDeviceEnum(IUnknown* punkOuter,void** ppobj) |
| { |
| CSysDevEnum* psde; |
| HRESULT hr; |
| |
| TRACE("(%p,%p)\n",punkOuter,ppobj); |
| |
| psde = (CSysDevEnum*)QUARTZ_AllocObj( sizeof(CSysDevEnum) ); |
| if ( psde == NULL ) |
| return E_OUTOFMEMORY; |
| |
| QUARTZ_IUnkInit( &psde->unk, punkOuter ); |
| |
| hr = CSysDevEnum_InitICreateDevEnum( psde ); |
| if ( FAILED(hr) ) |
| { |
| QUARTZ_FreeObj( psde ); |
| return hr; |
| } |
| |
| psde->unk.pEntries = CSysDevEnum_IFEntries; |
| psde->unk.dwEntries = sizeof(CSysDevEnum_IFEntries)/sizeof(CSysDevEnum_IFEntries[0]); |
| psde->unk.pOnFinalRelease = QUARTZ_DestroySystemDeviceEnum; |
| |
| *ppobj = (void*)(&psde->unk); |
| |
| return S_OK; |
| } |
| |
| |
| /*************************************************************************** |
| * |
| * CSysDevEnum::ICreateDevEnum |
| * |
| */ |
| |
| |
| static HRESULT WINAPI |
| ICreateDevEnum_fnQueryInterface(ICreateDevEnum* iface,REFIID riid,void** ppobj) |
| { |
| CSysDevEnum_THIS(iface,createdevenum); |
| |
| TRACE("(%p)->()\n",This); |
| |
| return IUnknown_QueryInterface(This->unk.punkControl,riid,ppobj); |
| } |
| |
| static ULONG WINAPI |
| ICreateDevEnum_fnAddRef(ICreateDevEnum* iface) |
| { |
| CSysDevEnum_THIS(iface,createdevenum); |
| |
| TRACE("(%p)->()\n",This); |
| |
| return IUnknown_AddRef(This->unk.punkControl); |
| } |
| |
| static ULONG WINAPI |
| ICreateDevEnum_fnRelease(ICreateDevEnum* iface) |
| { |
| CSysDevEnum_THIS(iface,createdevenum); |
| |
| TRACE("(%p)->()\n",This); |
| |
| return IUnknown_Release(This->unk.punkControl); |
| } |
| |
| static HRESULT WINAPI |
| ICreateDevEnum_fnCreateClassEnumerator(ICreateDevEnum* iface,REFCLSID rclsidDeviceClass,IEnumMoniker** ppobj, DWORD dwFlags) |
| { |
| CSysDevEnum_THIS(iface,createdevenum); |
| HRESULT hr; |
| HKEY hKey; |
| QUARTZ_CompList* pMonList; |
| IMoniker* pMon; |
| DWORD dwIndex; |
| LONG lr; |
| WCHAR wszPath[ 1024 ]; |
| DWORD dwLen; |
| DWORD dwNameMax; |
| DWORD cbName; |
| FILETIME ftLastWrite; |
| |
| TRACE("(%p)->(%s,%p,%08lx)\n",This, |
| debugstr_guid(rclsidDeviceClass),ppobj,dwFlags); |
| if ( dwFlags != 0 ) |
| { |
| FIXME("unknown flags %08lx\n",dwFlags); |
| return E_NOTIMPL; |
| } |
| |
| if ( ppobj == NULL ) |
| return E_POINTER; |
| *ppobj = NULL; |
| |
| hr = QUARTZ_CreateCLSIDPath( |
| wszPath, sizeof(wszPath)/sizeof(wszPath[0]) - 16, |
| rclsidDeviceClass, QUARTZ_wszInstance ); |
| if ( FAILED(hr) ) |
| return hr; |
| |
| lr = RegOpenKeyExW( HKEY_CLASSES_ROOT, wszPath, |
| 0, KEY_READ, &hKey ); |
| if ( lr != ERROR_SUCCESS ) |
| { |
| TRACE("cannot open %s\n",debugstr_w(wszPath)); |
| if ( lr == ERROR_FILE_NOT_FOUND || |
| lr == ERROR_PATH_NOT_FOUND ) |
| return S_FALSE; |
| return E_FAIL; |
| } |
| |
| dwLen = lstrlenW(wszPath); |
| wszPath[dwLen++] = '\\'; wszPath[dwLen] = 0; |
| dwNameMax = sizeof(wszPath)/sizeof(wszPath[0]) - dwLen - 8; |
| |
| pMonList = QUARTZ_CompList_Alloc(); |
| if ( pMonList == NULL ) |
| { |
| hr = E_OUTOFMEMORY; |
| goto err; |
| } |
| |
| /* enumerate all subkeys. */ |
| dwIndex = 0; |
| while ( 1 ) |
| { |
| cbName = dwNameMax; |
| lr = RegEnumKeyExW( |
| hKey, dwIndex, &wszPath[dwLen], &cbName, |
| NULL, NULL, NULL, &ftLastWrite ); |
| if ( lr == ERROR_NO_MORE_ITEMS ) |
| break; |
| if ( lr != ERROR_SUCCESS ) |
| { |
| TRACE("RegEnumKeyEx returns %08lx\n",lr); |
| hr = E_FAIL; |
| goto err; |
| } |
| |
| hr = QUARTZ_CreateDeviceMoniker( |
| HKEY_CLASSES_ROOT, wszPath, &pMon ); |
| if ( FAILED(hr) ) |
| goto err; |
| |
| hr = QUARTZ_CompList_AddComp( |
| pMonList, (IUnknown*)pMon, NULL, 0 ); |
| IMoniker_Release( pMon ); |
| |
| if ( FAILED(hr) ) |
| goto err; |
| |
| dwIndex ++; |
| } |
| |
| /* create an enumerator. */ |
| hr = QUARTZ_CreateEnumUnknown( |
| &IID_IEnumMoniker, (void**)ppobj, pMonList ); |
| if ( FAILED(hr) ) |
| goto err; |
| |
| hr = S_OK; |
| err: |
| if ( pMonList != NULL ) |
| QUARTZ_CompList_Free( pMonList ); |
| RegCloseKey( hKey ); |
| |
| return hr; |
| } |
| |
| static ICOM_VTABLE(ICreateDevEnum) icreatedevenum = |
| { |
| ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE |
| /* IUnknown fields */ |
| ICreateDevEnum_fnQueryInterface, |
| ICreateDevEnum_fnAddRef, |
| ICreateDevEnum_fnRelease, |
| /* ICreateDevEnum fields */ |
| ICreateDevEnum_fnCreateClassEnumerator, |
| }; |
| |
| HRESULT CSysDevEnum_InitICreateDevEnum( CSysDevEnum* psde ) |
| { |
| TRACE("(%p)\n",psde); |
| ICOM_VTBL(&psde->createdevenum) = &icreatedevenum; |
| |
| return NOERROR; |
| } |
| |
| void CSysDevEnum_UninitICreateDevEnum( CSysDevEnum* psde ) |
| { |
| TRACE("(%p)\n",psde); |
| } |
| |
| |
| /*************************************************************************** |
| * |
| * CDeviceMoniker::IMoniker |
| * |
| */ |
| |
| static HRESULT WINAPI |
| IMoniker_fnQueryInterface(IMoniker* iface,REFIID riid,void** ppobj) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| TRACE("(%p)->()\n",This); |
| |
| return IUnknown_QueryInterface(This->unk.punkControl,riid,ppobj); |
| } |
| |
| static ULONG WINAPI |
| IMoniker_fnAddRef(IMoniker* iface) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| TRACE("(%p)->()\n",This); |
| |
| return IUnknown_AddRef(This->unk.punkControl); |
| } |
| |
| static ULONG WINAPI |
| IMoniker_fnRelease(IMoniker* iface) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| TRACE("(%p)->()\n",This); |
| |
| return IUnknown_Release(This->unk.punkControl); |
| } |
| |
| static HRESULT WINAPI IMoniker_fnGetClassID(IMoniker* iface, CLSID *pClassID) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| TRACE("(%p)->()\n",This); |
| |
| if ( pClassID == NULL ) |
| return E_POINTER; |
| memcpy( pClassID, &CLSID_CDeviceMoniker, sizeof(CLSID) ); |
| |
| return NOERROR; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnIsDirty(IMoniker* iface) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| FIXME("(%p)->() stub!\n",This); |
| |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnLoad(IMoniker* iface, IStream* pStm) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| FIXME("(%p)->() stub!\n",This); |
| |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnSave(IMoniker* iface, IStream* pStm, BOOL fClearDirty) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| FIXME("(%p)->() stub!\n",This); |
| |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnGetSizeMax(IMoniker* iface, ULARGE_INTEGER* pcbSize) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| FIXME("(%p)->() stub!\n",This); |
| |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnBindToObject(IMoniker* iface,IBindCtx* pbc, IMoniker* pmkToLeft, REFIID riid, VOID** ppvResult) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| HRESULT hr; |
| IPropertyBag* pPropBag; |
| IPersistPropertyBag* pPersistPropBag; |
| VARIANT vClsid; |
| CLSID clsid; |
| |
| TRACE("(%p)->(%p,%p,%s,%p)\n",This, |
| pbc,pmkToLeft,debugstr_guid(riid),ppvResult); |
| if ( pbc != NULL ) |
| { |
| FIXME("IBindCtx* pbc != NULL not implemented.\n"); |
| return E_FAIL; |
| } |
| if ( pmkToLeft != NULL ) |
| { |
| FIXME("IMoniker* pmkToLeft != NULL not implemented.\n"); |
| return E_FAIL; |
| } |
| if ( ppvResult == NULL ) |
| return E_POINTER; |
| |
| hr = QUARTZ_CreateRegPropertyBag( |
| This->m_hkRoot, This->m_pwszPath, &pPropBag ); |
| if ( FAILED(hr) ) |
| return hr; |
| |
| vClsid.n1.n2.vt = VT_BSTR; |
| hr = IPropertyBag_Read( |
| pPropBag, QUARTZ_wszCLSID, &vClsid, NULL ); |
| IPropertyBag_Release( pPropBag ); |
| if ( FAILED(hr) ) |
| return hr; |
| |
| hr = CLSIDFromString( vClsid.n1.n2.n3.bstrVal, &clsid ); |
| SysFreeString(vClsid.n1.n2.n3.bstrVal); |
| if ( FAILED(hr) ) |
| return hr; |
| |
| hr = CoCreateInstance( |
| &clsid, NULL, CLSCTX_INPROC_SERVER, riid, ppvResult ); |
| if ( FAILED(hr) ) |
| return hr; |
| |
| hr = IUnknown_QueryInterface((IUnknown*)*ppvResult,&IID_IPersistPropertyBag,(void**)&pPersistPropBag); |
| if ( hr == E_NOINTERFACE ) |
| { |
| hr = S_OK; |
| } |
| else |
| if ( SUCCEEDED(hr) ) |
| { |
| hr = QUARTZ_CreateRegPropertyBag( |
| This->m_hkRoot, This->m_pwszPath, &pPropBag ); |
| if ( SUCCEEDED(hr) ) |
| { |
| hr = IPersistPropertyBag_Load(pPersistPropBag,pPropBag,NULL); |
| IPropertyBag_Release( pPropBag ); |
| } |
| IPersistPropertyBag_Release(pPersistPropBag); |
| } |
| |
| if ( FAILED(hr) ) |
| { |
| IUnknown_Release((IUnknown*)*ppvResult); |
| *ppvResult = NULL; |
| } |
| |
| TRACE( "hr = %08lx\n", hr ); |
| |
| return hr; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnBindToStorage(IMoniker* iface,IBindCtx* pbc, IMoniker* pmkToLeft, REFIID riid, VOID** ppvResult) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| HRESULT hr; |
| |
| TRACE("(%p)->(%p,%p,%s,%p)\n",This, |
| pbc,pmkToLeft,debugstr_guid(riid),ppvResult); |
| if ( pbc != NULL ) |
| { |
| FIXME("IBindCtx* pbc != NULL not implemented.\n"); |
| return E_FAIL; |
| } |
| if ( pmkToLeft != NULL ) |
| { |
| FIXME("IMoniker* pmkToLeft != NULL not implemented.\n"); |
| return E_FAIL; |
| } |
| if ( ppvResult == NULL ) |
| return E_POINTER; |
| |
| hr = E_NOINTERFACE; |
| if ( IsEqualGUID(riid,&IID_IUnknown) || |
| IsEqualGUID(riid,&IID_IPropertyBag) ) |
| { |
| hr = QUARTZ_CreateRegPropertyBag( |
| This->m_hkRoot, This->m_pwszPath, |
| (IPropertyBag**)ppvResult ); |
| } |
| |
| return hr; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnReduce(IMoniker* iface,IBindCtx* pbc, DWORD dwReduceHowFar,IMoniker** ppmkToLeft, IMoniker** ppmkReduced) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| TRACE("(%p)->()\n",This); |
| |
| if ( ppmkReduced == NULL ) |
| return E_POINTER; |
| |
| *ppmkReduced = iface; IMoniker_AddRef(iface); |
| |
| return MK_S_REDUCED_TO_SELF; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnComposeWith(IMoniker* iface,IMoniker* pmkRight,BOOL fOnlyIfNotGeneric, IMoniker** ppmkComposite) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| FIXME("(%p)->() stub!\n",This); |
| |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnEnum(IMoniker* iface,BOOL fForward, IEnumMoniker** ppenumMoniker) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| TRACE("(%p)->()\n",This); |
| |
| if ( ppenumMoniker == NULL ) |
| return E_POINTER; |
| |
| *ppenumMoniker = NULL; |
| return NOERROR; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnIsEqual(IMoniker* iface,IMoniker* pmkOtherMoniker) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| FIXME("(%p)->() stub!\n",This); |
| |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnHash(IMoniker* iface,DWORD* pdwHash) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| FIXME("(%p)->() stub!\n",This); |
| |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnIsRunning(IMoniker* iface,IBindCtx* pbc, IMoniker* pmkToLeft, IMoniker* pmkNewlyRunning) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| FIXME("(%p)->() stub!\n",This); |
| |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnGetTimeOfLastChange(IMoniker* iface, IBindCtx* pbc, IMoniker* pmkToLeft, FILETIME* pCompositeTime) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| FIXME("(%p)->() stub!\n",This); |
| |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnInverse(IMoniker* iface,IMoniker** ppmk) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| FIXME("(%p)->() stub!\n",This); |
| |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnCommonPrefixWith(IMoniker* iface,IMoniker* pmkOther, IMoniker** ppmkPrefix) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| FIXME("(%p)->() stub!\n",This); |
| |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnRelativePathTo(IMoniker* iface,IMoniker* pmOther, IMoniker** ppmkRelPath) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| FIXME("(%p)->() stub!\n",This); |
| |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnGetDisplayName(IMoniker* iface,IBindCtx* pbc, IMoniker* pmkToLeft, LPOLESTR *ppszDisplayName) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| FIXME("(%p)->() stub!\n",This); |
| |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnParseDisplayName(IMoniker* iface,IBindCtx* pbc, IMoniker* pmkToLeft, LPOLESTR pszDisplayName, ULONG* pchEaten, IMoniker** ppmkOut) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| FIXME("(%p)->() stub!\n",This); |
| |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI IMoniker_fnIsSystemMoniker(IMoniker* iface,DWORD* pdwMksys) |
| { |
| CDeviceMoniker_THIS(iface,moniker); |
| |
| TRACE("(%p)->()\n",This); |
| if ( pdwMksys == NULL ) |
| return E_POINTER; |
| |
| *pdwMksys = MKSYS_NONE; |
| return S_FALSE; |
| } |
| |
| |
| static ICOM_VTABLE(IMoniker) imoniker = |
| { |
| ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE |
| /* IUnknown fields */ |
| IMoniker_fnQueryInterface, |
| IMoniker_fnAddRef, |
| IMoniker_fnRelease, |
| /* IPersist fields */ |
| IMoniker_fnGetClassID, |
| /* IPersistStream fields */ |
| IMoniker_fnIsDirty, |
| IMoniker_fnLoad, |
| IMoniker_fnSave, |
| IMoniker_fnGetSizeMax, |
| /* IMoniker fields */ |
| IMoniker_fnBindToObject, |
| IMoniker_fnBindToStorage, |
| IMoniker_fnReduce, |
| IMoniker_fnComposeWith, |
| IMoniker_fnEnum, |
| IMoniker_fnIsEqual, |
| IMoniker_fnHash, |
| IMoniker_fnIsRunning, |
| IMoniker_fnGetTimeOfLastChange, |
| IMoniker_fnInverse, |
| IMoniker_fnCommonPrefixWith, |
| IMoniker_fnRelativePathTo, |
| IMoniker_fnGetDisplayName, |
| IMoniker_fnParseDisplayName, |
| IMoniker_fnIsSystemMoniker, |
| }; |
| |
| |
| static HRESULT CDeviceMoniker_InitIMoniker( |
| CDeviceMoniker* pdm, HKEY hkRoot, LPCWSTR lpKeyPath ) |
| { |
| DWORD dwLen; |
| |
| ICOM_VTBL(&pdm->moniker) = &imoniker; |
| pdm->m_hkRoot = hkRoot; |
| pdm->m_pwszPath = NULL; |
| |
| dwLen = sizeof(WCHAR)*(lstrlenW(lpKeyPath)+1); |
| pdm->m_pwszPath = (WCHAR*)QUARTZ_AllocMem( dwLen ); |
| if ( pdm->m_pwszPath == NULL ) |
| return E_OUTOFMEMORY; |
| memcpy( pdm->m_pwszPath, lpKeyPath, dwLen ); |
| |
| return NOERROR; |
| } |
| |
| static void CDeviceMoniker_UninitIMoniker( |
| CDeviceMoniker* pdm ) |
| { |
| if ( pdm->m_pwszPath != NULL ) |
| QUARTZ_FreeMem( pdm->m_pwszPath ); |
| } |
| |
| /*************************************************************************** |
| * |
| * new/delete for CDeviceMoniker |
| * |
| */ |
| |
| static void QUARTZ_DestroyDeviceMoniker(IUnknown* punk) |
| { |
| CDeviceMoniker_THIS(punk,unk); |
| |
| CDeviceMoniker_UninitIMoniker( This ); |
| } |
| |
| /* can I use offsetof safely? - FIXME? */ |
| static QUARTZ_IFEntry CDeviceMoniker_IFEntries[] = |
| { |
| { &IID_IPersist, offsetof(CDeviceMoniker,moniker)-offsetof(CDeviceMoniker,unk) }, |
| { &IID_IPersistStream, offsetof(CDeviceMoniker,moniker)-offsetof(CDeviceMoniker,unk) }, |
| { &IID_IMoniker, offsetof(CDeviceMoniker,moniker)-offsetof(CDeviceMoniker,unk) }, |
| }; |
| |
| HRESULT QUARTZ_CreateDeviceMoniker( |
| HKEY hkRoot, LPCWSTR lpKeyPath, |
| IMoniker** ppMoniker ) |
| { |
| CDeviceMoniker* pdm; |
| HRESULT hr; |
| |
| TRACE("(%08x,%s,%p)\n",hkRoot,debugstr_w(lpKeyPath),ppMoniker ); |
| |
| pdm = (CDeviceMoniker*)QUARTZ_AllocObj( sizeof(CDeviceMoniker) ); |
| if ( pdm == NULL ) |
| return E_OUTOFMEMORY; |
| |
| QUARTZ_IUnkInit( &pdm->unk, NULL ); |
| hr = CDeviceMoniker_InitIMoniker( pdm, hkRoot, lpKeyPath ); |
| if ( FAILED(hr) ) |
| { |
| QUARTZ_FreeObj( pdm ); |
| return hr; |
| } |
| |
| pdm->unk.pEntries = CDeviceMoniker_IFEntries; |
| pdm->unk.dwEntries = sizeof(CDeviceMoniker_IFEntries)/sizeof(CDeviceMoniker_IFEntries[0]); |
| pdm->unk.pOnFinalRelease = &QUARTZ_DestroyDeviceMoniker; |
| |
| *ppMoniker = (IMoniker*)(&pdm->moniker); |
| |
| return S_OK; |
| } |
| |
| |
| /*************************************************************************** |
| * |
| * CRegPropertyBag::IPropertyBag |
| * |
| */ |
| |
| static HRESULT WINAPI |
| IPropertyBag_fnQueryInterface(IPropertyBag* iface,REFIID riid,void** ppobj) |
| { |
| CRegPropertyBag_THIS(iface,propbag); |
| |
| TRACE("(%p)->()\n",This); |
| |
| return IUnknown_QueryInterface(This->unk.punkControl,riid,ppobj); |
| } |
| |
| static ULONG WINAPI |
| IPropertyBag_fnAddRef(IPropertyBag* iface) |
| { |
| CRegPropertyBag_THIS(iface,propbag); |
| |
| TRACE("(%p)->()\n",This); |
| |
| return IUnknown_AddRef(This->unk.punkControl); |
| } |
| |
| static ULONG WINAPI |
| IPropertyBag_fnRelease(IPropertyBag* iface) |
| { |
| CRegPropertyBag_THIS(iface,propbag); |
| |
| TRACE("(%p)->()\n",This); |
| |
| return IUnknown_Release(This->unk.punkControl); |
| } |
| |
| static HRESULT WINAPI |
| IPropertyBag_fnRead(IPropertyBag* iface,LPCOLESTR lpszPropName,VARIANT* pVar,IErrorLog* pLog) |
| { |
| CRegPropertyBag_THIS(iface,propbag); |
| HRESULT hr; |
| LONG lr; |
| DWORD dwSize; |
| DWORD dwValueType; |
| DWORD dwDWordValue; |
| SAFEARRAYBOUND sab; |
| SAFEARRAY* pArray; |
| |
| TRACE("(%p)->(%s,%p,%p)\n",This, |
| debugstr_w(lpszPropName),pVar,pLog); |
| |
| if ( lpszPropName == NULL || pVar == NULL ) |
| return E_POINTER; |
| |
| dwSize = 0; |
| lr = RegQueryValueExW( |
| This->m_hKey, lpszPropName, NULL, |
| &dwValueType, NULL, &dwSize ); |
| if ( lr != ERROR_SUCCESS ) |
| { |
| WARN( "RegQueryValueExW failed.\n" ); |
| return E_INVALIDARG; |
| } |
| |
| switch ( dwValueType ) |
| { |
| case REG_SZ: |
| TRACE( "REG_SZ / length = %lu\n", dwSize ); |
| if ( pVar->n1.n2.vt == VT_EMPTY ) |
| pVar->n1.n2.vt = VT_BSTR; |
| if ( pVar->n1.n2.vt != VT_BSTR ) |
| { |
| FIXME( "type of VARIANT is not BSTR.\n" ); |
| return E_FAIL; |
| } |
| |
| pVar->n1.n2.n3.bstrVal = SysAllocStringByteLen( |
| NULL, dwSize ); |
| if ( pVar->n1.n2.n3.bstrVal == NULL ) |
| { |
| WARN( "out of memory.\n" ); |
| return E_OUTOFMEMORY; |
| } |
| lr = RegQueryValueExW( |
| This->m_hKey, lpszPropName, NULL, |
| &dwValueType, |
| (BYTE*)pVar->n1.n2.n3.bstrVal, &dwSize ); |
| if ( lr != ERROR_SUCCESS ) |
| { |
| WARN( "failed to query value\n" ); |
| SysFreeString(pVar->n1.n2.n3.bstrVal); |
| return E_FAIL; |
| } |
| TRACE( "value is BSTR; %s\n", debugstr_w(pVar->n1.n2.n3.bstrVal) ); |
| break; |
| case REG_BINARY: |
| TRACE( "REG_BINARY / length = %lu\n", dwSize ); |
| if ( pVar->n1.n2.vt == VT_EMPTY ) |
| pVar->n1.n2.vt = VT_ARRAY|VT_UI1; |
| if ( pVar->n1.n2.vt != (VT_ARRAY|VT_UI1) ) |
| { |
| FIXME( "type of VARIANT is not VT_ARRAY|VT_UI1.\n" ); |
| return E_FAIL; |
| } |
| sab.lLbound = 0; |
| sab.cElements = dwSize; |
| pArray = SafeArrayCreate( VT_UI1, 1, &sab ); |
| if ( pArray == NULL ) |
| return E_OUTOFMEMORY; |
| hr = SafeArrayLock( pArray ); |
| if ( FAILED(hr) ) |
| { |
| WARN( "safe array can't be locked\n" ); |
| SafeArrayDestroy( pArray ); |
| return hr; |
| } |
| lr = RegQueryValueExW( |
| This->m_hKey, lpszPropName, NULL, |
| &dwValueType, |
| (BYTE*)pArray->pvData, &dwSize ); |
| SafeArrayUnlock( pArray ); |
| if ( lr != ERROR_SUCCESS ) |
| { |
| WARN( "failed to query value\n" ); |
| SafeArrayDestroy( pArray ); |
| return E_FAIL; |
| } |
| pVar->n1.n2.n3.parray = pArray; |
| TRACE( "value is SAFEARRAY - array of BYTE; \n" ); |
| break; |
| case REG_DWORD: |
| TRACE( "REG_DWORD / length = %lu\n", dwSize ); |
| if ( dwSize != sizeof(DWORD) ) |
| { |
| WARN( "The length of REG_DWORD value is not sizeof(DWORD).\n" ); |
| return E_FAIL; |
| } |
| if ( pVar->n1.n2.vt == VT_EMPTY ) |
| pVar->n1.n2.vt = VT_I4; |
| if ( pVar->n1.n2.vt != VT_I4 ) |
| { |
| FIXME( "type of VARIANT is not VT_I4.\n" ); |
| return E_FAIL; |
| } |
| lr = RegQueryValueExW( |
| This->m_hKey, lpszPropName, NULL, |
| &dwValueType, |
| (BYTE*)(&dwDWordValue), &dwSize ); |
| if ( lr != ERROR_SUCCESS ) |
| { |
| WARN( "failed to query value\n" ); |
| return E_FAIL; |
| } |
| pVar->n1.n2.n3.lVal = dwDWordValue; |
| TRACE( "value is DWORD; %08lx\n", dwDWordValue ); |
| break; |
| default: |
| FIXME("(%p)->(%s,%p,%p) - unsupported value type.\n",This, |
| debugstr_w(lpszPropName),pVar,pLog); |
| return E_FAIL; |
| } |
| |
| TRACE( "returned successfully.\n" ); |
| return NOERROR; |
| } |
| |
| static HRESULT WINAPI |
| IPropertyBag_fnWrite(IPropertyBag* iface,LPCOLESTR lpszPropName,VARIANT* pVar) |
| { |
| CRegPropertyBag_THIS(iface,propbag); |
| HRESULT hr; |
| LONG lr; |
| DWORD dwDWordValue; |
| SAFEARRAY* pArray; |
| |
| TRACE("(%p)->(%s,%p)\n",This, |
| debugstr_w(lpszPropName),pVar); |
| |
| if ( lpszPropName == NULL || pVar == NULL ) |
| return E_POINTER; |
| |
| switch ( pVar->n1.n2.vt ) |
| { |
| case VT_I4: /* REG_DWORD */ |
| dwDWordValue = pVar->n1.n2.n3.lVal; |
| lr = RegSetValueExW( |
| This->m_hKey, lpszPropName, 0, |
| REG_DWORD, |
| (const BYTE*)(&dwDWordValue), sizeof(DWORD) ); |
| if ( lr != ERROR_SUCCESS ) |
| { |
| WARN( "failed to set value\n" ); |
| return E_FAIL; |
| } |
| break; |
| case VT_BSTR: /* REG_SZ */ |
| lr = RegSetValueExW( |
| This->m_hKey, lpszPropName, 0, |
| REG_SZ, |
| (const BYTE*)(pVar->n1.n2.n3.bstrVal), |
| SysStringByteLen( pVar->n1.n2.n3.bstrVal ) ); |
| if ( lr != ERROR_SUCCESS ) |
| { |
| WARN( "failed to set value\n" ); |
| return E_FAIL; |
| } |
| break; |
| case (VT_ARRAY|VT_UI1): /* REG_BINARY */ |
| pArray = pVar->n1.n2.n3.parray; |
| if ( pArray->cDims != 1 || pArray->cbElements != 1 || |
| pArray->rgsabound[0].lLbound != 0 ) |
| { |
| WARN( "invalid array\n" ); |
| return E_INVALIDARG; |
| } |
| hr = SafeArrayLock( pArray ); |
| if ( FAILED(hr) ) |
| { |
| WARN( "safe array can't be locked\n" ); |
| return hr; |
| } |
| lr = RegSetValueExW( |
| This->m_hKey, lpszPropName, 0, |
| REG_BINARY, |
| (const BYTE*)pArray->pvData, |
| pArray->rgsabound[0].cElements ); |
| SafeArrayUnlock( pArray ); |
| if ( lr != ERROR_SUCCESS ) |
| { |
| WARN( "failed to set value\n" ); |
| return E_FAIL; |
| } |
| break; |
| default: |
| FIXME("(%p)->(%s,%p) invalid/unsupported VARIANT type %04x\n",This, |
| debugstr_w(lpszPropName),pVar,pVar->n1.n2.vt); |
| return E_INVALIDARG; |
| } |
| |
| TRACE( "returned successfully.\n" ); |
| return NOERROR; |
| } |
| |
| |
| |
| |
| static ICOM_VTABLE(IPropertyBag) ipropbag = |
| { |
| ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE |
| /* IUnknown fields */ |
| IPropertyBag_fnQueryInterface, |
| IPropertyBag_fnAddRef, |
| IPropertyBag_fnRelease, |
| /* IPropertyBag fields */ |
| IPropertyBag_fnRead, |
| IPropertyBag_fnWrite, |
| }; |
| |
| static HRESULT CRegPropertyBag_InitIPropertyBag( |
| CRegPropertyBag* prpb, HKEY hkRoot, LPCWSTR lpKeyPath ) |
| { |
| WCHAR wszREG_SZ[ NUMELEMS(QUARTZ_wszREG_SZ) ]; |
| DWORD dwDisp; |
| |
| ICOM_VTBL(&prpb->propbag) = &ipropbag; |
| |
| memcpy(wszREG_SZ,QUARTZ_wszREG_SZ,sizeof(QUARTZ_wszREG_SZ) ); |
| |
| if ( RegCreateKeyExW( |
| hkRoot, lpKeyPath, 0, wszREG_SZ, |
| REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, |
| &prpb->m_hKey, &dwDisp ) != ERROR_SUCCESS ) |
| return E_FAIL; |
| |
| return NOERROR; |
| } |
| |
| static void CRegPropertyBag_UninitIPropertyBag( |
| CRegPropertyBag* prpb ) |
| { |
| RegCloseKey( prpb->m_hKey ); |
| } |
| |
| |
| /*************************************************************************** |
| * |
| * new/delete for CRegPropertyBag |
| * |
| */ |
| |
| static void QUARTZ_DestroyRegPropertyBag(IUnknown* punk) |
| { |
| CRegPropertyBag_THIS(punk,unk); |
| |
| CRegPropertyBag_UninitIPropertyBag(This); |
| } |
| |
| |
| /* can I use offsetof safely? - FIXME? */ |
| static QUARTZ_IFEntry CRegPropertyBag_IFEntries[] = |
| { |
| { &IID_IPropertyBag, offsetof(CRegPropertyBag,propbag)-offsetof(CRegPropertyBag,unk) }, |
| }; |
| |
| HRESULT QUARTZ_CreateRegPropertyBag( |
| HKEY hkRoot, LPCWSTR lpKeyPath, |
| IPropertyBag** ppPropBag ) |
| { |
| CRegPropertyBag* prpb; |
| HRESULT hr; |
| |
| TRACE("(%08x,%s,%p)\n",hkRoot,debugstr_w(lpKeyPath),ppPropBag ); |
| |
| prpb = (CRegPropertyBag*)QUARTZ_AllocObj( sizeof(CRegPropertyBag) ); |
| if ( prpb == NULL ) |
| return E_OUTOFMEMORY; |
| |
| QUARTZ_IUnkInit( &prpb->unk, NULL ); |
| hr = CRegPropertyBag_InitIPropertyBag( prpb, hkRoot, lpKeyPath ); |
| if ( FAILED(hr) ) |
| { |
| QUARTZ_FreeObj( prpb ); |
| return hr; |
| } |
| |
| prpb->unk.pEntries = CRegPropertyBag_IFEntries; |
| prpb->unk.dwEntries = sizeof(CRegPropertyBag_IFEntries)/sizeof(CRegPropertyBag_IFEntries[0]); |
| prpb->unk.pOnFinalRelease = &QUARTZ_DestroyRegPropertyBag; |
| |
| *ppPropBag = (IPropertyBag*)(&prpb->propbag); |
| |
| return S_OK; |
| } |
| |
| /*************************************************************************** |
| * |
| * Helper for registering filters. |
| * |
| */ |
| |
| HRESULT QUARTZ_GetFilterRegPath( |
| WCHAR** ppwszPath, /* [OUT] path from HKEY_CLASSES_ROOT */ |
| const CLSID* pguidFilterCategory, /* [IN] Category */ |
| const CLSID* pclsid, /* [IN] CLSID of this filter */ |
| LPCWSTR lpInstance ) /* [IN] instance */ |
| { |
| HRESULT hr; |
| WCHAR szKey[ 1024 ]; |
| WCHAR szFilterPath[ 512 ]; |
| WCHAR szCLSID[ 256 ]; |
| int buflen; |
| |
| TRACE("(%p,%s,%s,%s)\n",ppwszPath,debugstr_guid(pguidFilterCategory),debugstr_guid(pclsid),debugstr_w(lpInstance) ); |
| |
| *ppwszPath = NULL; |
| |
| QUARTZ_GUIDtoString( szCLSID, pclsid ); |
| lstrcpyW( szFilterPath, QUARTZ_wszInstance ); |
| QUARTZ_CatPathSepW( szFilterPath ); |
| if ( lpInstance != NULL ) |
| { |
| if ( lstrlenW(lpInstance) >= 256 ) |
| return E_INVALIDARG; |
| lstrcatW( szFilterPath, lpInstance ); |
| } |
| else |
| { |
| lstrcatW( szFilterPath, szCLSID ); |
| } |
| |
| hr = QUARTZ_CreateCLSIDPath( |
| szKey, NUMELEMS(szKey), |
| pguidFilterCategory, szFilterPath ); |
| if ( FAILED(hr) ) |
| return hr; |
| |
| buflen = sizeof(WCHAR)*(lstrlenW(szKey)+1); |
| *ppwszPath = QUARTZ_AllocMem( buflen ); |
| if ( *ppwszPath == NULL ) |
| return E_OUTOFMEMORY; |
| memcpy( *ppwszPath, szKey, buflen ); |
| return S_OK; |
| } |
| |
| HRESULT QUARTZ_RegisterFilterToMoniker( |
| IMoniker* pMoniker, /* [IN] Moniker */ |
| const CLSID* pclsid, /* [IN] CLSID of this filter */ |
| LPCWSTR lpFriendlyName, /* [IN] friendly name */ |
| const BYTE* pbFilterData, /* [IN] filter data */ |
| DWORD cbFilterData ) /* [IN] size of the filter data */ |
| { |
| IPropertyBag* pPropBag = NULL; |
| WCHAR wszClsid[128]; |
| VARIANT var; |
| HRESULT hr; |
| SAFEARRAYBOUND sab; |
| SAFEARRAY* pArray = NULL; |
| |
| TRACE("(%p,%s,%s,%p,%08lu)\n",pMoniker,debugstr_guid(pclsid),debugstr_w(lpFriendlyName),pbFilterData,cbFilterData); |
| |
| hr = IMoniker_BindToStorage(pMoniker,NULL,NULL,&IID_IPropertyBag,(void**)&pPropBag); |
| if ( FAILED(hr) ) |
| goto err; |
| QUARTZ_GUIDtoString( wszClsid, pclsid ); |
| var.n1.n2.vt = VT_BSTR; |
| var.n1.n2.n3.bstrVal = SysAllocString(wszClsid); |
| if ( var.n1.n2.n3.bstrVal == NULL ) |
| { |
| hr = E_OUTOFMEMORY; |
| goto err; |
| } |
| hr = IPropertyBag_Write(pPropBag,QUARTZ_wszCLSID,&var); |
| SysFreeString(var.n1.n2.n3.bstrVal); |
| if ( FAILED(hr) ) |
| goto err; |
| |
| var.n1.n2.vt = VT_BSTR; |
| var.n1.n2.n3.bstrVal = SysAllocString(lpFriendlyName); |
| if ( var.n1.n2.n3.bstrVal == NULL ) |
| { |
| hr = E_OUTOFMEMORY; |
| goto err; |
| } |
| hr = IPropertyBag_Write(pPropBag,QUARTZ_wszFriendlyName,&var); |
| SysFreeString(var.n1.n2.n3.bstrVal); |
| if ( FAILED(hr) ) |
| goto err; |
| |
| if ( pbFilterData != NULL ) |
| { |
| var.n1.n2.vt = VT_ARRAY|VT_UI1; |
| sab.lLbound = 0; |
| sab.cElements = cbFilterData; |
| pArray = SafeArrayCreate( VT_UI1, 1, &sab ); |
| if ( pArray == NULL ) |
| { |
| hr = E_OUTOFMEMORY; |
| goto err; |
| } |
| hr = SafeArrayLock( pArray ); |
| if ( FAILED(hr) ) |
| goto err; |
| var.n1.n2.n3.parray = pArray; |
| memcpy( pArray->pvData, pbFilterData, cbFilterData ); |
| hr = IPropertyBag_Write(pPropBag,QUARTZ_wszFilterData,&var); |
| SafeArrayUnlock( pArray ); |
| if ( FAILED(hr) ) |
| goto err; |
| } |
| |
| hr = NOERROR; |
| err: |
| if ( pArray != NULL ) |
| SafeArrayDestroy( pArray ); |
| if ( pPropBag != NULL ) |
| IPropertyBag_Release(pPropBag); |
| |
| return hr; |
| } |
| |
| HRESULT QUARTZ_RegDeleteKey( HKEY hkRoot, LPCWSTR lpKeyPath ) |
| { |
| LONG lr; |
| HRESULT hr; |
| HKEY hKey; |
| DWORD dwIndex; |
| DWORD cbName; |
| FILETIME ftLastWrite; |
| DWORD dwType; |
| WCHAR wszPath[ 512 ]; |
| |
| TRACE("(%08x,%s)\n",hkRoot,debugstr_w(lpKeyPath)); |
| |
| lr = RegOpenKeyExW( hkRoot, lpKeyPath, 0, KEY_ALL_ACCESS, &hKey ); |
| if ( lr == ERROR_SUCCESS ) |
| { |
| dwIndex = 0; |
| while ( 1 ) |
| { |
| cbName = NUMELEMS(wszPath); |
| lr = RegEnumKeyExW( |
| hKey, dwIndex, wszPath, &cbName, |
| NULL, NULL, NULL, &ftLastWrite ); |
| if ( lr != ERROR_SUCCESS ) |
| break; |
| hr = QUARTZ_RegDeleteKey( hKey, wszPath ); |
| if ( FAILED(hr) ) |
| return hr; |
| if ( hr != S_OK ) |
| dwIndex ++; |
| } |
| while ( 1 ) |
| { |
| cbName = NUMELEMS(wszPath); |
| lr = RegEnumValueW( |
| hKey, 0, wszPath, &cbName, 0, |
| &dwType, NULL, 0 ); |
| if ( lr != ERROR_SUCCESS ) |
| break; |
| lr = RegDeleteValueW( hKey, wszPath ); |
| if ( lr != ERROR_SUCCESS ) |
| { |
| WARN("RegDeleteValueW - %08lx\n",lr); |
| return E_FAIL; |
| } |
| } |
| } |
| RegCloseKey( hKey ); |
| |
| lr = RegDeleteKeyW( hkRoot, lpKeyPath ); |
| WARN("RegDeleteKeyW - %08lx\n",lr); |
| if ( lr != ERROR_SUCCESS && lr != ERROR_FILE_NOT_FOUND ) |
| return S_FALSE; |
| return S_OK; |
| } |
| |
| static |
| HRESULT QUARTZ_GetPropertyFromMoniker( |
| IMoniker* pMoniker, /* [IN] Moniker */ |
| LPCOLESTR lpszPropName, /* [IN] Property */ |
| VARIANT* pVar ) /* [OUT] */ |
| { |
| IPropertyBag* pPropBag = NULL; |
| HRESULT hr; |
| |
| TRACE("(%p,%s,%p)\n",pMoniker,debugstr_w(lpszPropName),pVar); |
| |
| hr = IMoniker_BindToStorage(pMoniker,NULL,NULL,&IID_IPropertyBag,(void**)&pPropBag); |
| if ( FAILED(hr) ) |
| return hr; |
| hr = IPropertyBag_Read(pPropBag,lpszPropName,pVar,NULL); |
| IPropertyBag_Release(pPropBag); |
| |
| return hr; |
| } |
| |
| HRESULT QUARTZ_GetCLSIDFromMoniker( |
| IMoniker* pMoniker, /* [IN] Moniker */ |
| CLSID* pclsid ) /* [OUT] */ |
| { |
| VARIANT var; |
| HRESULT hr; |
| |
| var.n1.n2.vt = VT_BSTR; |
| hr = QUARTZ_GetPropertyFromMoniker( |
| pMoniker, QUARTZ_wszCLSID, &var ); |
| if ( hr == S_OK ) |
| { |
| hr = CLSIDFromString(var.n1.n2.n3.bstrVal,pclsid); |
| SysFreeString(var.n1.n2.n3.bstrVal); |
| } |
| |
| return hr; |
| } |
| |
| HRESULT QUARTZ_GetMeritFromMoniker( |
| IMoniker* pMoniker, /* [IN] Moniker */ |
| DWORD* pdwMerit ) /* [OUT] */ |
| { |
| VARIANT var; |
| HRESULT hr; |
| |
| var.n1.n2.vt = VT_I4; |
| hr = QUARTZ_GetPropertyFromMoniker( |
| pMoniker, QUARTZ_wszMerit, &var ); |
| if ( hr == S_OK ) |
| *pdwMerit = var.n1.n2.n3.lVal; |
| |
| return hr; |
| } |
| |
| HRESULT QUARTZ_GetFilterDataFromMoniker( |
| IMoniker* pMoniker, /* [IN] Moniker */ |
| BYTE** ppbFilterData, /* [OUT] */ |
| DWORD* pcbFilterData ) /* [OUT] */ |
| { |
| VARIANT var; |
| HRESULT hr; |
| SAFEARRAY* pArray; |
| |
| var.n1.n2.vt = VT_ARRAY|VT_UI1; |
| hr = QUARTZ_GetPropertyFromMoniker( |
| pMoniker, QUARTZ_wszFilterData, &var ); |
| if ( hr == S_OK ) |
| { |
| pArray = var.n1.n2.n3.parray; |
| hr = SafeArrayLock( pArray ); |
| if ( SUCCEEDED(hr) ) |
| { |
| *pcbFilterData = pArray->rgsabound[0].cElements - pArray->rgsabound[0].lLbound; |
| *ppbFilterData = (BYTE*)QUARTZ_AllocMem( *pcbFilterData ); |
| if ( *ppbFilterData == NULL ) |
| hr = E_OUTOFMEMORY; |
| else |
| memcpy( *ppbFilterData, pArray->pvData, *pcbFilterData ); |
| |
| SafeArrayUnlock( pArray ); |
| } |
| SafeArrayDestroy( pArray ); |
| } |
| |
| return hr; |
| } |
| |