Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1360 lines
38 KiB

  1. /*****************************************************************************
  2. *
  3. * (C) COPYRIGHT MICROSOFT CORPORATION, 2000
  4. *
  5. * TITLE: tmplutil.cpp
  6. *
  7. * VERSION: 1.0
  8. *
  9. * AUTHOR: LazarI
  10. *
  11. * DATE: 10-Mar-2000
  12. *
  13. * DESCRIPTION: Smart pointers, utility templates, etc...
  14. *
  15. *****************************************************************************/
  16. #include "precomp.h"
  17. #pragma hdrstop
  18. /*****************************************************************************
  19. IMPORTANT!
  20. all those headers should be included in the pch file before including tmplutil.h
  21. (in the same order!) in order to be able to compile this file.
  22. // some common headers
  23. #include <shlobj.h> // shell OM interfaces
  24. #include <shlwapi.h> // shell common API
  25. #include <winspool.h> // spooler
  26. #include <assert.h> // assert
  27. #include <commctrl.h> // common controls
  28. #include <lm.h> // Lan manager (netapi32.dll)
  29. #include <wininet.h> // inet core - necessary for INTERNET_MAX_HOST_NAME_LENGTH
  30. // some private shell headers
  31. #include <shlwapip.h> // private shell common API
  32. #include <shpriv.h> // private shell interfaces
  33. #include <comctrlp.h> // private common controls
  34. *****************************************************************************/
  35. #include "tmplutil.h"
  36. #define gszBackwardSlash TEXT('\\')
  37. #define gszLeadingSlashes TEXT("\\\\")
  38. /*****************************************************************************
  39. COMObjects_GetCount
  40. *****************************************************************************/
  41. static LONG g_lCOMObjectsCount = 0;
  42. LONG COMObjects_GetCount()
  43. {
  44. return g_lCOMObjectsCount;
  45. }
  46. HRESULT PinCurrentDLL()
  47. {
  48. HRESULT hr = S_OK;
  49. HINSTANCE hModuleSelf = NULL;
  50. // Let's get the handle of the current module -
  51. // the one this function belongs to.
  52. MEMORY_BASIC_INFORMATION mbi;
  53. if (VirtualQuery(reinterpret_cast<LPCVOID>(PinCurrentDLL), &mbi, sizeof(mbi)))
  54. {
  55. hModuleSelf = reinterpret_cast<HINSTANCE>(mbi.AllocationBase);
  56. }
  57. else
  58. {
  59. // VirtualQuery failed.
  60. hr = CreateHRFromWin32();
  61. }
  62. if (SUCCEEDED(hr))
  63. {
  64. // Get the module name and call LoadLibrary on it.
  65. TCHAR szModuleName[MAX_PATH];
  66. if (GetModuleFileName(hModuleSelf, szModuleName, ARRAYSIZE(szModuleName)))
  67. {
  68. if (NULL == LoadLibrary(szModuleName))
  69. {
  70. // LoadLibrary failed.
  71. hr = CreateHRFromWin32();
  72. }
  73. }
  74. else
  75. {
  76. // GetModuleFileName failed.
  77. hr = CreateHRFromWin32();
  78. }
  79. }
  80. return hr;
  81. }
  82. /*****************************************************************************
  83. PrinterSplitFullName
  84. Routine Description:
  85. splits a fully qualified printer connection name into server and printer name parts.
  86. Arguments:
  87. pszFullName - full qualifier printer name ('printer' or '\\server\printer')
  88. pszBuffer - scratch buffer used to store output strings.
  89. nMaxLength - the size of the scratch buffer in characters
  90. ppszServer - receives pointer to the server string. If it is a
  91. local printer, empty string is returned.
  92. ppszPrinter - receives a pointer to the printer string. OPTIONAL
  93. Return Value:
  94. returns S_OK on sucess or COM error otherwise
  95. *****************************************************************************/
  96. HRESULT PrinterSplitFullName(LPCTSTR pszFullName, TCHAR szBuffer[], int nMaxLength, LPCTSTR *ppszServer,LPCTSTR *ppszPrinter)
  97. {
  98. HRESULT hr = S_OK;
  99. lstrcpyn(szBuffer, pszFullName, nMaxLength);
  100. LPTSTR pszPrinter = szBuffer;
  101. if (pszFullName[0] != TEXT('\\') || pszFullName[1] != TEXT('\\'))
  102. {
  103. pszPrinter = szBuffer;
  104. *ppszServer = TEXT("");
  105. }
  106. else
  107. {
  108. *ppszServer = szBuffer;
  109. pszPrinter = _tcschr(*ppszServer + 2, TEXT('\\'));
  110. if (NULL == pszPrinter)
  111. {
  112. //
  113. // We've encountered a printer called "\\server"
  114. // (only two backslashes in the string). We'll treat
  115. // it as a local printer. We should never hit this,
  116. // but the spooler doesn't enforce this. We won't
  117. // format the string. Server is local, so set to szNULL.
  118. //
  119. pszPrinter = szBuffer;
  120. *ppszServer = TEXT("");
  121. }
  122. else
  123. {
  124. //
  125. // We found the third backslash; null terminate our
  126. // copy and set bRemote TRUE to format the string.
  127. //
  128. *pszPrinter++ = 0;
  129. }
  130. }
  131. if (ppszPrinter)
  132. {
  133. *ppszPrinter = pszPrinter;
  134. }
  135. return hr;
  136. }
  137. ////////////////////////////////////////////////
  138. //
  139. // class COleComInitializer
  140. //
  141. // smart OLE2, COM initializer - just declare
  142. // an instance wherever need to use COM, OLE2
  143. //
  144. COleComInitializer::COleComInitializer(BOOL bOleInit)
  145. : m_hr(E_FAIL),
  146. m_bOleInit(bOleInit)
  147. {
  148. if( m_bOleInit )
  149. {
  150. m_hr = OleInitialize(NULL);
  151. }
  152. else
  153. {
  154. m_hr = CoInitialize(NULL);
  155. }
  156. }
  157. COleComInitializer::~COleComInitializer()
  158. {
  159. if( SUCCEEDED(m_hr) )
  160. {
  161. if( m_bOleInit )
  162. {
  163. OleUninitialize();
  164. }
  165. else
  166. {
  167. CoUninitialize();
  168. }
  169. }
  170. }
  171. COleComInitializer::operator BOOL () const
  172. {
  173. if( FAILED(m_hr) )
  174. {
  175. return (RPC_E_CHANGED_MODE == m_hr);
  176. }
  177. else
  178. {
  179. return TRUE;
  180. }
  181. }
  182. ////////////////////////////////////////////////
  183. //
  184. // class CDllLoader
  185. //
  186. // smart DLL loader - calls LoadLibrary
  187. // FreeLibrary for you.
  188. //
  189. CDllLoader::CDllLoader(LPCTSTR pszDllName)
  190. : m_hLib(NULL)
  191. {
  192. m_hLib = LoadLibrary(pszDllName);
  193. }
  194. CDllLoader::~CDllLoader()
  195. {
  196. if( m_hLib )
  197. {
  198. FreeLibrary( m_hLib );
  199. m_hLib = NULL;
  200. }
  201. }
  202. CDllLoader::operator BOOL () const
  203. {
  204. return (NULL != m_hLib);
  205. }
  206. FARPROC CDllLoader::GetProcAddress( LPCSTR lpProcName )
  207. {
  208. if( m_hLib )
  209. {
  210. return ::GetProcAddress( m_hLib, lpProcName );
  211. }
  212. return NULL;
  213. }
  214. FARPROC CDllLoader::GetProcAddress( WORD wProcOrd )
  215. {
  216. if( m_hLib )
  217. {
  218. return ::GetProcAddress( m_hLib, (LPCSTR)MAKEINTRESOURCE(wProcOrd) );
  219. }
  220. return NULL;
  221. }
  222. ////////////////////////////////////////////////
  223. // class CCookiesHolder
  224. //
  225. // this a utility class which allows us to pass more
  226. // than one pointer through a single cookie.
  227. //
  228. CCookiesHolder::CCookiesHolder()
  229. : m_pCookies(NULL),
  230. m_uCount(0)
  231. {
  232. }
  233. CCookiesHolder::CCookiesHolder(UINT uCount)
  234. : m_pCookies(NULL),
  235. m_uCount(0)
  236. {
  237. SetCount(uCount);
  238. }
  239. CCookiesHolder::~CCookiesHolder()
  240. {
  241. SetCount(0);
  242. }
  243. BOOL CCookiesHolder::SetCount(UINT uCount)
  244. {
  245. BOOL bReturn = FALSE;
  246. if( uCount )
  247. {
  248. // reset first
  249. SetCount(0);
  250. // attempt to allocate memory for the cookies
  251. LPVOID *pCookies = new LPVOID[uCount];
  252. if( pCookies )
  253. {
  254. m_uCount = uCount;
  255. m_pCookies = pCookies;
  256. bReturn = TRUE;
  257. }
  258. }
  259. else
  260. {
  261. // zero means - reset
  262. if( m_pCookies )
  263. {
  264. delete[] m_pCookies;
  265. m_pCookies = NULL;
  266. m_uCount = 0;
  267. }
  268. bReturn = TRUE;
  269. }
  270. return bReturn;
  271. }
  272. ////////////////////////////////////////////////
  273. // class CPrintersAutoCompleteSource
  274. //
  275. // printer's autocomplete source impl.
  276. //
  277. QITABLE_DECLARE(CPrintersAutoCompleteSource)
  278. class CPrintersAutoCompleteSource: public CUnknownMT<QITABLE_GET(CPrintersAutoCompleteSource)>, // MT impl. of IUnknown
  279. public IEnumString, // string enumerator
  280. public IACList // autocomplete list generator
  281. {
  282. public:
  283. CPrintersAutoCompleteSource();
  284. ~CPrintersAutoCompleteSource();
  285. //////////////////
  286. // IUnknown
  287. //
  288. IMPLEMENT_IUNKNOWN()
  289. //////////////////
  290. // IEnumString
  291. //
  292. STDMETHODIMP Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched);
  293. STDMETHODIMP Skip(ULONG celt);
  294. STDMETHODIMP Reset(void);
  295. STDMETHODIMP Clone(IEnumString **ppenum) { return E_NOTIMPL; }
  296. //////////////////
  297. // IACList
  298. //
  299. STDMETHODIMP Expand(LPCOLESTR pszExpand);
  300. private:
  301. CAutoPtrArray<BYTE> m_spBufferPrinters;
  302. CAutoPtrArray<BYTE> m_spBufferShares;
  303. PRINTER_INFO_5 *m_pPI5;
  304. SHARE_INFO_1 *m_pSI1;
  305. ULONG m_ulCount;
  306. ULONG m_ulPos;
  307. TCHAR m_szServer[PRINTER_MAX_PATH];
  308. CRefPtrCOM<IEnumString> m_spCustomMRUEnum;
  309. BOOL _IsServerName(LPCTSTR psz, BOOL *pbPartial);
  310. static BOOL _IsMasqPrinter(const PRINTER_INFO_5 &pi5);
  311. static HRESULT _CreateCustomMRU(REFIID riid, void **ppv);
  312. static HRESULT _AddCustomMRU(LPCTSTR psz);
  313. };
  314. // QueryInterface table
  315. QITABLE_BEGIN(CPrintersAutoCompleteSource)
  316. QITABENT(CPrintersAutoCompleteSource, IEnumString), // IID_IEnumString
  317. QITABENT(CPrintersAutoCompleteSource, IACList), // IID_IACList
  318. QITABLE_END()
  319. #define SZ_REGKEY_PRNCONNECTMRU L"Printers\\Settings\\Wizard\\ConnectMRU"
  320. // comctrlp.h defines this as AddMRUStringW preventing us from using the IACLCustomMRU interface
  321. #undef AddMRUString
  322. HRESULT CPrintersACS_CreateInstance(IUnknown **ppUnk)
  323. {
  324. HRESULT hr = E_INVALIDARG;
  325. if( ppUnk )
  326. {
  327. hr = PinCurrentDLL();
  328. if( SUCCEEDED(hr) )
  329. {
  330. CPrintersAutoCompleteSource *pObj = new CPrintersAutoCompleteSource();
  331. hr = pObj ? S_OK : E_OUTOFMEMORY;
  332. if( SUCCEEDED(hr) )
  333. {
  334. hr = pObj->QueryInterface(IID_IUnknown, (void**)ppUnk);
  335. pObj->Release();
  336. }
  337. }
  338. }
  339. return hr;
  340. }
  341. CPrintersAutoCompleteSource::CPrintersAutoCompleteSource():
  342. m_pPI5(NULL),
  343. m_ulCount(0),
  344. m_ulPos(0)
  345. {
  346. InterlockedIncrement(&g_lCOMObjectsCount);
  347. }
  348. CPrintersAutoCompleteSource::~CPrintersAutoCompleteSource()
  349. {
  350. InterlockedDecrement(&g_lCOMObjectsCount);
  351. }
  352. //////////////////
  353. // IUnknown
  354. //
  355. STDMETHODIMP CPrintersAutoCompleteSource::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
  356. {
  357. HRESULT hr = S_FALSE;
  358. if( pceltFetched )
  359. {
  360. *pceltFetched = 0;
  361. }
  362. if( m_ulCount && (m_pPI5 || m_pSI1) )
  363. {
  364. ULONG cFetched = 0;
  365. if( m_pPI5 )
  366. {
  367. // printers enumerated
  368. for( ; m_ulPos < m_ulCount && cFetched < celt; m_ulPos++ )
  369. {
  370. // if this is a valid (non-masq) printer just return it
  371. if( m_pPI5[m_ulPos].pPrinterName[0] && SUCCEEDED(SHStrDup(m_pPI5[m_ulPos].pPrinterName, &rgelt[cFetched])) )
  372. {
  373. cFetched++;
  374. }
  375. }
  376. }
  377. else
  378. {
  379. // shares enumerated
  380. TCHAR szBuffer[PRINTER_MAX_PATH];
  381. for( ; m_ulPos < m_ulCount && cFetched < celt; m_ulPos++ )
  382. {
  383. // if this is a valid printer share name, just return it
  384. if( m_pSI1[m_ulPos].shi1_netname[0] &&
  385. -1 != wnsprintf(szBuffer, ARRAYSIZE(szBuffer), TEXT("%s\\%s"),
  386. m_szServer, m_pSI1[m_ulPos].shi1_netname) &&
  387. SUCCEEDED(SHStrDup(szBuffer, &rgelt[cFetched])) )
  388. {
  389. cFetched++;
  390. }
  391. }
  392. }
  393. if( pceltFetched )
  394. {
  395. *pceltFetched = cFetched;
  396. }
  397. hr = cFetched == celt ? S_OK : S_FALSE;
  398. }
  399. else
  400. {
  401. // use our custom MRU if any...
  402. if( m_spCustomMRUEnum )
  403. {
  404. hr = m_spCustomMRUEnum->Next(celt, rgelt, pceltFetched);
  405. }
  406. }
  407. return hr;
  408. }
  409. STDMETHODIMP CPrintersAutoCompleteSource::Skip(ULONG celt)
  410. {
  411. HRESULT hr = S_FALSE;
  412. if( m_ulCount && (m_pPI5 || m_pSI1) )
  413. {
  414. hr = ((m_ulPos + celt) <= m_ulCount) ? S_OK : S_FALSE;
  415. m_ulPos = min(m_ulPos + celt, m_ulCount);
  416. }
  417. else
  418. {
  419. // use our custom MRU if any...
  420. if( m_spCustomMRUEnum )
  421. {
  422. hr = m_spCustomMRUEnum->Skip(celt);
  423. }
  424. }
  425. return hr;
  426. }
  427. STDMETHODIMP CPrintersAutoCompleteSource::Reset(void)
  428. {
  429. HRESULT hr = S_OK;
  430. if( m_ulCount && (m_pPI5 || m_pSI1) )
  431. {
  432. m_ulPos = 0;
  433. }
  434. else
  435. {
  436. // use our custom MRU if any...
  437. if( m_spCustomMRUEnum )
  438. {
  439. hr = m_spCustomMRUEnum->Reset();
  440. }
  441. }
  442. return hr;
  443. }
  444. typedef bool PI5_less_type(const PRINTER_INFO_5 &i1, const PRINTER_INFO_5 &i2);
  445. static bool PI5_less(const PRINTER_INFO_5 &i1, const PRINTER_INFO_5 &i2)
  446. {
  447. return (lstrcmp(i1.pPrinterName, i2.pPrinterName) < 0);
  448. }
  449. typedef bool SI1_less_type(const SHARE_INFO_1 &i1, const SHARE_INFO_1 &i2);
  450. static bool SI1_less(const SHARE_INFO_1 &i1, const SHARE_INFO_1 &i2)
  451. {
  452. return (lstrcmp(i1.shi1_netname, i2.shi1_netname) < 0);
  453. }
  454. //////////////////
  455. // IACList
  456. //
  457. STDMETHODIMP CPrintersAutoCompleteSource::Expand(LPCOLESTR pszExpand)
  458. {
  459. HRESULT hr = E_FAIL;
  460. DWORD cReturned = 0;
  461. BOOL bPartial = FALSE;
  462. // assume this is not a server name, so reset the list first
  463. m_pPI5 = NULL;
  464. m_pSI1 = NULL;
  465. m_spBufferPrinters = NULL;
  466. m_spBufferShares = NULL;
  467. m_ulCount = m_ulPos = 0;
  468. m_szServer[0] = 0;
  469. m_spCustomMRUEnum = NULL;
  470. if( _IsServerName(pszExpand, &bPartial) )
  471. {
  472. // make a copy of the print buffer & cut off the last slash
  473. TCHAR szBuffer[PRINTER_MAX_PATH];
  474. lstrcpyn(szBuffer, pszExpand, ARRAYSIZE(szBuffer));
  475. szBuffer[lstrlen(szBuffer)-1] = 0;
  476. // enum the printers on that server
  477. if( SUCCEEDED(hr = ShellServices::EnumPrintersWrap(PRINTER_ENUM_NAME, 5, szBuffer, &m_spBufferPrinters, &cReturned)) && cReturned )
  478. {
  479. m_ulPos = 0;
  480. m_ulCount = cReturned;
  481. m_pPI5 = m_spBufferPrinters.GetPtrAs<PRINTER_INFO_5*>();
  482. lstrcpyn(m_szServer, szBuffer, ARRAYSIZE(m_szServer));
  483. // successful expand - remember the MRU string
  484. _AddCustomMRU(szBuffer);
  485. // traverse to check for masq printers
  486. for( ULONG ulPos = 0; ulPos < m_ulCount; ulPos++ )
  487. {
  488. if( _IsMasqPrinter(m_pPI5[ulPos]) )
  489. {
  490. // we don't really care for masq printer's since they are
  491. // an obsolete concept and can't be truly shared/connected to
  492. m_pPI5[ulPos].pPrinterName = TEXT("");
  493. }
  494. }
  495. // invoke STL to sort
  496. std::sort<PRINTER_INFO_5*, PI5_less_type*>(m_pPI5, m_pPI5 + m_ulCount, PI5_less);
  497. }
  498. else
  499. {
  500. // enumeration of the printers failed, this could be because the remote spooler is down
  501. // or it is a downlevel print provider (win9x, novell, linux, sun...) in this case we
  502. // would like to try enumerating the shares as possible connection points.
  503. if( SUCCEEDED(hr = ShellServices::NetAPI_EnumShares(szBuffer, 1, &m_spBufferShares, &cReturned)) && cReturned )
  504. {
  505. m_ulPos = 0;
  506. m_ulCount = cReturned;
  507. m_pSI1 = m_spBufferShares.GetPtrAs<SHARE_INFO_1*>();
  508. lstrcpyn(m_szServer, szBuffer, ARRAYSIZE(m_szServer));
  509. // successful expand - remember the MRU string
  510. _AddCustomMRU(szBuffer);
  511. // traverse to remove the non-printer shares
  512. for( ULONG ulPos = 0; ulPos < m_ulCount; ulPos++ )
  513. {
  514. if( STYPE_PRINTQ != m_pSI1[ulPos].shi1_type )
  515. {
  516. // this is a non-printer share, remove
  517. m_pSI1[ulPos].shi1_netname[0] = 0;
  518. }
  519. }
  520. // invoke STL to sort
  521. std::sort<SHARE_INFO_1*, SI1_less_type*>(m_pSI1, m_pSI1 + m_ulCount, SI1_less);
  522. }
  523. }
  524. }
  525. else
  526. {
  527. if( bPartial )
  528. {
  529. // use our custom MRU for autocomplete
  530. hr = _CreateCustomMRU(IID_IEnumString, m_spCustomMRUEnum.GetPPV());
  531. }
  532. }
  533. return hr;
  534. }
  535. BOOL CPrintersAutoCompleteSource::_IsServerName(LPCTSTR psz, BOOL *pbPartial)
  536. {
  537. ASSERT(pbPartial);
  538. BOOL bRet = FALSE;
  539. int i, iSepCount = 0, iLen = lstrlen(psz);
  540. for( i=0; i<iLen; i++ )
  541. {
  542. if( psz[i] == gszBackwardSlash )
  543. {
  544. iSepCount++;
  545. }
  546. }
  547. if( (1 == iSepCount && psz[0] == gszBackwardSlash) ||
  548. (2 == iSepCount && psz[0] == gszBackwardSlash && psz[1] == gszBackwardSlash) )
  549. {
  550. *pbPartial = TRUE;
  551. }
  552. if( 3 < iLen &&
  553. 3 == iSepCount &&
  554. psz[0] == gszBackwardSlash &&
  555. psz[1] == gszBackwardSlash &&
  556. psz[iLen-1] == gszBackwardSlash )
  557. {
  558. bRet = TRUE;
  559. }
  560. return bRet;
  561. }
  562. BOOL CPrintersAutoCompleteSource::_IsMasqPrinter(const PRINTER_INFO_5 &pi5)
  563. {
  564. // this is a little bit hacky, but there is no other way to tell the masq printer
  565. // in the remote case. the spooler APIs suffer from some severe design flaws and
  566. // we have to put up with that.
  567. LPCTSTR pszServer;
  568. LPCTSTR pszPrinter;
  569. TCHAR szScratch[PRINTER_MAX_PATH];
  570. // split the full printer name into its components.
  571. if( SUCCEEDED(PrinterSplitFullName(pi5.pPrinterName,
  572. szScratch, ARRAYSIZE(szScratch), &pszServer, &pszPrinter)) )
  573. {
  574. return (0 == _tcsnicmp(pszPrinter, gszLeadingSlashes, _tcslen(gszLeadingSlashes)));
  575. }
  576. else
  577. {
  578. return FALSE;
  579. }
  580. }
  581. HRESULT CPrintersAutoCompleteSource::_CreateCustomMRU(REFIID riid, void **ppv)
  582. {
  583. HRESULT hr = E_INVALIDARG;
  584. CRefPtrCOM<IACLCustomMRU> spCustomMRU;
  585. if( ppv &&
  586. SUCCEEDED(hr = CoCreateInstance(CLSID_ACLCustomMRU, NULL,
  587. CLSCTX_INPROC_SERVER, IID_IACLCustomMRU, spCustomMRU.GetPPV())) &&
  588. SUCCEEDED(hr = spCustomMRU->Initialize(SZ_REGKEY_PRNCONNECTMRU, 26)) )
  589. {
  590. // query the specified interface
  591. hr = spCustomMRU->QueryInterface(riid, ppv);
  592. }
  593. return hr;
  594. }
  595. HRESULT CPrintersAutoCompleteSource::_AddCustomMRU(LPCTSTR psz)
  596. {
  597. HRESULT hr = E_INVALIDARG;
  598. CRefPtrCOM<IACLCustomMRU> spCustomMRU;
  599. if( psz &&
  600. SUCCEEDED(hr = _CreateCustomMRU(IID_IACLCustomMRU, spCustomMRU.GetPPV())) )
  601. {
  602. // just remember the MRU string
  603. hr = spCustomMRU->AddMRUString(psz);
  604. }
  605. return hr;
  606. }
  607. ////////////////////////////////////////////////
  608. // shell related services
  609. namespace ShellServices
  610. {
  611. // creates a PIDL to a printer in the local printers folder by using ParseDisplayName
  612. // see the description of CreatePrinterPIDL below.
  613. HRESULT CreatePrinterPIDL_Parse(HWND hwnd, LPCTSTR pszPrinterName, IShellFolder **ppLocalPrnFolder, LPITEMIDLIST *ppidlPrinter)
  614. {
  615. HRESULT hr = E_UNEXPECTED;
  616. CRefPtrCOM<IShellFolder> spDesktopFolder;
  617. CRefPtrCOM<IShellFolder> spPrnFolder;
  618. CAutoPtrPIDL pidlPrinters;
  619. CAutoPtrPIDL pidlPrinter;
  620. // attempt to get the fully qualified name (for parsing) of the printers folder
  621. if( SUCCEEDED(hr = SHGetDesktopFolder(&spDesktopFolder)) &&
  622. SUCCEEDED(hr = SHGetSpecialFolderLocation(NULL, CSIDL_PRINTERS, &pidlPrinters)) &&
  623. SUCCEEDED(hr = spDesktopFolder->BindToObject(pidlPrinters, 0, IID_IShellFolder, spPrnFolder.GetPPV())) )
  624. {
  625. ULONG uEaten = 0;
  626. ULONG uAttributes = SFGAO_DROPTARGET;
  627. // attempt parse the printer name into PIDL
  628. hr = spPrnFolder->ParseDisplayName(hwnd, 0, (LPOLESTR )pszPrinterName,
  629. &uEaten, &pidlPrinter, &uAttributes);
  630. if( SUCCEEDED(hr) )
  631. {
  632. if( ppLocalPrnFolder )
  633. {
  634. // return the local printers folder
  635. *ppLocalPrnFolder = spPrnFolder.Detach();
  636. }
  637. if( ppidlPrinter )
  638. {
  639. // return the printer PIDL
  640. *ppidlPrinter = pidlPrinter.Detach();
  641. }
  642. }
  643. }
  644. return hr;
  645. }
  646. // creates a PIDL to a printer in the local printers folder by enumerating the printers
  647. // see the description of CreatePrinterPIDL below.
  648. HRESULT CreatePrinterPIDL_Enum(HWND hwnd, LPCTSTR pszPrinterName, IShellFolder **ppLocalPrnFolder, LPITEMIDLIST *ppidlPrinter)
  649. {
  650. HRESULT hr = E_UNEXPECTED;
  651. CRefPtrCOM<IShellFolder> spDesktopFolder;
  652. CRefPtrCOM<IShellFolder> spPrnFolder;
  653. CRefPtrCOM<IEnumIDList> spPrnEnum;
  654. CAutoPtrPIDL pidlPrinters;
  655. STRRET str = {0};
  656. // attempt to get the fully qualified name (for parsing) of the printers folder
  657. if( SUCCEEDED(hr = SHGetDesktopFolder(&spDesktopFolder)) &&
  658. SUCCEEDED(hr = SHGetSpecialFolderLocation(NULL, CSIDL_PRINTERS, &pidlPrinters)) &&
  659. SUCCEEDED(hr = spDesktopFolder->BindToObject(pidlPrinters, 0, IID_IShellFolder, spPrnFolder.GetPPV())) &&
  660. SUCCEEDED(hr = spPrnFolder->EnumObjects(hwnd, SHCONTF_NONFOLDERS, &spPrnEnum)) )
  661. {
  662. TCHAR szBuffer[PRINTER_MAX_PATH];
  663. CAutoPtrPIDL pidlPrinter;
  664. ULONG uFetched = 0;
  665. for( ;; )
  666. {
  667. // get next printer
  668. hr = spPrnEnum->Next(1, &pidlPrinter, &uFetched);
  669. if( S_OK != hr )
  670. {
  671. // no more printers, or error
  672. break;
  673. }
  674. if( SUCCEEDED(hr = spPrnFolder->GetDisplayNameOf(pidlPrinter, SHGDN_FORPARSING, &str)) &&
  675. SUCCEEDED(hr = StrRetToBuf(&str, pidlPrinter, szBuffer, COUNTOF(szBuffer))) &&
  676. !lstrcmp(szBuffer, pszPrinterName) )
  677. {
  678. // found!
  679. if( ppLocalPrnFolder )
  680. {
  681. // return the local printers folder
  682. *ppLocalPrnFolder = spPrnFolder.Detach();
  683. }
  684. if( ppidlPrinter )
  685. {
  686. // return the printer PIDL
  687. *ppidlPrinter = pidlPrinter.Detach();
  688. }
  689. break;
  690. }
  691. // release the PIDL
  692. pidlPrinter = NULL;
  693. }
  694. if( hr == S_FALSE )
  695. {
  696. // printer name not found. setup the correct HRESULT.
  697. hr = HRESULT_FROM_WIN32(ERROR_INVALID_PRINTER_NAME);
  698. }
  699. }
  700. return hr;
  701. }
  702. // creates a PIDL to a printer in the local printers folder.
  703. // args:
  704. // [in] hwnd - window handle (in case we need to show UI - message box)
  705. // [in] pszPrinterName - full printer name.
  706. // [out] ppLocalPrnFolder - the printers folder (optional - may be NULL)
  707. // [out] ppidlPrinter - the PIDL of the printer pointed by pszPrinterName (optional - may be NULL)
  708. //
  709. // remarks:
  710. // pszPrinterName should be fully qualified printer name, i.e. if printer connection it should be
  711. // like "\\server\printer", if local printer just the printer name.
  712. //
  713. // returns:
  714. // S_OK on success, or OLE2 error otherwise
  715. HRESULT CreatePrinterPIDL(HWND hwnd, LPCTSTR pszPrinterName, IShellFolder **ppLocalPrnFolder, LPITEMIDLIST *ppidlPrinter)
  716. {
  717. // attempt to obtain the printer PIDL by parsing first - it's much quicker.
  718. HRESULT hr = CreatePrinterPIDL_Parse(hwnd, pszPrinterName, ppLocalPrnFolder, ppidlPrinter);
  719. if( E_NOTIMPL == hr )
  720. {
  721. // if parsing is not implemented then go ahead and enum the printers - slower.
  722. hr = CreatePrinterPIDL_Enum(hwnd, pszPrinterName, ppLocalPrnFolder, ppidlPrinter);
  723. }
  724. return hr;
  725. }
  726. // loads a popup menu
  727. HMENU LoadPopupMenu(HINSTANCE hInstance, UINT id, UINT uSubOffset)
  728. {
  729. HMENU hMenuPopup = NULL;
  730. CAutoHandleMenu shMenuParent = LoadMenu(hInstance, MAKEINTRESOURCE(id));
  731. if( shMenuParent && (hMenuPopup = GetSubMenu(shMenuParent, uSubOffset)) )
  732. {
  733. // tear off our submenu before destroying the parent
  734. RemoveMenu(shMenuParent, uSubOffset, MF_BYPOSITION);
  735. }
  736. return hMenuPopup;
  737. }
  738. // initializes enum printer's autocomplete
  739. HRESULT InitPrintersAutoComplete(HWND hwndEdit)
  740. {
  741. HRESULT hr = E_INVALIDARG;
  742. if( hwndEdit )
  743. {
  744. // create an autocomplete object
  745. CRefPtrCOM<IAutoComplete> spAC; // auto complete interface
  746. CRefPtrCOM<IAutoComplete2> spAC2; // auto complete 2 interface
  747. CRefPtrCOM<IUnknown> spACS; // auto complete source (IEnumString & IACList)
  748. // initialize all the objects & hook them up
  749. if( SUCCEEDED(hr = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER,
  750. IID_IAutoComplete, (void**)&spAC)) &&
  751. SUCCEEDED(hr = CPrintersACS_CreateInstance(&spACS)) &&
  752. SUCCEEDED(hr = spAC->Init(hwndEdit, spACS, NULL, NULL)) &&
  753. SUCCEEDED(hr = spAC->QueryInterface(IID_IAutoComplete2, (void **)&spAC2)) &&
  754. SUCCEEDED(hr = spAC2->SetOptions(ACO_AUTOSUGGEST)) )
  755. {
  756. hr = S_OK;
  757. }
  758. }
  759. return hr;
  760. }
  761. // helpers for the Enum* idioms
  762. HRESULT EnumPrintersWrap(DWORD dwFlags, DWORD dwLevel, LPCTSTR pszName, BYTE **ppBuffer, DWORD *pcReturned)
  763. {
  764. HRESULT hr = E_INVALIDARG;
  765. if( ppBuffer && pcReturned )
  766. {
  767. int iTry = -1;
  768. DWORD cbNeeded = 0;
  769. DWORD cReturned = 0;
  770. CAutoPtrArray<BYTE> pData;
  771. BOOL bStatus = FALSE;
  772. for( ;; )
  773. {
  774. if( iTry++ >= ENUM_MAX_RETRY )
  775. {
  776. // max retry count reached. this is also
  777. // considered out of memory case
  778. pData = NULL;
  779. break;
  780. }
  781. // call EnumPrinters...
  782. bStatus = EnumPrinters(dwFlags, const_cast<LPTSTR>(pszName), dwLevel,
  783. pData, cbNeeded, &cbNeeded, &cReturned);
  784. if( !bStatus && ERROR_INSUFFICIENT_BUFFER == GetLastError() && cbNeeded )
  785. {
  786. // buffer too small case
  787. pData = new BYTE[cbNeeded];
  788. continue;
  789. }
  790. break;
  791. }
  792. // setup the error code properly
  793. hr = bStatus ? S_OK : GetLastError() != ERROR_SUCCESS ? HRESULT_FROM_WIN32(GetLastError()) :
  794. !pData ? E_OUTOFMEMORY : E_FAIL;
  795. // setup the out parameters
  796. if( SUCCEEDED(hr) )
  797. {
  798. *ppBuffer = pData.Detach();
  799. *pcReturned = cReturned;
  800. }
  801. else
  802. {
  803. *ppBuffer = NULL;
  804. *pcReturned = 0;
  805. }
  806. }
  807. return hr;
  808. }
  809. // helpers for the GetJob API - see the SDK for mor information.
  810. HRESULT GetJobWrap(HANDLE hPrinter, DWORD JobId, DWORD dwLevel, BYTE **ppBuffer, DWORD *pcReturned)
  811. {
  812. HRESULT hr = E_INVALIDARG;
  813. if( ppBuffer && pcReturned )
  814. {
  815. int iTry = -1;
  816. DWORD cbNeeded = 0;
  817. CAutoPtrArray<BYTE> pData;
  818. BOOL bStatus = FALSE;
  819. for( ;; )
  820. {
  821. if( iTry++ >= ENUM_MAX_RETRY )
  822. {
  823. // max retry count reached. this is also
  824. // considered out of memory case
  825. pData = NULL;
  826. break;
  827. }
  828. // call GetJob...
  829. bStatus = GetJob(hPrinter, JobId, dwLevel, pData, cbNeeded, &cbNeeded);
  830. if( !bStatus && ERROR_INSUFFICIENT_BUFFER == GetLastError() && cbNeeded )
  831. {
  832. // buffer too small case
  833. pData = new BYTE[cbNeeded];
  834. continue;
  835. }
  836. break;
  837. }
  838. // setup the error code properly
  839. hr = bStatus ? S_OK : GetLastError() != ERROR_SUCCESS ? HRESULT_FROM_WIN32(GetLastError()) :
  840. !pData ? E_OUTOFMEMORY : E_FAIL;
  841. // setup the out parameters
  842. if( SUCCEEDED(hr) )
  843. {
  844. *ppBuffer = pData.Detach();
  845. *pcReturned = cbNeeded;
  846. }
  847. else
  848. {
  849. *ppBuffer = NULL;
  850. *pcReturned = 0;
  851. }
  852. }
  853. return hr;
  854. }
  855. typedef NET_API_STATUS
  856. type_NetAPI_NetShareEnum(
  857. LPWSTR servername,
  858. DWORD level,
  859. LPBYTE *bufptr,
  860. DWORD prefmaxlen,
  861. LPDWORD entriesread,
  862. LPDWORD totalentries,
  863. LPDWORD resume_handle
  864. );
  865. typedef NET_API_STATUS
  866. type_NetAPI_NetApiBufferFree(
  867. LPVOID Buffer
  868. );
  869. typedef NET_API_STATUS
  870. type_NetAPI_NetApiBufferSize(
  871. LPVOID Buffer,
  872. LPDWORD ByteCount
  873. );
  874. // enumerates the shared resources on a server, for more info see SDK for NetShareEnum API.
  875. HRESULT NetAPI_EnumShares(LPCTSTR pszServer, DWORD dwLevel, BYTE **ppBuffer, DWORD *pcReturned)
  876. {
  877. HRESULT hr = E_INVALIDARG;
  878. if( ppBuffer && pcReturned )
  879. {
  880. hr = E_FAIL;
  881. *pcReturned = 0;
  882. *ppBuffer = NULL;
  883. LPBYTE pNetBuf = NULL;
  884. DWORD dwRead, dwTemp;
  885. CDllLoader dll(TEXT("netapi32.dll"));
  886. if( dll )
  887. {
  888. // netapi32.dll loaded here...
  889. type_NetAPI_NetShareEnum *pfnNetShareEnum = (type_NetAPI_NetShareEnum *)dll.GetProcAddress("NetShareEnum");
  890. type_NetAPI_NetApiBufferSize *pfnNetApiBufferSize = (type_NetAPI_NetApiBufferSize *)dll.GetProcAddress("NetApiBufferSize");
  891. type_NetAPI_NetApiBufferFree *pfnNetApiBufferFree = (type_NetAPI_NetApiBufferFree *)dll.GetProcAddress("NetApiBufferFree");
  892. if( pfnNetShareEnum && pfnNetApiBufferSize && pfnNetApiBufferFree &&
  893. NERR_Success == pfnNetShareEnum(const_cast<LPTSTR>(pszServer), dwLevel,
  894. &pNetBuf, MAX_PREFERRED_LENGTH, &dwRead, &dwTemp, NULL) &&
  895. dwRead && pNetBuf &&
  896. NERR_Success == pfnNetApiBufferSize(pNetBuf, &dwTemp) )
  897. {
  898. *ppBuffer = new BYTE[dwTemp];
  899. if( *ppBuffer )
  900. {
  901. // copy the bits first
  902. memcpy(*ppBuffer, pNetBuf, dwTemp);
  903. // adjust the pointers here - a little bit ugly, but works
  904. for( DWORD dw = 0; dw < dwRead; dw++ )
  905. {
  906. // adjust shi1_netname
  907. reinterpret_cast<SHARE_INFO_1*>(*ppBuffer)[dw].shi1_netname =
  908. reinterpret_cast<LPWSTR>(
  909. (*ppBuffer) +
  910. (reinterpret_cast<BYTE*>(
  911. reinterpret_cast<SHARE_INFO_1*>(pNetBuf)[dw].shi1_netname) -
  912. pNetBuf));
  913. // adjust shi1_remark
  914. reinterpret_cast<SHARE_INFO_1*>(*ppBuffer)[dw].shi1_remark =
  915. reinterpret_cast<LPWSTR>(
  916. (*ppBuffer) +
  917. (reinterpret_cast<BYTE*>(
  918. reinterpret_cast<SHARE_INFO_1*>(pNetBuf)[dw].shi1_remark) -
  919. pNetBuf));
  920. }
  921. // number of structures returned
  922. *pcReturned = dwRead;
  923. }
  924. hr = ((*ppBuffer) ? S_OK : E_OUTOFMEMORY);
  925. CHECK(NERR_Success == pfnNetApiBufferFree(pNetBuf));
  926. }
  927. }
  928. }
  929. if( E_FAIL == hr && ERROR_SUCCESS != GetLastError() )
  930. {
  931. // if failed, let's be more spcific about what the error is...
  932. hr = HRESULT_FROM_WIN32(GetLastError());
  933. }
  934. return hr;
  935. }
  936. } // namespace ShellServices
  937. // utility functions
  938. HRESULT LoadXMLDOMDoc(LPCTSTR pszURL, IXMLDOMDocument **ppXMLDoc)
  939. {
  940. HRESULT hr = E_INVALIDARG;
  941. CRefPtrCOM<IXMLDOMDocument> spXMLDoc;
  942. if( pszURL && ppXMLDoc )
  943. {
  944. *ppXMLDoc = NULL;
  945. // create an instance of XMLDOM
  946. hr = CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument, (void **)&spXMLDoc);
  947. if( SUCCEEDED(hr) )
  948. {
  949. CComVariant xmlSource(pszURL);
  950. if( VT_BSTR == xmlSource.vt )
  951. {
  952. // just load the XML document here
  953. VARIANT_BOOL fIsSuccessful = VARIANT_TRUE;
  954. hr = spXMLDoc->load(xmlSource, &fIsSuccessful);
  955. if( S_FALSE == hr || VARIANT_FALSE == fIsSuccessful )
  956. {
  957. // this isn't a valid XML file.
  958. hr = E_FAIL;
  959. }
  960. else
  961. {
  962. // everything looks successful here - just return the XML document
  963. *ppXMLDoc = spXMLDoc.Detach();
  964. }
  965. }
  966. else
  967. {
  968. // xmlSource failed to allocate the string
  969. hr = E_OUTOFMEMORY;
  970. }
  971. }
  972. }
  973. return hr;
  974. }
  975. HRESULT CreateStreamFromURL(LPCTSTR pszURL, IStream **pps)
  976. {
  977. HRESULT hr = E_INVALIDARG;
  978. if( pszURL && pps )
  979. {
  980. *pps = NULL;
  981. TCHAR szBuf[INTERNET_MAX_SCHEME_LENGTH];
  982. DWORD cch = ARRAYSIZE(szBuf);
  983. if( SUCCEEDED(hr = CoInternetParseUrl(pszURL, PARSE_SCHEMA, 0, szBuf, cch, &cch, 0)) &&
  984. 0 == lstrcmp(szBuf, TEXT("res")) )
  985. {
  986. // check if this is a res:// URL to handle explicitly since
  987. // this protocol doesn't report filename and therefore can't
  988. // be used in conditions where caching is required - we can't
  989. // call URLOpenBlockingStream - use alternatives.
  990. // not impl. yet...
  991. ASSERT(FALSE);
  992. }
  993. hr = URLOpenBlockingStream(NULL, pszURL, pps, 0, NULL);
  994. }
  995. return hr;
  996. }
  997. HRESULT CreateStreamFromResource(LPCTSTR pszModule, LPCTSTR pszResType, LPCTSTR pszResName, IStream **pps)
  998. {
  999. HRESULT hr = E_INVALIDARG;
  1000. if( pszResType && pszResName )
  1001. {
  1002. hr = E_FAIL;
  1003. *pps = NULL;
  1004. HINSTANCE hModule = NULL;
  1005. if( (NULL == pszModule) || (hModule = LoadLibrary(pszModule)) )
  1006. {
  1007. HRSRC hHint = NULL;
  1008. ULONG uSize = 0;
  1009. if( (hHint = FindResource(hModule, pszResName, pszResType)) &&
  1010. (uSize = SizeofResource(hModule, hHint)) )
  1011. {
  1012. HGLOBAL hResData = LoadResource(hModule, hHint);
  1013. if( hResData )
  1014. {
  1015. LPVOID lpResData = LockResource(hResData);
  1016. if( lpResData )
  1017. {
  1018. if( (*pps = SHCreateMemStream(reinterpret_cast<LPBYTE>(lpResData), uSize)) )
  1019. {
  1020. hr = S_OK;
  1021. }
  1022. UnlockResource(lpResData);
  1023. }
  1024. FreeResource(hResData);
  1025. }
  1026. }
  1027. }
  1028. if( hModule )
  1029. {
  1030. FreeLibrary(hModule);
  1031. }
  1032. }
  1033. return hr;
  1034. }
  1035. HRESULT Gdiplus2HRESULT(Gdiplus::Status status)
  1036. {
  1037. // can't think of a better way to do this now
  1038. HRESULT hr = E_FAIL;
  1039. switch( status )
  1040. {
  1041. case Gdiplus::Ok:
  1042. hr = S_OK;
  1043. break;
  1044. case Gdiplus::InvalidParameter:
  1045. hr = E_INVALIDARG;
  1046. break;
  1047. case Gdiplus::OutOfMemory:
  1048. hr = E_OUTOFMEMORY;
  1049. break;
  1050. case Gdiplus::InsufficientBuffer:
  1051. hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
  1052. break;
  1053. case Gdiplus::Aborted:
  1054. hr = E_ABORT;
  1055. break;
  1056. case Gdiplus::ObjectBusy:
  1057. hr = E_PENDING;
  1058. break;
  1059. case Gdiplus::FileNotFound:
  1060. hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
  1061. break;
  1062. case Gdiplus::AccessDenied:
  1063. hr = E_ACCESSDENIED;
  1064. break;
  1065. case Gdiplus::UnknownImageFormat:
  1066. hr = HRESULT_FROM_WIN32(ERROR_INVALID_PIXEL_FORMAT);
  1067. break;
  1068. case Gdiplus::NotImplemented:
  1069. hr = E_NOTIMPL;
  1070. break;
  1071. case Gdiplus::Win32Error:
  1072. hr = HRESULT_FROM_WIN32(GetLastError());
  1073. break;
  1074. case Gdiplus::ValueOverflow:
  1075. case Gdiplus::FontFamilyNotFound:
  1076. case Gdiplus::FontStyleNotFound:
  1077. case Gdiplus::NotTrueTypeFont:
  1078. case Gdiplus::UnsupportedGdiplusVersion:
  1079. case Gdiplus::GdiplusNotInitialized:
  1080. case Gdiplus::WrongState:
  1081. break;
  1082. }
  1083. return hr;
  1084. }
  1085. HRESULT LoadAndScaleBmp(LPCTSTR pszURL, UINT nWidth, UINT nHeight, Gdiplus::Bitmap **ppBmp)
  1086. {
  1087. CRefPtrCOM<IStream> spStream;
  1088. HRESULT hr = CreateStreamFromURL(pszURL, &spStream);
  1089. if( SUCCEEDED(hr) )
  1090. {
  1091. hr = LoadAndScaleBmp(spStream, nWidth, nHeight, ppBmp);
  1092. }
  1093. return hr;
  1094. }
  1095. HRESULT LoadAndScaleBmp(IStream *pStream, UINT nWidth, UINT nHeight, Gdiplus::Bitmap **ppBmp)
  1096. {
  1097. HRESULT hr = E_INVALIDARG;
  1098. if( pStream && nWidth && nHeight && ppBmp )
  1099. {
  1100. hr = E_FAIL;
  1101. *ppBmp = NULL;
  1102. Gdiplus::Bitmap bmp(pStream);
  1103. if( SUCCEEDED(hr = Gdiplus2HRESULT(bmp.GetLastStatus())) )
  1104. {
  1105. hr = E_OUTOFMEMORY;
  1106. CAutoPtr<Gdiplus::Bitmap> spBmpNew = new Gdiplus::Bitmap(nWidth, nHeight);
  1107. if( spBmpNew && SUCCEEDED(hr = Gdiplus2HRESULT(spBmpNew->GetLastStatus())) )
  1108. {
  1109. Gdiplus::Graphics g(spBmpNew);
  1110. if( SUCCEEDED(hr = Gdiplus2HRESULT(g.GetLastStatus())) )
  1111. {
  1112. if( SUCCEEDED(hr = g.DrawImage(&bmp, 0, 0, nWidth, nHeight)) )
  1113. {
  1114. *ppBmp = spBmpNew.Detach();
  1115. hr = S_OK;
  1116. }
  1117. }
  1118. }
  1119. }
  1120. }
  1121. return hr;
  1122. }
  1123. //
  1124. // This function is trying to get the last active popup of the top
  1125. // level owner of the current thread active window.
  1126. //
  1127. HRESULT GetCurrentThreadLastPopup(HWND *phwnd)
  1128. {
  1129. HRESULT hr = E_INVALIDARG;
  1130. if( phwnd )
  1131. {
  1132. hr = E_FAIL;
  1133. if( NULL == *phwnd )
  1134. {
  1135. // if *phwnd is NULL then get the current thread active window
  1136. GUITHREADINFO ti = {0};
  1137. ti.cbSize = sizeof(ti);
  1138. if( GetGUIThreadInfo(0, &ti) && ti.hwndActive )
  1139. {
  1140. *phwnd = ti.hwndActive;
  1141. }
  1142. }
  1143. if( *phwnd )
  1144. {
  1145. HWND hwndOwner, hwndParent;
  1146. // climb up to the top parent in case it's a child window...
  1147. while( hwndParent = GetParent(*phwnd) )
  1148. {
  1149. *phwnd = hwndParent;
  1150. }
  1151. // get the owner in case the top parent is owned
  1152. hwndOwner = GetWindow(*phwnd, GW_OWNER);
  1153. if( hwndOwner )
  1154. {
  1155. *phwnd = hwndOwner;
  1156. }
  1157. // get the last popup of the owner of the top level parent window
  1158. *phwnd = GetLastActivePopup(*phwnd);
  1159. hr = (*phwnd) ? S_OK : E_FAIL;
  1160. }
  1161. }
  1162. return hr;
  1163. }