1 /* 2 * ReactOS Explorer 3 * 4 * Copyright 2006 - 2007 Thomas Weidenmueller <w3seek@reactos.org> 5 * 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. 10 * 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. 15 * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA 19 */ 20 21 #include "precomp.h" 22 23 #include <shdeprecated.h> 24 25 /***************************************************************************** 26 ** ITrayBandSite ************************************************************ 27 *****************************************************************************/ 28 29 // WARNING: Can't use ATL for this class due to our ATL not fully supporting the AGGREGATION functions needed for this class to be an "outer" class 30 // it works just fine this way. 31 class CTrayBandSite : 32 public ITrayBandSite, 33 public IBandSite, 34 public IBandSiteStreamCallback 35 /* TODO: IWinEventHandler */ 36 { 37 volatile LONG m_RefCount; 38 39 CComPtr<ITrayWindow> m_Tray; 40 41 CComPtr<IUnknown> m_Inner; 42 CComPtr<IBandSite> m_BandSite; 43 CComPtr<IDeskBand> m_TaskBand; 44 CComPtr<IWinEventHandler> m_WindowEventHandler; 45 CComPtr<IContextMenu> m_ContextMenu; 46 47 HWND m_Rebar; 48 49 union 50 { 51 DWORD dwFlags; 52 struct 53 { 54 DWORD Locked : 1; 55 }; 56 }; 57 58 public: 59 60 virtual ULONG STDMETHODCALLTYPE AddRef() 61 { 62 return InterlockedIncrement(&m_RefCount); 63 } 64 65 virtual ULONG STDMETHODCALLTYPE Release() 66 { 67 ULONG Ret = InterlockedDecrement(&m_RefCount); 68 69 if (Ret == 0) 70 delete this; 71 72 return Ret; 73 } 74 75 virtual HRESULT STDMETHODCALLTYPE QueryInterface(IN REFIID riid, OUT LPVOID *ppvObj) 76 { 77 if (ppvObj == NULL) 78 return E_POINTER; 79 80 if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IBandSiteHelper)) 81 { 82 // return IBandSiteStreamCallback's IUnknown 83 *ppvObj = static_cast<IBandSiteStreamCallback*>(this); 84 } 85 else if (IsEqualIID(riid, IID_IBandSite)) 86 { 87 *ppvObj = static_cast<IBandSite*>(this); 88 } 89 else if (IsEqualIID(riid, IID_IWinEventHandler)) 90 { 91 TRACE("ITaskBandSite: IWinEventHandler queried!\n"); 92 *ppvObj = NULL; 93 return E_NOINTERFACE; 94 } 95 else if (m_Inner != NULL) 96 { 97 return m_Inner->QueryInterface(riid, ppvObj); 98 } 99 else 100 { 101 *ppvObj = NULL; 102 return E_NOINTERFACE; 103 } 104 105 AddRef(); 106 return S_OK; 107 } 108 109 public: 110 CTrayBandSite() : 111 m_RefCount(0), 112 m_Rebar(NULL) 113 { 114 } 115 116 virtual ~CTrayBandSite() { } 117 118 virtual HRESULT STDMETHODCALLTYPE OnLoad( 119 IN OUT IStream *pStm, 120 IN REFIID riid, 121 OUT PVOID *pvObj) 122 { 123 LARGE_INTEGER liPosZero; 124 ULARGE_INTEGER liCurrent; 125 CLSID clsid; 126 ULONG ulRead; 127 HRESULT hRet; 128 129 /* NOTE: Callback routine called by the shell while loading the task band 130 stream. We use it to intercept the default behavior when the task 131 band is loaded from the stream. 132 133 NOTE: riid always points to IID_IUnknown! This is because the shell hasn't 134 read anything from the stream and therefore doesn't know what CLSID 135 it's dealing with. We'll have to find it out ourselves by reading 136 the GUID from the stream. */ 137 138 /* Read the current position of the stream, we'll have to reset it everytime 139 we read a CLSID that's not the task band... */ 140 ZeroMemory(&liPosZero, sizeof(liPosZero)); 141 hRet = pStm->Seek(liPosZero, STREAM_SEEK_CUR, &liCurrent); 142 143 if (SUCCEEDED(hRet)) 144 { 145 /* Now let's read the CLSID from the stream and see if it's our task band */ 146 hRet = pStm->Read(&clsid, (ULONG)sizeof(clsid), &ulRead); 147 148 if (SUCCEEDED(hRet) && ulRead == sizeof(clsid)) 149 { 150 if (IsEqualGUID(clsid, CLSID_ITaskBand)) 151 { 152 ASSERT(m_TaskBand != NULL); 153 /* We're trying to load the task band! Let's create it... */ 154 155 hRet = m_TaskBand->QueryInterface( 156 riid, 157 pvObj); 158 if (SUCCEEDED(hRet)) 159 { 160 /* Load the stream */ 161 TRACE("IBandSiteStreamCallback::OnLoad intercepted the task band CLSID!\n"); 162 } 163 164 return hRet; 165 } 166 } 167 } 168 169 /* Reset the position and let the shell do all the work for us */ 170 hRet = pStm->Seek( 171 *(LARGE_INTEGER*) &liCurrent, 172 STREAM_SEEK_SET, 173 NULL); 174 if (SUCCEEDED(hRet)) 175 { 176 /* Let the shell handle everything else for us :) */ 177 hRet = OleLoadFromStream(pStm, 178 riid, 179 pvObj); 180 } 181 182 if (!SUCCEEDED(hRet)) 183 { 184 TRACE("IBandSiteStreamCallback::OnLoad(0x%p, 0x%p, 0x%p) returns 0x%x\n", pStm, riid, pvObj, hRet); 185 } 186 187 return hRet; 188 } 189 190 virtual HRESULT STDMETHODCALLTYPE OnSave( 191 IN OUT IUnknown *pUnk, 192 IN OUT IStream *pStm) 193 { 194 /* NOTE: Callback routine called by the shell while saving the task band 195 stream. We use it to intercept the default behavior when the task 196 band is saved to the stream */ 197 /* FIXME: Implement */ 198 TRACE("IBandSiteStreamCallback::OnSave(0x%p, 0x%p) returns E_NOTIMPL\n", pUnk, pStm); 199 return E_NOTIMPL; 200 } 201 202 virtual HRESULT STDMETHODCALLTYPE IsTaskBand(IN IUnknown *punk) 203 { 204 return IsSameObject(m_BandSite, punk); 205 } 206 207 virtual HRESULT STDMETHODCALLTYPE ProcessMessage( 208 IN HWND hWnd, 209 IN UINT uMsg, 210 IN WPARAM wParam, 211 IN LPARAM lParam, 212 OUT LRESULT *plResult) 213 { 214 HRESULT hRet; 215 216 ASSERT(m_Rebar != NULL); 217 218 /* Custom task band behavior */ 219 switch (uMsg) 220 { 221 case WM_NOTIFY: 222 { 223 const NMHDR *nmh = (const NMHDR *) lParam; 224 225 if (nmh->hwndFrom == m_Rebar) 226 { 227 switch (nmh->code) 228 { 229 case NM_NCHITTEST: 230 { 231 LPNMMOUSE nmm = (LPNMMOUSE) lParam; 232 233 if (nmm->dwHitInfo == RBHT_CLIENT || nmm->dwHitInfo == RBHT_NOWHERE || 234 nmm->dwItemSpec == (DWORD_PTR) -1) 235 { 236 /* Make the rebar control appear transparent so the user 237 can drag the tray window */ 238 *plResult = HTTRANSPARENT; 239 } 240 return S_OK; 241 } 242 243 case RBN_MINMAX: 244 /* Deny if an Administrator disabled this "feature" */ 245 *plResult = (SHRestricted(REST_NOMOVINGBAND) != 0); 246 return S_OK; 247 } 248 } 249 250 //TRACE("ITrayBandSite::ProcessMessage: WM_NOTIFY for 0x%p, From: 0x%p, Code: NM_FIRST-%u...\n", hWnd, nmh->hwndFrom, NM_FIRST - nmh->code); 251 break; 252 } 253 } 254 255 /* Forward to the shell's IWinEventHandler interface to get the default shell behavior! */ 256 if (!m_WindowEventHandler) 257 return E_FAIL; 258 259 /*TRACE("Calling IWinEventHandler::ProcessMessage(0x%p, 0x%x, 0x%p, 0x%p, 0x%p) hWndRebar=0x%p\n", hWnd, uMsg, wParam, lParam, plResult, hWndRebar);*/ 260 hRet = m_WindowEventHandler->OnWinEvent(hWnd, uMsg, wParam, lParam, plResult); 261 262 #if 0 263 if (FAILED(hRet)) 264 { 265 if (uMsg == WM_NOTIFY) 266 { 267 const NMHDR *nmh = (const NMHDR *) lParam; 268 ERR("ITrayBandSite->IWinEventHandler::ProcessMessage: WM_NOTIFY for 0x%p, From: 0x%p, Code: NM_FIRST-%u returned 0x%x\n", hWnd, nmh->hwndFrom, NM_FIRST - nmh->code, hRet); 269 } 270 else 271 { 272 ERR("ITrayBandSite->IWinEventHandler::ProcessMessage(0x%p,0x%x,0x%p,0x%p,0x%p->0x%p) returned: 0x%x\n", hWnd, uMsg, wParam, lParam, plResult, *plResult, hRet); 273 } 274 } 275 #endif 276 277 return hRet; 278 } 279 280 virtual HRESULT STDMETHODCALLTYPE AddContextMenus( 281 IN HMENU hmenu, 282 IN UINT indexMenu, 283 IN UINT idCmdFirst, 284 IN UINT idCmdLast, 285 IN UINT uFlags, 286 OUT IContextMenu **ppcm) 287 { 288 HRESULT hRet; 289 290 if (m_ContextMenu == NULL) 291 { 292 /* Cache the context menu so we don't need to CoCreateInstance all the time... */ 293 hRet = _CBandSiteMenu_CreateInstance(IID_PPV_ARG(IContextMenu, &m_ContextMenu)); 294 if (FAILED_UNEXPECTEDLY(hRet)) 295 return hRet; 296 297 hRet = IUnknown_SetOwner(m_ContextMenu, (IBandSite*)this); 298 if (FAILED_UNEXPECTEDLY(hRet)) 299 return hRet; 300 } 301 302 if (ppcm != NULL) 303 { 304 m_ContextMenu->AddRef(); 305 *ppcm = m_ContextMenu; 306 } 307 308 /* Add the menu items */ 309 hRet = m_ContextMenu->QueryContextMenu(hmenu, indexMenu, idCmdFirst, idCmdLast, uFlags); 310 if (FAILED_UNEXPECTEDLY(hRet)) 311 return hRet; 312 313 return S_OK; 314 } 315 316 virtual HRESULT STDMETHODCALLTYPE Lock(IN BOOL bLock) 317 { 318 BOOL bPrevLocked = Locked; 319 BANDSITEINFO bsi; 320 HRESULT hRet; 321 322 ASSERT(m_BandSite != NULL); 323 324 if (bPrevLocked != bLock) 325 { 326 Locked = bLock; 327 328 bsi.dwMask = BSIM_STYLE; 329 bsi.dwStyle = (Locked ? BSIS_LOCKED | BSIS_NOGRIPPER : BSIS_AUTOGRIPPER); 330 331 hRet = m_BandSite->SetBandSiteInfo(&bsi); 332 if (SUCCEEDED(hRet)) 333 { 334 hRet = Update(); 335 } 336 337 return hRet; 338 } 339 340 return S_FALSE; 341 } 342 343 /*******************************************************************/ 344 345 virtual HRESULT STDMETHODCALLTYPE AddBand(IN IUnknown *punk) 346 { 347 /* Send the DBID_DELAYINIT command to initialize the band to be added */ 348 /* FIXME: Should be delayed */ 349 IUnknown_Exec(punk, IID_IDeskBand, DBID_DELAYINIT, 0, NULL, NULL); 350 351 HRESULT hr = m_BandSite->AddBand(punk); 352 if (FAILED_UNEXPECTEDLY(hr)) 353 return hr; 354 355 VARIANT vThemeName; 356 V_VT(&vThemeName) = VT_BSTR; 357 V_BSTR(&vThemeName) = SysAllocString(L"TaskBar"); 358 IUnknown_Exec(punk, 359 IID_IDeskBand, 360 DBID_SETWINDOWTHEME, 361 0, 362 &vThemeName, 363 NULL); 364 365 SysFreeString(V_BSTR(&vThemeName)); 366 367 return S_OK; 368 } 369 370 virtual HRESULT STDMETHODCALLTYPE EnumBands( 371 IN UINT uBand, 372 OUT DWORD *pdwBandID) 373 { 374 return m_BandSite->EnumBands(uBand, pdwBandID); 375 } 376 377 virtual HRESULT STDMETHODCALLTYPE QueryBand( 378 IN DWORD dwBandID, 379 OUT IDeskBand **ppstb, 380 OUT DWORD *pdwState, 381 OUT LPWSTR pszName, 382 IN int cchName) 383 { 384 HRESULT hRet; 385 IDeskBand *pstb = NULL; 386 387 hRet = m_BandSite->QueryBand( 388 dwBandID, 389 &pstb, 390 pdwState, 391 pszName, 392 cchName); 393 394 if (SUCCEEDED(hRet)) 395 { 396 hRet = IsSameObject(pstb, m_TaskBand); 397 if (hRet == S_OK) 398 { 399 /* Add the BSSF_UNDELETEABLE flag to pdwState because the task bar band shouldn't be deletable */ 400 if (pdwState != NULL) 401 *pdwState |= BSSF_UNDELETEABLE; 402 } 403 else if (!SUCCEEDED(hRet)) 404 { 405 pstb->Release(); 406 pstb = NULL; 407 } 408 409 if (ppstb != NULL) 410 *ppstb = pstb; 411 } 412 else if (ppstb != NULL) 413 *ppstb = NULL; 414 415 return hRet; 416 } 417 418 virtual HRESULT STDMETHODCALLTYPE SetBandState( 419 IN DWORD dwBandID, 420 IN DWORD dwMask, 421 IN DWORD dwState) 422 { 423 return m_BandSite->SetBandState(dwBandID, dwMask, dwState); 424 } 425 426 virtual HRESULT STDMETHODCALLTYPE RemoveBand( 427 IN DWORD dwBandID) 428 { 429 return m_BandSite->RemoveBand(dwBandID); 430 } 431 432 virtual HRESULT STDMETHODCALLTYPE GetBandObject( 433 IN DWORD dwBandID, 434 IN REFIID riid, 435 OUT VOID **ppv) 436 { 437 return m_BandSite->GetBandObject(dwBandID, riid, ppv); 438 } 439 440 virtual HRESULT STDMETHODCALLTYPE SetBandSiteInfo( 441 IN const BANDSITEINFO *pbsinfo) 442 { 443 return m_BandSite->SetBandSiteInfo(pbsinfo); 444 } 445 446 virtual HRESULT STDMETHODCALLTYPE GetBandSiteInfo( 447 IN OUT BANDSITEINFO *pbsinfo) 448 { 449 return m_BandSite->GetBandSiteInfo(pbsinfo); 450 } 451 452 virtual BOOL HasTaskBand() 453 { 454 CComPtr<IPersist> pBand; 455 CLSID BandCLSID; 456 DWORD dwBandID; 457 UINT uBand = 0; 458 459 /* Enumerate all bands */ 460 while (SUCCEEDED(m_BandSite->EnumBands(uBand, &dwBandID))) 461 { 462 if (SUCCEEDED(m_BandSite->GetBandObject(dwBandID, IID_PPV_ARG(IPersist, &pBand)))) 463 { 464 if (SUCCEEDED(pBand->GetClassID(&BandCLSID))) 465 { 466 if (IsEqualGUID(BandCLSID, CLSID_ITaskBand)) 467 { 468 return TRUE; 469 } 470 } 471 } 472 uBand++; 473 } 474 475 return FALSE; 476 } 477 478 virtual HRESULT Update() 479 { 480 return IUnknown_Exec(m_Inner, 481 IID_IDeskBand, 482 DBID_BANDINFOCHANGED, 483 0, 484 NULL, 485 NULL); 486 } 487 488 virtual VOID BroadcastOleCommandExec(REFGUID pguidCmdGroup, 489 DWORD nCmdID, 490 DWORD nCmdExecOpt, 491 VARIANTARG *pvaIn, 492 VARIANTARG *pvaOut) 493 { 494 IOleCommandTarget *pOct; 495 DWORD dwBandID; 496 UINT uBand = 0; 497 498 /* Enumerate all bands */ 499 while (SUCCEEDED(m_BandSite->EnumBands(uBand, &dwBandID))) 500 { 501 if (SUCCEEDED(m_BandSite->GetBandObject(dwBandID, IID_PPV_ARG(IOleCommandTarget, &pOct)))) 502 { 503 /* Execute the command */ 504 pOct->Exec( 505 &pguidCmdGroup, 506 nCmdID, 507 nCmdExecOpt, 508 pvaIn, 509 pvaOut); 510 511 pOct->Release(); 512 } 513 514 uBand++; 515 } 516 } 517 518 virtual HRESULT FinishInit() 519 { 520 /* Broadcast the DBID_FINISHINIT command */ 521 BroadcastOleCommandExec(IID_IDeskBand, DBID_FINISHINIT, 0, NULL, NULL); 522 523 return S_OK; 524 } 525 526 virtual HRESULT Show(IN BOOL bShow) 527 { 528 CComPtr<IDeskBarClient> pDbc; 529 HRESULT hRet; 530 531 hRet = m_BandSite->QueryInterface(IID_PPV_ARG(IDeskBarClient, &pDbc)); 532 if (SUCCEEDED(hRet)) 533 { 534 hRet = pDbc->UIActivateDBC(bShow ? DBC_SHOW : DBC_HIDE); 535 } 536 537 return hRet; 538 } 539 540 virtual HRESULT LoadFromStream(IN OUT IStream *pStm) 541 { 542 CComPtr<IPersistStream> pPStm; 543 HRESULT hRet; 544 545 ASSERT(m_BandSite != NULL); 546 547 /* We implement the undocumented COM interface IBandSiteStreamCallback 548 that the shell will query so that we can intercept and custom-load 549 the task band when it finds the task band's CLSID (which is internal). 550 This way we can prevent the shell from attempting to CoCreateInstance 551 the (internal) task band, resulting in a failure... */ 552 hRet = m_BandSite->QueryInterface(IID_PPV_ARG(IPersistStream, &pPStm)); 553 if (SUCCEEDED(hRet)) 554 { 555 hRet = pPStm->Load(pStm); 556 TRACE("->Load() returned 0x%x\n", hRet); 557 } 558 559 return hRet; 560 } 561 562 virtual IStream * GetUserBandsStream(IN DWORD grfMode) 563 { 564 HKEY hkStreams; 565 IStream *Stream = NULL; 566 567 if (RegCreateKeyW(hkExplorer, 568 L"Streams", 569 &hkStreams) == ERROR_SUCCESS) 570 { 571 Stream = SHOpenRegStreamW(hkStreams, 572 L"Desktop", 573 L"TaskbarWinXP", 574 grfMode); 575 576 RegCloseKey(hkStreams); 577 } 578 579 return Stream; 580 } 581 582 virtual IStream * GetDefaultBandsStream(IN DWORD grfMode) 583 { 584 HKEY hkStreams; 585 IStream *Stream = NULL; 586 587 if (RegCreateKeyW(HKEY_LOCAL_MACHINE, 588 L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Streams", 589 &hkStreams) == ERROR_SUCCESS) 590 { 591 Stream = SHOpenRegStreamW(hkStreams, 592 L"Desktop", 593 L"Default Taskbar", 594 grfMode); 595 596 RegCloseKey(hkStreams); 597 } 598 599 return Stream; 600 } 601 602 virtual HRESULT Load() 603 { 604 IStream *pStm; 605 HRESULT hRet; 606 607 /* Try to load the user's settings */ 608 pStm = GetUserBandsStream(STGM_READ); 609 if (pStm != NULL) 610 { 611 hRet = LoadFromStream(pStm); 612 613 TRACE("Loaded user bands settings: 0x%x\n", hRet); 614 pStm->Release(); 615 } 616 else 617 hRet = E_FAIL; 618 619 /* If the user's settings couldn't be loaded, try with 620 default settings (ie. when the user logs in for the 621 first time! */ 622 if (!SUCCEEDED(hRet)) 623 { 624 pStm = GetDefaultBandsStream(STGM_READ); 625 if (pStm != NULL) 626 { 627 hRet = LoadFromStream(pStm); 628 629 TRACE("Loaded default user bands settings: 0x%x\n", hRet); 630 pStm->Release(); 631 } 632 else 633 hRet = E_FAIL; 634 } 635 636 return hRet; 637 } 638 639 HRESULT _Init(IN ITrayWindow *tray, IN IDeskBand* pTaskBand) 640 { 641 CComPtr<IDeskBarClient> pDbc; 642 CComPtr<IDeskBand> pDb; 643 CComPtr<IOleWindow> pOw; 644 HRESULT hRet; 645 646 m_Tray = tray; 647 m_TaskBand = pTaskBand; 648 649 /* Create the RebarBandSite */ 650 hRet = _CBandSite_CreateInstance(static_cast<IBandSite*>(this), IID_PPV_ARG(IUnknown, &m_Inner)); 651 if (FAILED_UNEXPECTEDLY(hRet)) 652 return hRet; 653 654 hRet = m_Inner->QueryInterface(IID_PPV_ARG(IBandSite, &m_BandSite)); 655 if (FAILED_UNEXPECTEDLY(hRet)) 656 return hRet; 657 658 hRet = m_Inner->QueryInterface(IID_PPV_ARG(IWinEventHandler, &m_WindowEventHandler)); 659 if (FAILED_UNEXPECTEDLY(hRet)) 660 return hRet; 661 662 hRet = m_Inner->QueryInterface(IID_PPV_ARG(IDeskBarClient, &pDbc)); 663 if (FAILED_UNEXPECTEDLY(hRet)) 664 return hRet; 665 666 667 668 669 /* Crete the rebar in the tray */ 670 hRet = pDbc->SetDeskBarSite(tray); 671 if (FAILED_UNEXPECTEDLY(hRet)) 672 return hRet; 673 674 hRet = pDbc->GetWindow(&m_Rebar); 675 if (FAILED_UNEXPECTEDLY(hRet)) 676 return hRet; 677 678 SetWindowStyle(m_Rebar, RBS_BANDBORDERS, 0); 679 680 /* Set the Desk Bar mode to the current one */ 681 DWORD dwMode = 0; 682 /* FIXME: We need to set the mode (and update) whenever the user docks 683 the tray window to another monitor edge! */ 684 if (!m_Tray->IsHorizontal()) 685 dwMode = DBIF_VIEWMODE_VERTICAL; 686 687 hRet = pDbc->SetModeDBC(dwMode); 688 689 /* Load the saved state of the task band site */ 690 /* FIXME: We should delay loading shell extensions, also see DBID_DELAYINIT */ 691 Load(); 692 693 /* Add the task bar band if it hasn't been added while loading */ 694 if (!HasTaskBand()) 695 { 696 hRet = m_BandSite->AddBand(m_TaskBand); 697 if (FAILED_UNEXPECTEDLY(hRet)) 698 return hRet; 699 } 700 701 /* Should we send this after showing it? */ 702 Update(); 703 704 /* FIXME: When should we send this? Does anyone care anyway? */ 705 FinishInit(); 706 707 /* Activate the band site */ 708 Show(TRUE); 709 710 return S_OK; 711 } 712 }; 713 /*******************************************************************/ 714 715 HRESULT CTrayBandSite_CreateInstance(IN ITrayWindow *tray, IN IDeskBand* pTaskBand, OUT ITrayBandSite** pBandSite) 716 { 717 HRESULT hr; 718 719 CTrayBandSite * tb = new CTrayBandSite(); 720 if (!tb) 721 return E_FAIL; 722 723 tb->AddRef(); 724 725 hr = tb->_Init(tray, pTaskBand); 726 if (FAILED_UNEXPECTEDLY(hr)) 727 { 728 tb->Release(); 729 return hr; 730 } 731 732 *pBandSite = tb; 733 734 return S_OK; 735 } 736