windows-nt/Source/XPSP1/NT/shell/shdocvw/tbext.cpp
2020-09-26 16:20:57 +08:00

857 lines
23 KiB
C++

////////////////////////////////////////////////////////////////////////////
// File: TBExt.cpp (toolbar extension classes)
// Author: Karim Farouki
//
// We define here three classes:
// (1) CToolbarExt a base class that takes care of the
// button work for our custom extensions
// (2) CToolbarExtBand the object which deals with custom
// buttons that plug into bands
// (3) CToolbarExtExec the object which deals with custom
// buttons (or tools menu items) that exec stuff.
//
// The latter two are derived from the former
#include "priv.h"
#include <mshtmcid.h>
#include "tbext.h"
//////////////////////////////
// Class CToolbarExt
//
// This is the base class from which CToolbarExtBand and CToolbarExtExec
// both inherit. It takes care of all the ToolbarButton specific stuff
// like lazy loading the appropriate icons, and keeping track of the button
// text.
// Constructor / Destructor
//
CToolbarExt::CToolbarExt() : _cRef(1)
{
ASSERT(_hIcon == NULL);
ASSERT(_hIconSm == NULL);
ASSERT(_hHotIcon == NULL);
ASSERT(_hHotIconSm == NULL);
ASSERT(_bstrButtonText == NULL);
ASSERT(_bstrToolTip == NULL);
ASSERT(_hkeyThisExtension == NULL);
ASSERT(_hkeyCurrentLang == NULL);
ASSERT(_pisb == NULL);
DllAddRef();
}
// Destructor
//
CToolbarExt::~CToolbarExt()
{
if (_pisb)
_pisb->Release();
if (_bstrButtonText)
SysFreeString(_bstrButtonText);
if (_bstrToolTip)
SysFreeString(_bstrToolTip);
if (_hIcon)
DestroyIcon(_hIcon);
if (_hIconSm)
DestroyIcon(_hIconSm);
if (_hHotIcon)
DestroyIcon(_hHotIcon);
if (_hHotIconSm)
DestroyIcon(_hHotIconSm);
if (_hkeyThisExtension)
RegCloseKey(_hkeyThisExtension);
if (_hkeyCurrentLang)
RegCloseKey(_hkeyCurrentLang);
DllRelease();
}
// IUnknown implementation
//
STDMETHODIMP CToolbarExt::QueryInterface(const IID& iid, void** ppv)
{
if (iid == IID_IUnknown)
*ppv = static_cast<IBrowserExtension*>(this);
else if (iid == IID_IBrowserExtension)
*ppv = static_cast<IBrowserExtension*>(this);
else if (iid == IID_IOleCommandTarget)
*ppv = static_cast<IOleCommandTarget*>(this);
else if (iid == IID_IObjectWithSite)
*ppv = static_cast<IObjectWithSite*>(this);
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) CToolbarExt::AddRef()
{
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_(ULONG) CToolbarExt::Release()
{
if (InterlockedDecrement(&_cRef) == 0)
{
delete this;
return 0;
}
return _cRef;
}
// IBrowserExtension::Init Implementation. We'll read the ButtonText here but wait on the icons until
// a specific variant of the icon is requested.
STDMETHODIMP CToolbarExt::Init(REFGUID rguid)
{
HRESULT hr = S_OK;
LPOLESTR pszGUID;
if (SUCCEEDED(StringFromCLSID(rguid, &pszGUID)))
{
//Open the extension reg key associated with this guid
WCHAR szKey[MAX_PATH];
StrCpyN(szKey, TEXT("Software\\Microsoft\\Internet Explorer\\Extensions\\"), ARRAYSIZE(szKey));
StrCatBuff(szKey, pszGUID, ARRAYSIZE(szKey));
// We will keep _hkeyThisExtension around... it will be closed in the destructor!
if (RegOpenKeyEx(HKEY_CURRENT_USER, szKey, 0, KEY_READ, &_hkeyThisExtension) == ERROR_SUCCESS ||
RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, KEY_READ, &_hkeyThisExtension) == ERROR_SUCCESS)
{
// See if there is a subkey for the current language
LANGID langid = MLGetUILanguage();
WCHAR szBuff[MAX_PATH];
wnsprintf(szBuff, ARRAYSIZE(szBuff), L"Lang%04x", langid);
RegOpenKeyEx(_hkeyThisExtension, szBuff, 0, KEY_READ, &_hkeyCurrentLang);
// Now get the button text
_RegReadString(_hkeyThisExtension, TEXT("ButtonText"), &_bstrButtonText);
}
CoTaskMemFree(pszGUID);
}
if (!_bstrButtonText)
hr = E_FAIL;
return hr;
}
//
// Gets the icon closest to the desired size from an .ico file or from the
// resource in a .dll of .exe file
//
HICON CToolbarExt::_ExtractIcon
(
LPWSTR pszPath, // file to get icon from
int resid, // resource id (0 if unused)
int cx, // desired icon width
int cy // desired icon height
)
{
HICON hIcon = NULL;
WCHAR szPath[MAX_PATH];
SHExpandEnvironmentStrings(pszPath, szPath, ARRAYSIZE(szPath));
// If no resource id, assume it's an ico file
if (resid == 0)
{
hIcon = (HICON)LoadImage(0, szPath, IMAGE_ICON, cx, cy, LR_LOADFROMFILE);
}
// Otherwise, see if it's a resouce
if (hIcon == NULL)
{
HINSTANCE hInst = LoadLibraryEx(szPath, NULL, LOAD_LIBRARY_AS_DATAFILE);
if (hInst)
{
hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(resid), IMAGE_ICON, cx, cy, LR_DEFAULTCOLOR);
FreeLibrary(hInst);
}
}
return hIcon;
}
//
// Returns the desired icon in pvarProperty
//
HRESULT CToolbarExt::_GetIcon
(
LPCWSTR pszIcon, // Name of icon value in registry
int nWidth, // icon width
int nHeight, // icon height
HICON& rhIcon, // location to cached icon
VARIANTARG * pvarProperty // used for return icon
)
{
HRESULT hr = S_OK;
if (pvarProperty)
{
if (rhIcon == NULL)
{
BSTR bstrIconName;
if (_RegReadString(_hkeyThisExtension, pszIcon, &bstrIconName, TRUE))
{
// Parse entry such as "file.ext,1" to get the icon index
int nIconIndex = PathParseIconLocation(bstrIconName);
// If the entry was ",#" then it's an index into our built-in button bitmap
if (*bstrIconName == L'\0')
{
pvarProperty->vt = VT_I4;
pvarProperty->lVal = nIconIndex;
SysFreeString(bstrIconName);
return hr;
}
else
{
rhIcon = _ExtractIcon(bstrIconName, nIconIndex, nWidth, nHeight);
}
SysFreeString(bstrIconName);
}
}
if (rhIcon)
{
pvarProperty->vt = VT_BYREF;
pvarProperty->byref = rhIcon;
}
else
{
VariantInit(pvarProperty);
}
}
return hr;
}
//
// Implementation of IBrowserExtension::GetProperty(). There are two important points here:
// (1) We are lazy loading the appropriate icons. This way if the user never goes into small icon
// mode we never create the images...
// (2) If we are called with a NULL pvarProperty then we must still return S_OK if the iPropID
// is for a property that we support and E_NOTIMPL if we do not. This is why the if (pvarProperty)
// check is done for each case rather tan outside the case block. This behavior is important
// for CBrowserExtension::Update() who passes in a NULL pvarProperty but still is trying to determine
// what kind of extension this is!
//
STDMETHODIMP CToolbarExt::GetProperty(SHORT iPropID, VARIANTARG * pvarProperty)
{
HRESULT hr = S_OK;
if (pvarProperty)
VariantInit(pvarProperty); // in case of failure
switch (iPropID)
{
case TBEX_BUTTONTEXT:
if (pvarProperty)
{
pvarProperty->bstrVal = SysAllocString(_bstrButtonText);
if (pvarProperty->bstrVal)
{
pvarProperty->vt = VT_BSTR;
}
else
{
hr = E_OUTOFMEMORY;
}
}
break;
case TBEX_GRAYICON:
// For Whistler, we now use a 24 x 24 icons
if (SHUseClassicToolbarGlyphs())
{
hr = _GetIcon(TEXT("Icon"), 20, 20, _hIcon, pvarProperty);
}
else
{
hr = _GetIcon(TEXT("Icon"), 24, 24, _hIcon, pvarProperty);
}
break;
case TBEX_GRAYICONSM:
hr = _GetIcon(TEXT("Icon"), 16, 16, _hIconSm, pvarProperty);
break;
case TBEX_HOTICON:
// For Whistler, we now use a 24 x 24 icons
if (SHUseClassicToolbarGlyphs())
{
hr = _GetIcon(TEXT("HotIcon"), 20, 20, _hHotIcon, pvarProperty);
}
else
{
hr = _GetIcon(TEXT("HotIcon"), 24, 24, _hHotIcon, pvarProperty);
}
break;
case TBEX_HOTICONSM:
hr = _GetIcon(TEXT("HotIcon"), 16, 16, _hHotIconSm, pvarProperty);
break;
case TBEX_DEFAULTVISIBLE:
if (pvarProperty)
{
BOOL fVisible = _RegGetBoolValue(L"Default Visible", FALSE);
pvarProperty->vt = VT_BOOL;
pvarProperty->boolVal = fVisible ? VARIANT_TRUE : VARIANT_FALSE;
}
break;
default:
hr = E_NOTIMPL;
}
return hr;
}
//
// IOleCommandTarget Implementation
//
STDMETHODIMP CToolbarExt::QueryStatus(const GUID* pguidCmdGroup, ULONG cCmds, OLECMD rgCmds[], OLECMDTEXT* pCmdText)
{
HRESULT hr = OLECMDERR_E_UNKNOWNGROUP;
if (pguidCmdGroup && IsEqualGUID(*pguidCmdGroup, CLSID_ToolbarExtButtons))
{
// Default to all commands enabled
for (ULONG i = 0; i < cCmds; i++)
{
// if (prgCmds[i].cmdID == 1)
// Execing this object is supported and can be done at this point
rgCmds[i].cmdf = OLECMDF_ENABLED | OLECMDF_SUPPORTED;
// else
// prgCmds[i].cmdf = 0;
}
hr = S_OK;
}
// Return an empty pCmdText
if (pCmdText != NULL)
{
pCmdText->cwActual = 0;
}
return hr;
}
//
// IObjectWithSite Implementation
//
STDMETHODIMP CToolbarExt::SetSite(IUnknown* pUnkSite)
{
if (_pisb != NULL)
{
_pisb->Release();
_pisb = NULL;
}
if (pUnkSite)
pUnkSite->QueryInterface(IID_IShellBrowser, (void **)&_pisb);
return S_OK;
}
STDMETHODIMP CToolbarExt::GetSite(REFIID riid, void ** ppvSite)
{
return E_NOTIMPL;
}
BOOL CToolbarExt::_RegGetBoolValue
(
LPCWSTR pszPropName,
BOOL fDefault
)
{
WCHAR szData[MAX_PATH];
DWORD cbData = SIZEOF(szData);
if ((_hkeyCurrentLang && RegQueryValueEx(_hkeyCurrentLang, pszPropName, NULL, NULL, (unsigned char *)szData, &cbData) == ERROR_SUCCESS) ||
(_hkeyThisExtension && RegQueryValueEx(_hkeyThisExtension, pszPropName, NULL, NULL, (unsigned char *)szData, &cbData) == ERROR_SUCCESS))
{
if ((0 == StrCmpI(L"TRUE", szData)) ||
(0 == StrCmpI(L"YES", szData)))
{
fDefault = TRUE; // We read TRUE from the registry.
}
else if ((0 == StrCmpI(L"FALSE", szData)) ||
(0 == StrCmpI(L"NO", szData)))
{
fDefault = FALSE; // We read TRUE from the registry.
}
}
return fDefault;
}
// Private Helper Functions
//
// shlwapi has some similar function; however, they all insist on reopening and closing the key in question
// with each read. It is explicitly suggested that we use our own helper if we are caching the key...
BOOL CToolbarExt::_RegReadString
(
HKEY hkeyThisExtension,
LPCWSTR pszPropName,
BSTR * pbstrProp,
BOOL fExpand // = FALSE, Expand Environment strings
)
{
WCHAR szData[MAX_PATH];
*pbstrProp = NULL;
BOOL fSuccess = FALSE;
// First try the optional location for localized content
if (_hkeyCurrentLang)
{
if (SUCCEEDED(SHLoadRegUIString(_hkeyCurrentLang, pszPropName, szData, ARRAYSIZE(szData))))
{
fSuccess = TRUE;
}
}
// Next try default location
if (!fSuccess && _hkeyThisExtension)
{
if (SUCCEEDED(SHLoadRegUIString(hkeyThisExtension, pszPropName, szData, ARRAYSIZE(szData))))
{
fSuccess = TRUE;
}
}
if (fSuccess)
{
LPWSTR psz = szData;
WCHAR szExpand[MAX_PATH];
if (fExpand)
{
SHExpandEnvironmentStrings(szData, szExpand, ARRAYSIZE(szExpand));
psz = szExpand;
}
*pbstrProp = SysAllocString(psz);
}
return (NULL != *pbstrProp);
}
///////////////////////////////////////////////////////////
// Class CToolbarExtBand
//
// This class adds to the base functionality of CToolbarExt
// by storing the CLSID for a registered band, and displaying that
// band upon execution of IOleCommandTarget::Exec
//
//
STDAPI CToolbarExtBand_CreateInstance(
IUnknown * punkOuter,
IUnknown ** ppunk,
LPCOBJECTINFO poi
)
{
HRESULT hr = S_OK;
*ppunk = NULL;
CToolbarExtBand * lpTEB = new CToolbarExtBand();
if (lpTEB == NULL)
hr = E_OUTOFMEMORY;
else
*ppunk = SAFECAST(lpTEB, IBrowserExtension *);
return hr;
}
// Constructor / Destructor
//
CToolbarExtBand::CToolbarExtBand()
{
ASSERT(_cRef == 1);
ASSERT(_bBandState == FALSE);
ASSERT(_bstrBandCLSID == NULL);
}
// Destructor
//
CToolbarExtBand::~CToolbarExtBand()
{
if (_bstrBandCLSID)
SysFreeString(_bstrBandCLSID);
}
// IBrowserExtension::Init() We pass the majroity of the work on to the base class, then we load
// the BandCLSID and cache it.
STDMETHODIMP CToolbarExtBand::Init(REFGUID rguid)
{
HRESULT hr = CToolbarExt::Init(rguid);
_RegReadString(_hkeyThisExtension, TEXT("BandCLSID"), &_bstrBandCLSID);
if (!(_bstrButtonText && _bstrBandCLSID))
hr = E_FAIL;
return hr;
}
STDMETHODIMP CToolbarExtBand::QueryStatus
(
const GUID * pguidCmdGroup,
ULONG cCmds,
OLECMD prgCmds[],
OLECMDTEXT * pCmdText
)
{
HRESULT hr = OLECMDERR_E_UNKNOWNGROUP;
if (pguidCmdGroup && IsEqualGUID(*pguidCmdGroup, CLSID_ToolbarExtButtons))
{
VARIANT varClsid;
// Default to all commands enabled
for (ULONG i = 0; i < cCmds; i++)
{
varClsid.vt = VT_BSTR;
varClsid.bstrVal = _bstrBandCLSID;
prgCmds[i].cmdf = OLECMDF_ENABLED | OLECMDF_SUPPORTED;
hr = IUnknown_Exec(_pisb, &CGID_ShellDocView, SHDVID_ISBROWSERBARVISIBLE, 0, &varClsid, NULL);
if (S_OK == hr)
{
prgCmds[i].cmdf |= OLECMDF_LATCHED;
}
}
hr = S_OK;
}
return hr;
}
// Take the pIShellBrowser (obtained from IObjectWithSite::SetSite()) and disply the band
STDMETHODIMP CToolbarExtBand::Exec(
const GUID * pguidCmdGroup,
DWORD nCmdID,
DWORD nCmdexecopt,
VARIANT * pvaIn,
VARIANT * pvaOut
)
{
HRESULT hr = E_FAIL;
if (_pisb)
{
VARIANT varClsid;
varClsid.vt = VT_BSTR;
varClsid.bstrVal = _bstrBandCLSID;
_bBandState = !_bBandState;
IUnknown_Exec(_pisb, &CGID_ShellDocView, SHDVID_SHOWBROWSERBAR, _bBandState, &varClsid, NULL);
hr = S_OK;
}
return hr;
}
///////////////////////////////////////////////////////////////////////
// Class CToolbarExtExec
//
// Expands on the base class by adding support for tools menu plug-ins.
// An instance of this class can be a button OR a menu OR BOTH. It also
// keeps track of a BSTR which it ShellExecutes in its IOleCommandTarget::Exec()
//
STDAPI CToolbarExtExec_CreateInstance(
IUnknown * punkOuter,
IUnknown ** ppunk,
LPCOBJECTINFO poi
)
{
HRESULT hr = S_OK;
*ppunk = NULL;
CToolbarExtExec * lpTEE = new CToolbarExtExec();
if (lpTEE == NULL)
hr = E_OUTOFMEMORY;
else
*ppunk = SAFECAST(lpTEE, IBrowserExtension *);
return hr;
}
CToolbarExtExec::CToolbarExtExec()
{
ASSERT(_cRef == 1);
ASSERT(_bstrToolTip == NULL);
ASSERT(_bstrExec == NULL);
ASSERT(_bstrScript == NULL);
ASSERT(_bstrMenuText == NULL);
ASSERT(_bstrMenuCustomize == NULL);
ASSERT(_bstrMenuStatusBar == NULL);
ASSERT(_punkExt == NULL);
}
CToolbarExtExec::~CToolbarExtExec()
{
if (_bstrToolTip)
SysFreeString(_bstrToolTip);
if (_bstrExec)
SysFreeString(_bstrExec);
if (_bstrScript)
SysFreeString(_bstrScript);
if (_bstrMenuText)
SysFreeString(_bstrMenuText);
if (_bstrMenuCustomize)
SysFreeString(_bstrMenuCustomize);
if (_bstrMenuStatusBar)
SysFreeString(_bstrMenuStatusBar);
if (_punkExt)
_punkExt->Release();
}
// Pass on the work for the toolbar button intiaztion to the base class then determine the object
// type and initialize the menu information if necessary...
STDMETHODIMP CToolbarExtExec::Init(REFGUID rguid)
{
HRESULT hr = CToolbarExt::Init(rguid);
// If the baseclass initialization went OK, then we have a working button
if (hr == S_OK)
_bButton = TRUE;
// Get app and/or script to execute (optional)
_RegReadString(_hkeyThisExtension, TEXT("Exec"), &_bstrExec, TRUE);
_RegReadString(_hkeyThisExtension, TEXT("Script"), &_bstrScript, TRUE);
// See if we have a menu item
if (_RegReadString(_hkeyThisExtension, TEXT("MenuText"), &_bstrMenuText))
{
_RegReadString(_hkeyThisExtension, TEXT("MenuCustomize"), &_bstrMenuCustomize);
_RegReadString(_hkeyThisExtension, TEXT("MenuStatusBar"), &_bstrMenuStatusBar);
_bMenuItem = TRUE;
}
if (_bMenuItem || _bButton)
{
hr = S_OK;
}
return hr;
}
// It we're a button try passing the work on to the base class, if that doesn't cut it we'll
// check the menu stuff...
STDMETHODIMP CToolbarExtExec::GetProperty(SHORT iPropID, VARIANTARG * pvarProperty)
{
HRESULT hr = S_OK;
BOOL fImple = FALSE;
if (_bButton)
{
// If The generic button's getproperty returns S_OK then our job here is done
if (CToolbarExt::GetProperty(iPropID, pvarProperty) == S_OK)
fImple = TRUE;
}
if (_bMenuItem && !fImple)
{
fImple = TRUE;
if (pvarProperty)
VariantInit(pvarProperty);
switch (iPropID)
{
case TMEX_CUSTOM_MENU:
{
if (pvarProperty)
{
pvarProperty->bstrVal = SysAllocString(_bstrMenuCustomize);
if (pvarProperty->bstrVal)
{
pvarProperty->vt = VT_BSTR;
}
else
{
hr = E_OUTOFMEMORY;
}
}
}
break;
case TMEX_MENUTEXT:
if (pvarProperty)
{
pvarProperty->bstrVal = SysAllocString(_bstrMenuText);
if (pvarProperty->bstrVal)
{
pvarProperty->vt = VT_BSTR;
}
else
{
hr = E_OUTOFMEMORY;
}
}
break;
case TMEX_STATUSBARTEXT:
if (pvarProperty)
{
pvarProperty->bstrVal = SysAllocString(_bstrMenuStatusBar);
if (pvarProperty->bstrVal)
{
pvarProperty->vt = VT_BSTR;
}
else
{
hr = E_OUTOFMEMORY;
}
}
break;
default:
fImple = FALSE;
}
}
if (!fImple)
hr = E_NOTIMPL;
return hr;
}
STDMETHODIMP CToolbarExtExec::SetSite(IUnknown* punkSite)
{
// Give the external object our site
IUnknown_SetSite(_punkExt, punkSite);
// Call base class
return CToolbarExt::SetSite(punkSite);
}
STDMETHODIMP CToolbarExtExec::QueryStatus(const GUID * pguidCmdGroup, ULONG cCmds, OLECMD rgCmds[], OLECMDTEXT * pCmdText)
{
HRESULT hr = S_OK;
// Pass query to external object if it exists
IOleCommandTarget* pCmd;
if (_punkExt && SUCCEEDED(_punkExt->QueryInterface(IID_IOleCommandTarget, (LPVOID*)&pCmd)))
{
hr = pCmd->QueryStatus(pguidCmdGroup, cCmds, rgCmds, pCmdText);
pCmd->Release();
}
else
{
// Let base class handle this
hr = CToolbarExt::QueryStatus(pguidCmdGroup, cCmds, rgCmds, pCmdText);
}
return hr;
}
// Shell execute the _bstrExec
STDMETHODIMP CToolbarExtExec::Exec(
const GUID * pguidCmdGroup,
DWORD nCmdId,
DWORD nCmdexecopt,
VARIANT * pvaIn,
VARIANT * pvaOut
)
{
HRESULT hr = S_OK;
//
// The first time this is called, we lazy instantiate an external object if
// one is registered.. This object can JIT in components and provide a
// command target.
//
if (!_bExecCalled)
{
// We only do this once
_bExecCalled = TRUE;
BSTR bstrExtCLSID;
if (_RegReadString(_hkeyThisExtension, TEXT("clsidExtension"), &bstrExtCLSID))
{
// We have an extension clsid, so create the object. This gives the object an oportunity
// to jit in code when its button or menu is invoked.
CLSID clsidExt;
if (CLSIDFromString(bstrExtCLSID, &clsidExt) == S_OK)
{
if (SUCCEEDED(CoCreateInstance(clsidExt, NULL, CLSCTX_INPROC_SERVER,
IID_IUnknown, (void **)&_punkExt)))
{
// Give the object our site (optional)
IUnknown_SetSite(_punkExt, _pisb);
}
}
SysFreeString(bstrExtCLSID);
}
}
// Pass command to external object if it exists
IOleCommandTarget* pCmd;
if (_punkExt && SUCCEEDED(_punkExt->QueryInterface(IID_IOleCommandTarget, (LPVOID*)&pCmd)))
{
hr = pCmd->Exec(pguidCmdGroup, nCmdId, nCmdexecopt, pvaIn, pvaOut);
pCmd->Release();
}
// Run a script if one was specified
if(_bstrScript && _pisb)
{
IOleCommandTarget *poct = NULL;
VARIANT varArg;
varArg.vt = VT_BSTR;
varArg.bstrVal = _bstrScript;
hr = _pisb->QueryInterface(IID_IOleCommandTarget, (LPVOID *)&poct);
if (SUCCEEDED(hr))
{
// Tell MSHTML to execute the script
hr = poct->Exec(&CGID_MSHTML, IDM_RUNURLSCRIPT, 0, &varArg, NULL);
poct->Release();
}
}
// Launch executable if one was specified
if (_bstrExec)
{
SHELLEXECUTEINFO sei = { 0 };
sei.cbSize = sizeof(sei);
sei.lpFile = _bstrExec;
sei.nShow = SW_SHOWNORMAL;
// We are using ShellExecuteEx over ShellExecute because the Unicode version of ShellExecute
// is bogus on 95/98
if (ShellExecuteExW(&sei) == FALSE)
hr = E_FAIL;
}
return hr;
}