천객만래 [千客萬來] (It has an interminable succession of visitors)

[VC] IE에 사용자 툴바를 설치후에 기본으로 보이게 하는 방법





IE에 새 툴바밴드를 설치하고 새 IE를 실행했을때 기본으로 툴바가 IE에 보여지도록 하는 방법에 대한 문서이다. 혹자는 이것이 왜 필요하냐고 물을수도 있는데, 한마디로 필요하다. 

왜냐하면 설치를 했다면 당연히 눈여보여서 동작하는 것이 사용자의 기대동작이기 때문이다. 만약 IE에서 툴바를 켜는 방법을 모르는 사용자가 있다면 어떨까? 설치를 한 의미가 없어진다. 사용을 촉진시키고, UI를 보여줌으로써 관심을 일으키는 것이 마케팅일것이다.

1 툴바 DLL의 기본 설정을 이용하는 방법 

이 방법을 알아낸다면 밑에 있는 방법은 다 없어도 된다. 이 방법은 웹 검색과 구글검색으로도 쉽게 알아내지 못했다. IE의 툴바 레이아웃은 
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Toolbar
There are 3 sub keys: Explorer, ShellBrowser, WebBrowser'
In each of these sub-keys is the entry for ITBarLayout 
For IE Toolbars: The WebBrowser Sub-key directly affects IE.
For Windows Explorer Toolbars: The Explorer Sub-key directly affects Explorer.
The ShellBrowser sub-key may affect IE as well. Its effect on IE and Windows Explorer is unknown at this time, so if the first option doesn't work, delete the ITBarLayout value here as well.
Choose the relavent sub-key and right click where it says ITBarLayout and select Delete. 
The deleted value will automatically be recreated for you.

이런식으로 저장이 된다. 문제는 ITBarLayout에 레이아웃 구조체가 저장되는데 이 키를 지우면 모든 레이아웃 정보가 사라지고 IE가 디폴트로 툴바 레이아웃을 생성하는데, 이때 특정 툴바는 기본으로 뜬다는 것이다. 설치하고 나서 기본으로 뜨고 사용자가 안 보이게 하면 그때 부터 안 보인다. 이런 설정은 어떻게 하는 것일까? 만들때 어떤 옵션을 주는것 같기도 하고. 만약 방법을 찾게 되면 이 페이지에서 가장 강조되는 블럭으로 말들어 놓겠다.

2 html을 이용하는 방법 

이 방법은 해당 툴바를 Object 태그로 HTML에 삽입하는 방법이다. 다시 말해서 해당 COM객체를 IE에서 실행하라고 알리는 것이다. 설치가 끝나고 나서 인스톨러가 설치가 완료되었다는 깜찍하고 작은 IE를 띄우는것은 애교로 봐 줄만하다. 그 IE가 띄우는 페이지에 저 Object에 툴바의 클래스 아이디를 넣는다는 얘기이다. HTML에서는 COM객체를 로드할 것이고, 그 객체는 툴바DLL이기 때문에 당연히 IE에 리바영역에 나타날 것이다. 

문제점이있다. 어떤 툴바는 로드되지 않는다. 내가 뜨기를 원하는 툴바에서는 안 뜨고 다른 툴바는 성공적으로 뜨는 바람에 바로 다른 방법을 강구하기 시작했는데, 자기가 배포하려는 툴바가 이 방법으로 잘 되는지 확인해 볼 필요가 있다. 

주의할 점은 winxp sp2에서는 기본 보안설정으로는 DLL이 로드되지 않는다는 점이다., IE의 보안레벨에 의존적이다.

3 IE 메뉴를 이용하는 방법 

새로운 IE를 실행하면서 그 IE의 IEFrame핸들을 얻을 수 있는데 그 윈도우 핸들에 WM_COMMAND를 보내는 방법이다. 이 경우, 예를들면 Ctrl+N과 같은 IE의 기본적인 값은 spy++로 확인해 보면 항상 같다는 점을 알 수 있다. 그래서 테스트 결과 WM_COMMAND로 wParam에 그 값을 넣어서 보내면 정확히 그 메뉴가 실행된다. 이 점을 응용하면 보기-툴바에서 우리 툴바의 메뉴아이템의 ID를 구하기만 하면 되는 것이다. 그 ID는 항상 동적이다. 설치된 툴바의 갯수나 설치된 순서에 따라 다르기 때문이다. 그래서 IE의 메인메뉴를 HMENU를 구하고 거기서 모든 메뉴 아이템을 얻어서 우리가 원하는 메뉴 캡션을 찾아내면 그 메뉴 아이템의 ID가 우리가 찾는 ID가 되는 것이다.

[치명적 문제점]
메인메뉴의 HMENU 값을 구할 수가 없다? 일단 GetMenu(HWND) 는 메뉴가 없다. 왜 그럴까? IE의 메인메뉴는 메뉴바라고 하는 툴바에 올라가 있고, 팝업메뉴로 나타나기 때문이다. HMENU만 알아낸다면 확실히 효율적이고 안정적인 좋은 방법이다.

4 IWebBrowser2를 이용하는 방법 




STDMETHODIMP CShowBarObj::SetSite(IUnknown *pUnkSite)

(

 if (NULL != pUnkSite)

 {

  IWebBrowser2 *pBrowser = NULL;

  // Ensure that our site is an browser window

  HRESULT hr = pUnkSite->QueryInterface(IID_IWebBrowser2, (void **) &pBrowser);

  if (SUCCEEDED(hr))

  {

   // Display the band object (the Search bar in this case)

   VARIANT vtBandGUID, vtShow;

   vtBandGUID.vt = VT_BSTR;

   vtBandGUID.bstrVal = SysAllocString(OLESTR("{30D02401-6A81-11D0-8274-00C04FD5AE38}"));

   vtShow.vt = VT_BOOL;

   vtShow.boolVal = true;

   VARIANT vret;

   pBrowser->ShowBrowserBar(&vtBandGUID, &vtShow, &vret);

   SysFreeString(vtBandGUID.bstrVal);

   pBrowser->Release();

  }

 }

 return S_OK;

}




이 코드인데 이 코드를 툴바(툴바는 문제가 있는데 툴바는 켜지 않으면 DLL을 로드하지 않기 때문이다.)나 BHO에 넣으면 잘 동작한다. 그러나 IE를 실행하고 그 실행한 IE의 IWebBrowser2의 ShowBrowserBar를 호출하면 절대로 동작하지 않는다. 그것이 문제이다. 일단 그 이유를 모르겠다. 한가지 생각나는 이유라고는 TopLevel의 IWebBrowser2가 아니기 때문이 아닐까 싶기도 한데, 사실상 그런 이유때문은 아닌것 같다. 

아래 코드는 Activex에서 탑 레베의 IWebBrowser2를 얻는 코드이다. 나중에 참조해 볼 것.





#include <SHLGUID.h>

#define COMRELEASE(ptr)

if (ptr != NULL) 

{

ptr->Release();

   ptr = NULL;

}

IWebBrowser2 *browser = NULL;

STDMETHODIMP SetClientSite(IOleClientSite *pClientSite) 

{

  HRESULT hr = S_OK;

   IServiceProvider *isp, *isp2 = NULL;

    if (!pClientSite)

{

 COMRELEASE(browser);


else

{

hr = pClientSite->QueryInterface(IID_IServiceProvider, reinterpret_cast<void **>(&isp));

if (FAILED(hr)) 

{

hr = S_OK;

goto cleanup;

}

hr = isp->QueryService(SID_STopLevelBrowser, IID_IServiceProvider, reinterpret_cast<void **>(&isp2));

if (FAILED(hr))

{

hr = S_OK;

goto cleanup;

}

hr = isp2->QueryService(SID_SWebBrowserApp, IID_IWebBrowser2, reinterpret_cast<void **>(&browser));

if (FAILED(hr)) 

{

hr = S_OK;

goto cleanup;

}

cleanup:

// Free resources.

COMRELEASE(isp);

COMRELEASE(isp2);

return hr;

}

}


5 BHO를 이용하는 방법 

이 방법이 그나마 안정적이고 좋은방법 같다. ShowBrowserBar를 BHO에서 동작하도록 하는 것이다. BHO의 SetSite에서 우리가 원하는 툴바를 보이게 만들고 DLL을 등록해제해서 (레지스트리 삭제) 다음 IE에서는 이 DLL을 다시 로드하지 않도록 하는 방법이다. 

bho등록
IE를 안 보이게 실행
로드된 bho에서 우리가 원하는 툴바를 보이게 함
IE를 종료함 WM_CLOSE를 보내거나 ->Quit를 호출함
bho등록해제
bho dll삭제

와 같은 순서로 동작한다.

6 레이아웃 레지스트리를 조작하는 방법

레지스트리는 바이네리 데이타이다. 상식적으로 생각했을때 그건 IE내부에서만 사용되는 어떤 구조체일 것이고, 그 내막은 파악하기는 소스가 없다면 리버스엔지니어링이라고 불리는 삽질을 하는 수 밖에 없는 법이다. 당행스럽게도 나에겐 win2k src가 있었는데 없었다면 감히 삽질하려고 시도조차 하지 않았을 것이다. 그런데 그런 시도를 이미 한 사람이 있었으니 내가 감탄하지 않을 수 없었다. 구조체는


typedef struct tagCOOLBARSAVE
{
  UINT cbVer;
   UINT uiMaxTBWidth;
    UINT uiMaxQLWidth;
#ifdef UNIX
    BITBOOL fUnUsed : 28; // unused
#endif
  BITBOOL fVertical : 1; // The bar is oriented vertically
   BITBOOL fNoText :1; // "NoText"
    BITBOOL fList : 1; 
// toolbar is TBSTYLE_LIST (text on right) + TBSTYLE_EX_MIXEDBUTTONS
BITBOOL fAutoHide : 1; // Auto hide toolbar in theater mode
    BITBOOL fStatusBar : 1; // Status bar in theater mode
BITBOOL fSaveInShellIntegrationMode : 1; 
// Did we save in shell integration mode?
UINT uiVisible; // "Visible bands"
UINT cyRebar;
BANDSAVE bsCBANDSMAX;
CLSID aclsidExternalBands[ MAXEXTERNALBANDS ]; // Check classid
CLSID clsidVerticalBar; //clsid of bar persisted within vertical band
CLSID clsidHorizontalBar;
} COOLBARSAVE, *LPCOOLBARSAVE;




이렇다. 소스 코드를 분석해 본 결과 상당히 복잡한 방법으로 bs 멤버와 aclsidExternalBands에 저장되는 순서가 어떤 함수로 관계지어지고 uiVisible에 교묘한 방법으로 계산한 bit를 OR시켜야 한다. 등등 상당히 복잡한 방법으로 저장이 되고 있다는 점이다. 내가 수십시간에 걸쳐 분석하고 완성한 소스가 있지만 그것조차 100% 완벽하다고 장담하지 못하겠다. 솔직히 MS얘네들 간단한 내용을 왜 이렇게 복잡하게 만들었는지 모르겠다. 그냥 최대 15개의 툴바를 지원하므로 15개의 툴바에 구조체를 할당하고 보임 안보임 BOOL 변수와, 스타일 변수, 위치변수 3개만 있으면 다 될것 같은데, 뭐가 그리 복잡하게 만드어 놨는지. 간단하고 읽기좋고 단순한 알고리즘으로 충분히 리팩토링이 필요한 소스가 아닌가 하는 개인적인 불만이 있다.

7 가장 확실한 방법 



#include <mshtml.h>
void CDeletmeDlg::OnButton1()
{
 CoInitialize(NULL);
 IWebBrowser2 *pIE;
 HRESULT hr =  CoCreateInstance(CLSID_InternetExplorer, NULL,
  CLSCTX_LOCAL_SERVER,
  IID_IWebBrowser2,(void**)&pIE);
 pIE->put_Visible(VARIANT_TRUE);
 VARIANT vtBandGUID;
 VARIANT vtShow;
 VARIANT vtRet;
 vtBandGUID.vt = VT_BSTR;
 vtBandGUID.bstrVal = ::SysAllocString( L"{5258771B-ACBE-4974-8ABC-AE4969A2A5CD}" );
 vtShow.vt = VT_BOOL; 
 vtShow.boolVal = VARIANT_TRUE; //** 주의 true가 아니라 VARIANT_TRUE이다.
 vtRet.vt = VT_INT;
 vtRet.intVal = 1;
 //**주의 vtRet는 리턴값이므로 반드시 읽어야 한다.
 //NULL을 넣으면 제대로 동작하지 않는다..
 hr = pIE->ShowBrowserBar(&vtBandGUID, &vtShow, &vtRet); 
 SysFreeString(vtBandGUID.bstrVal);
 //주소바가 보이도록 설정되 있으면
 VARIANT_BOOL b;
 pIE->get_AddressBar(&b);
 if(b == VARIANT_TRUE)
 {    
  HWND hWnd=0;
  pIE->get_HWND((long*)&hWnd);
  if(::IsWindow(hWnd))
  {
   //수동으로 안 보이게한다.
   //put_AddressBar는 현재창에서는 되지만 종료하고 닫으면 주소바가 다시 보이게 되어 있다.
   //따라서 사용자가 메뉴를 누른것처럼 동작하도록 한 다면 리바의 위치정보를 즉시 다시 기록하게 된다.
   ::PostMessage(hWnd, WM_COMMAND, 41477, 0);
  }
 }
 pIE->Release();
}



내가 미처 몰랐는데 이 방식이 있다. 주의라고 표시되지 않은 부분에서 내가 실수를 해서 안 되었던 것이고, 이제 하니 잘 된다. 툴바를 새 줄로 놓는다면 그건 DLL에서 GetHostInfo인가 하는 함수에서 _BREAK을 제거하면 된다.




Posted by SB패밀리

64bit IE 에서 작동되는 BHO 개발을 하려면 어떻게 해야할까?


여러분의 윈도우즈 환경이 windows 7 64bit이라면


internet explorer는 다음과 같이 두 가지 버전으로 존재한다.


32bit 버전

C:\Program Files (x86)\Internet Explorer\iexplore.exe

64bit 버전

C:\Program Files\Internet Explorer\iexplore.exe


파일 속성을 보자.


먼저, 32bit IE




다음은 64bit IE


용량밖에 차이가 없다.



일반적으로는 바탕화면이나 시작메뉴에서 보이는 IE는 32bit IE이다.


그래서 BHO를 개발해서 작동을 시키면 32bit에서는 잘 설치되고 잘 작동한다.

기본적으로 개발해서 Release하는 버전인 32bit이기 때문에 32bit에서는 무리없이 동작한다.


그러나, 64bit IE를 실행했다고 해보자... 어라, 등록되어 있지도 않다.


자, 그럼 여기서 문제.


어떻게 해야 64bit IE에서 내가 개발한 BHO가 동작을 할까?


어떤 글에는 regasm.exe로 등록을 하면 64bit IE에 등록된다고 설명하고 있지만 

이게 정답은 아니다.


64bit BHO를 개발하여 64bit IE에 등록하는 것이 정답이다.


Visual Studio 에서 개발할 때 플랫폼을 x64로 선택한다.

BHO_sobakcc.DLL 파일을 Release한다. 

RegAsm.exe /codebase BHO_sobakcc.dll 을 명령어 입력창에서 실행한다.

"Types registered successfull"라는 메세지가 나오면 성공이다.







Posted by SB패밀리





출처 : http://csk61241.springnote.com/pages/5709479

다들 아시는 것처럼, IE 7에서는 "탭 브라우징" 환경을 지원합니다. "일반 사용자" 입장에서는 이는 분명 환영받을 만한 일입니다. 그런데... 개발자 입장에서는 IE 6과 IE 7간의 환경이 달라짐으로 인해 기존 응용 프로그램들이 정상적으로 구동되지 않는 문제가 있어 ^^ 힘겨울 따름인데요.

오랜만에, IE BHO(Browser Helper Object) 모듈을 다뤄보면서 저도 이 문제에 관심을 갖게 되었습니다. 한번 ^^ 같이 살펴 볼까요?

아마도 대부분의 BHO 모듈에서 다음과 같은 식의 SetSite 코드 유형을 구현하고 있을 것입니다.

          STDMETHODIMP CHelloWorldBHO::SetSite(IUnknown* pUnkSite)
          {
              if (pUnkSite != NULL)
              {
                  pUnkSite->QueryInterface(IID_IWebBrowser2, (void**)&m_spWebBrowser);
              }
              else
              {
                  m_spWebBrowser.Release();
              }
          
              return IObjectWithSiteImpl<CHelloWorldBHO>::SetSite(pUnkSite);
          }
SetSite의 주된 관심사는 바로 IWebBrowser2 인터페이스 포인터를 얻어내는 것입니다. BHO 는 하나의 "Internet Explorer_Server"가 생성될 때마다 그에 상응하는 BHO 개체가 생성되어 연결이 됩니다. 이해를 돕기 위해 다음의 그림을 보겠습니다.

[그림 1: IE 6에서의 웹 브라우저 윈도우 계층]


하나의 IE 인스턴스가 떠 있고, 그와 함께 "Shell DocObject View"와 "Internet Explorer_Server"가 떠 있습니다. 간단하게 생각해서 "Internet Explorer_Server" 윈도우가 생성될 때마다 하나의 BHO 가 생성된다고 보시면 됩니다. (그런데, 이것이 "가정" 에 불과한 것인지, 어딘가에 씌여져 있는 것을 보고 언제부턴가 그렇게 판단했었던 것인지는 잘 모르겠습니다. 그렇다고 제가 IE 소스를 열어보았던 것도 아니고... ^^; 어쩌면 "IEFrame"과 함께 BHO가 생성되는지도 모를 일입니다.)

음... 그렇다면 "A" 라는 웹 사이트를 방문했는데, 그 웹 사이트에서 "팝업 윈도우"를 띄웠다고 가정해보겠습니다. 그럼 몇 개의 BHO 개체가 생성되어져 있을까요? 그렇습니다. 2개입니다. "A" 웹 사이트를 보여주는 IE 인스턴스와 "팝업 윈도우" 역시 IE 인스턴스이기 때문에 총 2개의 BHO 개체가 떠 있는 것입니다. 중요한 것은, 이것들이 단 하나의 "EXE" 프로세스 내에서 생성되기 때문에 경우에 따라서는 static 변수를 쓰는 것이 편리할 수도 있습니다.

"static"을 보자마자 음... "Thread-safe을 고려해야 겠군" 이라고 생각하신 분이 있을 텐데요. ^^ 훌륭하십니다. 각각의 BHO가 호출되는 쓰레드를 다음과 같은 식으로 확인해 보시면,

          DWORD dwThread = ::GetCurrentThreadId(); 
          wchar_t outputDebug[ 4096 ];
          wsprintf( outputDebug, L"BHO::SetSite - threadid %d\r\n", dwThread );
          ::OutputDebugString( outputDebug );
          
          
각각 다른 쓰레드로 활성화되는 것을 볼 수 있습니다. 따라서, static 변수를 사용하실 요량이라면 그에 따른 "동기화 수단"을 제공하시는 것이 좋겠습니다.


IE 6 시절에는, 위와 같은 정도의 지식이라면 충분히 OK 일 수 있었습니다. 가만히 보면, 웹 브라우저 윈도우가 생성되면 해당 윈도우에는 각각 다음과 같은 클래스 이름을 가진 윈도우가 하나씩 존재하기 때문입니다.

          IEFrame
                  Shell DocObject View
                          Internet Explorer_Server
          


위와 같은 간단한 구조로 인해서, 다음과 같은 식으로 윈도우 핸들값을 구하는 것이 일반적이었습니다.

          // IEFrame 윈도우 클래스의 윈도우 핸들값
          m_spWebBrowser->get_HWND((LONG_PTR*)&m_WebWnd); 
          
          // Internet Explorer_Server의 윈도우 핸들값
          HWND ieWindow = ::FindWindowEx( m_WebWnd, L"Internet Explorer_Server", NULL );
          


음... 위의 결과로 보면, SetSite메서드가 구현된 BHO가 매핑되는 것은 IEFrame 윈도우 같기도 합니다.

그런데, 이러한 구조가 IE 7에 와서는 다음과 같은 식으로 바뀌었습니다.

[그림 2: IE 7에서의 웹 브라우저 윈도우 계층 - 2개의 웹 사이트 방문]
bho_ie6_ie7_diff_2.png

정리해 보면, 아래와 같은 구조입니다.

          ** 2개의 웹 사이트 방문시
          
          IEFrame
                  TabWindowClass
                          Shell DocObject View
                                  Internet Explorer_Server
                  TabWindowClass
                          Shell DocObject View
                                  Internet Explorer_Server
          


하나의 TabWindowClass 가 생성될 때마다 BHO 개체가 하나씩, 별도의 쓰레드로 호출되어집니다. (음... 여기서 다시 판단해 보면, BHO 가 매핑되는 것은 IEFrame 이 아닌, 그 하위의 윈도우 중 하나라고 보여집니다.) 실제로 Microsoft 에서 공개된 자료를 보아도 탭 윈도우로 구현된 웹 브라우저는, 기존 IE 6 의 웹 브라우저를 그대로 하나의 탭으로 옮겼다고 설명하고 있습니다. 이로 인해, 간혹 IE 7이 무겁다고 하는 피드백에 대해서 기존에는 IE 6을 2개를 실행했던 것을 하나의 IE 7프로세스에서 IE 6을 2개를 구동하는 것이므로 하나의 IE 7 프로세스와 하나의 IE 6 프로세스를 비교해서는 안되고, 하나의 IE 7 프로세스와 2개의 IE 6 프로세스를 비교해야 하며, 그렇게 고려해 보면 무거워졌다고 볼수는 없다는 것입니다.

실제로, "웹 밴드"의 경우를 봐도 IE 6 웹 브라우저 n 개를 IE 7 프로세스 하나에서 운영한다는 것이 실감나게 됩니다. 참고로, 아래의 그림 2개는 하나의 IE 7 윈도우에 2개의 탭이 있고, 각각의 탭마다 다르게 보여지는 웹 밴드를 확인할 수 있습니다. 말씀드렸듯이, 별개의 웹 브라우저가 하나의 탭에 할당되고 그 웹 브라우저는 독립적으로 웹 밴드를 소유하기 때문입니다.


자... 이제 다시 다음의 코드를 보면, 분명 문제가 있음을 알 수 있습니다.

          // IEFrame 윈도우 클래스의 윈도우 핸들값
          m_spWebBrowser->get_HWND((LONG_PTR*)&m_WebWnd); 
          
          // 언제나 첫번째 Internet Explorer_Server의 윈도우 핸들값 반환
          HWND ieWindow = ::FindWindowEx( m_WebWnd, L"Internet Explorer_Server", NULL );
          


자, 그럼 어떻게 해야 IE 6과 IE 7에서 전혀 코드 변경없이 원하는 목적을 이룰 수 있을까요? 사실, 그 동안은 굳이 필요없었기 때문에 주목받진 못했지만,,, 이제서야 빛을 보게 되는 코드가 있군요. 바로 다음과 같이 코딩을 해주시면 됩니다.

          // IEFrame 윈도우 클래스의 윈도우 핸들값
          m_spWebBrowser->get_HWND((LONG_PTR*)&m_WebWnd); 
          
          CComQIPtr<IServiceProvider> pServiceProvider = m_spWebBrowser;
          if ( pServiceProvider == NULL )
          {
                  return S_OK;
          }
          
          CComPtr<IOleWindow> pOleWindow;
          hr = pServiceProvider->QueryService(SID_SShellBrowser, IID_IOleWindow, (LPVOID *)&pOleWindow);
          if ( hr != S_OK )
          {
                  return S_OK;
          }
          
          // Internet Explorer_Server에 해당하는 윈도우 핸들값
          hr = pOleWindow->GetWindow( &m_hThisWindow );
          if ( hr != S_OK )
          {
                  return S_OK;
          }
          


에잉... 코드 한번 나오니까 더 설명할 것이 없군요... ^^; IE 6 시절부터 위와 같이 코딩을 해주셨다면 IE 7에 와서 굳이 코드를 변경하실 필요는 없겠지만. FindWindow 를 사용해서 코딩을 해주셨다면 반드시 위와 같이 IOleWindow 를 사용하도록 바꿔주셔야 합니다.

Posted by SB패밀리

[개발/IE] BHO(Browser Helper Object)란?

BHO(Browser Helper Object)란?

인터넷 탐색기를 사용하다보면 BHO(Browser Helper Object)가 문제를 발생하는 경우가 종종있다. 
아울러 넷트웍 속도의 저하의 주 원인중 하나도 사용자가 무분별하게 설치한 BHO때문에 발생하며, 
악성 스크립트의 전파 경로중 하나도 BHO인 경우가 많다.

따라서 필자는 BHO와 도구 막대를 관리할 수 있는 유용한 유틸리티라는 글을 통해 BHO를 관리할 수 있는 ToolbarCop이라는 프로그램을 소개했다.


그러면 BHO는 무엇일까?

Browser Helper Object라는 용어에서 알 수 있듯이 
BHO는 근본적으로 브로우저에서 지원하지 못하는 기능을 지원하기위해 플러그인 형태로 IE에 추가되는 DLL 모듈을 말한다. 
DLL 형태로 제공되며, IE를 실행하면 함께 실행되기 때문에 사용자는 그 실행 여부를 알 수 없고, 
따라서 현재는 상당히 많은 악성 프로그램이 이 BHO를 이용해서 실행 코드를 심고 있다.

PDF 파일을 웹 브로우저상에서 읽을 수 있는 아도브사의 Acrobat 플러그인도 이러한 BHO 중 하나이며, 
웹 개발자의 필수품, IE Developer Toolbar이라는 글에서 소개한 IE Developer Toolbar도 BHO이다. 
또한 e2give의 ieBHOs.dll 제거하기라는 글에서 알 수 있듯이 악성 프로그램 역시 BHO 형태로 배포되는 경우가 많다.

따라서 BHO는 ToolbarCop이나 Autoruns과 같은 프로그램을 이용해서 주기적으로 관리하는 습관을 들이는 것이 좋다


Posted by SB패밀리
온라인 키워드 검색광고 시장에서 BHO(Browser Helper Objects)를 이용하여 일반적으로 많이 사용되는 광고 형태의 분류이다.

주소창검색
웹 브라우저의 주소창에서 도메인 주소가 아닌 검색 키워드를 입력할 때 나타나는 배너 광고 또는 웹페이지 광고
주소창검색이란 웹 사이트에 관계없이 웹 브라우저 주소창에서 도메인이 아닌 한글이나 영문 키워드를 입력하는 경우 특정 검색사이트의 검색결과를 보여주거나 광고 페이지로 이동 또는 팝업 광고 등을 노출하는 웹페이지 광고를 말한다.

팝업 (창)
웹 브라우저가 새로 열릴 때마다 새로운 창(IE window)과 함께 나타나는 배너 광고 또는 웹페이지 광고
팝업 광고는 특정 웹페이지를 새로 열 때마다 새로 뜨는 창과 함께 그 위로 나타나는 윈도우 타입의 배너광고 또는 웹페이지 광고를 의미한다

팝언더 (창)
웹페이지 아래로 나타나 사용자가 볼 때까지 기다리는 팝업 광고
웹 브라우저가 새로 열릴 때마다 새로운 창(IE window)와 함께 창 아래로 나타나는 배너 광고 또는 웹페이지 광고를 말한다.

엔딩팝업 (창)
특정 사이트 접속후에 사용자가 사이트를 떠나거나 웹브라우저를 종료할 때 팝업창을 노출하는 배너 광고 또는 웹페이지 광고
팝업광고의 한 형태로 사용자의 불편함을 초래할 수 있다.

팝업탭
웹 페이지가 현재 탭에서 또는 새로 열릴 때마다 새로운 탭(IE Tab)이 나타나고 그 탭으로 이동되는 배너 광고 또는 웹페이지 광고
팝업탭 광고는 포탈사이트에서 검색을 하거나 특정 웹페이지를 열 때마다 새로 뜨는 탭과 함께 나타나는 배너광고 또는 웹페이지 광고를 의미한다
팝업광고처럼 광고가 사용자의 웹 페이지 전면에 나오기 때문에 사용자의 불편함을 초래한다.

팝언더탭
웹 페이지가 현재 탭에서 열릴 때마다  새로운 탭(IE Tab)이 나타나고 사용자가 그 탭을 볼 때까지 기다리는 배너 광고 또는 웹페이지 광고
팝언더웹 광고는 포탈사이트에서 검색을 하거나 특정 웹페이지를 열 때마다 새로 뜨는 탭이 나타나며 사용자가 탭을 볼 때까지 기다리는 배너 광고 또는 웹페이지 광고를 말한다.
팝업광고처럼 광고가 사용자의 웹 페이지에 숨겨져 나오기 때문에 사용자의 불편함이 팝업광고나 팝업탭보다 적다.

Posted by SB패밀리


BHO(Browser Helper Object)란?

인터넷 탐색기를 사용하다보면 BHO(Browser Helper Object)가 문제를 발생하는 경우가 종종있다.
아울러 넷트웍 속도의 저하의 주 원인중 하나도 사용자가 무분별하게 설치한 BHO때문에 발생하며,
악성 스크립트의 전파 경로중 하나도 BHO인 경우가 많다.

따라서 필자는 BHO와 도구 막대를 관리할 수 있는 유용한 유틸리티라는 글을 통해 BHO를 관리할 수 있는 ToolbarCop이라는 프로그램을 소개했다.


그러면 BHO는 무엇일까?

Browser Helper Object라는 용어에서 알 수 있듯이
BHO는 근본적으로 브로우저에서 지원하지 못하는 기능을 지원하기위해 플러그인 형태로 IE에 추가되는 DLL 모듈을 말한다.
DLL 형태로 제공되며, IE를 실행하면 함께 실행되기 때문에 사용자는 그 실행 여부를 알 수 없고,
따라서 현재는 상당히 많은 악성 프로그램이 이 BHO를 이용해서 실행 코드를 심고 있다.

PDF 파일을 웹 브로우저상에서 읽을 수 있는 아도브사의 Acrobat 플러그인도 이러한 BHO 중 하나이며,
웹 개발자의 필수품, IE Developer Toolbar이라는 글에서 소개한 IE Developer Toolbar도 BHO이다.
또한 e2give의 ieBHOs.dll 제거하기라는 글에서 알 수 있듯이 악성 프로그램 역시 BHO 형태로 배포되는 경우가 많다.

따라서 BHO는 ToolbarCop이나 Autoruns과 같은 프로그램을 이용해서 주기적으로 관리하는 습관을 들이는 것이 좋다.

 

Posted by SB패밀리
TAG BHO, IE, 개발
출처 : http://csk61241.springnote.com/pages/5709479

다들 아시는 것처럼, IE 7에서는 "탭 브라우징" 환경을 지원합니다. "일반 사용자" 입장에서는 이는 분명 환영받을 만한 일입니다. 그런데... 개발자 입장에서는 IE 6과 IE 7간의 환경이 달라짐으로 인해 기존 응용 프로그램들이 정상적으로 구동되지 않는 문제가 있어 ^^ 힘겨울 따름인데요.

오랜만에, IE BHO(Browser Helper Object) 모듈을 다뤄보면서 저도 이 문제에 관심을 갖게 되었습니다. 한번 ^^ 같이 살펴 볼까요?

아마도 대부분의 BHO 모듈에서 다음과 같은 식의 SetSite 코드 유형을 구현하고 있을 것입니다.
          STDMETHODIMP CHelloWorldBHO::SetSite(IUnknown* pUnkSite)
          {
              if (pUnkSite != NULL)
              {
                  pUnkSite->QueryInterface(IID_IWebBrowser2, (void**)&m_spWebBrowser);
              }
              else
              {
                  m_spWebBrowser.Release();
              }
          
              return IObjectWithSiteImpl<CHelloWorldBHO>::SetSite(pUnkSite);
          }
SetSite의 주된 관심사는 바로 IWebBrowser2 인터페이스 포인터를 얻어내는 것입니다. BHO 는 하나의 "Internet Explorer_Server"가 생성될 때마다 그에 상응하는 BHO 개체가 생성되어 연결이 됩니다. 이해를 돕기 위해 다음의 그림을 보겠습니다.

[그림 1: IE 6에서의 웹 브라우저 윈도우 계층]


하나의 IE 인스턴스가 떠 있고, 그와 함께 "Shell DocObject View"와 "Internet Explorer_Server"가 떠 있습니다. 간단하게 생각해서 "Internet Explorer_Server" 윈도우가 생성될 때마다 하나의 BHO 가 생성된다고 보시면 됩니다. (그런데, 이것이 "가정" 에 불과한 것인지, 어딘가에 씌여져 있는 것을 보고 언제부턴가 그렇게 판단했었던 것인지는 잘 모르겠습니다. 그렇다고 제가 IE 소스를 열어보았던 것도 아니고... ^^; 어쩌면 "IEFrame"과 함께 BHO가 생성되는지도 모를 일입니다.)

음... 그렇다면 "A" 라는 웹 사이트를 방문했는데, 그 웹 사이트에서 "팝업 윈도우"를 띄웠다고 가정해보겠습니다. 그럼 몇 개의 BHO 개체가 생성되어져 있을까요? 그렇습니다. 2개입니다. "A" 웹 사이트를 보여주는 IE 인스턴스와 "팝업 윈도우" 역시 IE 인스턴스이기 때문에 총 2개의 BHO 개체가 떠 있는 것입니다. 중요한 것은, 이것들이 단 하나의 "EXE" 프로세스 내에서 생성되기 때문에 경우에 따라서는 static 변수를 쓰는 것이 편리할 수도 있습니다.

"static"을 보자마자 음... "Thread-safe을 고려해야 겠군" 이라고 생각하신 분이 있을 텐데요. ^^ 훌륭하십니다. 각각의 BHO가 호출되는 쓰레드를 다음과 같은 식으로 확인해 보시면,

          DWORD dwThread = ::GetCurrentThreadId(); 
          wchar_t outputDebug[ 4096 ];
          wsprintf( outputDebug, L"BHO::SetSite - threadid %d\r\n", dwThread );
          ::OutputDebugString( outputDebug );
          
          
각각 다른 쓰레드로 활성화되는 것을 볼 수 있습니다. 따라서, static 변수를 사용하실 요량이라면 그에 따른 "동기화 수단"을 제공하시는 것이 좋겠습니다.


IE 6 시절에는, 위와 같은 정도의 지식이라면 충분히 OK 일 수 있었습니다. 가만히 보면, 웹 브라우저 윈도우가 생성되면 해당 윈도우에는 각각 다음과 같은 클래스 이름을 가진 윈도우가 하나씩 존재하기 때문입니다.

          IEFrame
                  Shell DocObject View
                          Internet Explorer_Server
          


위와 같은 간단한 구조로 인해서, 다음과 같은 식으로 윈도우 핸들값을 구하는 것이 일반적이었습니다.

          // IEFrame 윈도우 클래스의 윈도우 핸들값
          m_spWebBrowser->get_HWND((LONG_PTR*)&m_WebWnd); 
          
          // Internet Explorer_Server의 윈도우 핸들값
          HWND ieWindow = ::FindWindowEx( m_WebWnd, L"Internet Explorer_Server", NULL );
          


음... 위의 결과로 보면, SetSite메서드가 구현된 BHO가 매핑되는 것은 IEFrame 윈도우 같기도 합니다.

그런데, 이러한 구조가 IE 7에 와서는 다음과 같은 식으로 바뀌었습니다.

[그림 2: IE 7에서의 웹 브라우저 윈도우 계층 - 2개의 웹 사이트 방문]
bho_ie6_ie7_diff_2.png

정리해 보면, 아래와 같은 구조입니다.

          ** 2개의 웹 사이트 방문시
          
          IEFrame
                  TabWindowClass
                          Shell DocObject View
                                  Internet Explorer_Server
                  TabWindowClass
                          Shell DocObject View
                                  Internet Explorer_Server
          


하나의 TabWindowClass 가 생성될 때마다 BHO 개체가 하나씩, 별도의 쓰레드로 호출되어집니다. (음... 여기서 다시 판단해 보면, BHO 가 매핑되는 것은 IEFrame 이 아닌, 그 하위의 윈도우 중 하나라고 보여집니다.) 실제로 Microsoft 에서 공개된 자료를 보아도 탭 윈도우로 구현된 웹 브라우저는, 기존 IE 6 의 웹 브라우저를 그대로 하나의 탭으로 옮겼다고 설명하고 있습니다. 이로 인해, 간혹 IE 7이 무겁다고 하는 피드백에 대해서 기존에는 IE 6을 2개를 실행했던 것을 하나의 IE 7프로세스에서 IE 6을 2개를 구동하는 것이므로 하나의 IE 7 프로세스와 하나의 IE 6 프로세스를 비교해서는 안되고, 하나의 IE 7 프로세스와 2개의 IE 6 프로세스를 비교해야 하며, 그렇게 고려해 보면 무거워졌다고 볼수는 없다는 것입니다.

실제로, "웹 밴드"의 경우를 봐도 IE 6 웹 브라우저 n 개를 IE 7 프로세스 하나에서 운영한다는 것이 실감나게 됩니다. 참고로, 아래의 그림 2개는 하나의 IE 7 윈도우에 2개의 탭이 있고, 각각의 탭마다 다르게 보여지는 웹 밴드를 확인할 수 있습니다. 말씀드렸듯이, 별개의 웹 브라우저가 하나의 탭에 할당되고 그 웹 브라우저는 독립적으로 웹 밴드를 소유하기 때문입니다.


자... 이제 다시 다음의 코드를 보면, 분명 문제가 있음을 알 수 있습니다.

          // IEFrame 윈도우 클래스의 윈도우 핸들값
          m_spWebBrowser->get_HWND((LONG_PTR*)&m_WebWnd); 
          
          // 언제나 첫번째 Internet Explorer_Server의 윈도우 핸들값 반환
          HWND ieWindow = ::FindWindowEx( m_WebWnd, L"Internet Explorer_Server", NULL );
          


자, 그럼 어떻게 해야 IE 6과 IE 7에서 전혀 코드 변경없이 원하는 목적을 이룰 수 있을까요? 사실, 그 동안은 굳이 필요없었기 때문에 주목받진 못했지만,,, 이제서야 빛을 보게 되는 코드가 있군요. 바로 다음과 같이 코딩을 해주시면 됩니다.

          // IEFrame 윈도우 클래스의 윈도우 핸들값
          m_spWebBrowser->get_HWND((LONG_PTR*)&m_WebWnd); 
          
          CComQIPtr<IServiceProvider> pServiceProvider = m_spWebBrowser;
          if ( pServiceProvider == NULL )
          {
                  return S_OK;
          }
          
          CComPtr<IOleWindow> pOleWindow;
          hr = pServiceProvider->QueryService(SID_SShellBrowser, IID_IOleWindow, (LPVOID *)&pOleWindow);
          if ( hr != S_OK )
          {
                  return S_OK;
          }
          
          // Internet Explorer_Server에 해당하는 윈도우 핸들값
          hr = pOleWindow->GetWindow( &m_hThisWindow );
          if ( hr != S_OK )
          {
                  return S_OK;
          }
          


에잉... 코드 한번 나오니까 더 설명할 것이 없군요... ^^; IE 6 시절부터 위와 같이 코딩을 해주셨다면 IE 7에 와서 굳이 코드를 변경하실 필요는 없겠지만. FindWindow 를 사용해서 코딩을 해주셨다면 반드시 위와 같이 IOleWindow 를 사용하도록 바꿔주셔야 합니다.

Posted by SB패밀리

Building Browser Helper Objects with Visual Studio 2005

Tony Schreiner, John Sudds
Microsoft Corporation

October 27, 2006

Summary: This article demonstrates how to use Microsoft Visual Studio 2005 to create a simple Browser Helper Object (BHO), a Component Object Model (COM) object that implements the IObjectWithSite interface and attaches itself to Internet Explorer. This article describes how to create an entry-level BHO step-by-step. At first, the BHO displays a message that reads "Hello World!" as Internet Explorer loads a document. Then, the BHO is extended to remove images from the loaded page. This article is written for developers who want to learn how to extend the functionality of the browser and to create Web developer tools for Internet Explorer. (8 printed pages)

Contents

Introduction
Overview
Setting up the Project
Implementing the Basics
Responding to Events
Manipulating the DOM
Summary
Related Topics

Introduction

This article relies on Microsoft Visual Studio 2005 and Active Template Library (ATL) to develop a BHO using C++. We decided to use ATL because it conveniently implements a basic boilerplate that we can extend for our needs. There are other ways to create a BHO, such as using Microsoft Foundation Classes (MFC) or the Win32 API and COM, but ATL is a lightweight library that automatically handles a lot of the details for us, including setting up the registry with the BHO class identifier (CLSID).

Another strength of ATL is its COM-aware smart pointer classes (such as CComPtr and CComBSTR) that manage the lifetime of COM objects. For example, CComPtr calls AddRef as a value is assigned, and calls Release as the object is destroyed or goes out of scope. Smart pointers simplify the code and help eliminate memory leaks. Their stability and reliability are especially useful when used within the scope of a single method.

The first part of this article walks you through the process of implementing a simple BHO and verifying that it is loaded by Internet Explorer. The next part demonstrates how to connect the BHO to browser events, and the final part shows a simple interaction with the DHTML Document Object Model (DOM) that changes the appearance of a Web page.

Overview

What exactly is a Browser Helper Object (BHO)? In a nutshell, a BHO is a lightweight DLL extension that adds custom functionality to Internet Explorer. Although it is less common and not the focus of this article, BHOs can also add functionality to the Windows Explorer shell.

BHOs typically do not provide any user interface (UI) of their own. Rather, they function in the background by responding to browser events and user input. For example, BHOs can block pop-ups, auto-fill forms, or add support for mouse gestures. It is a common misconception that BHOs are required by toolbar extensions; however, BHOs used in conjunction with toolbars can provide an even richer user experience.

Note  BHOs are convenient tools for end users and developers alike; however, because BHOs are granted considerable power over the browser and Web content, and because they often go undetected, users should take great care to obtain and install BHOs from reliable sources.

The lifetime of a BHO is the same as the lifetime of the browser instance that it interacts with. In Internet Explorer 6 and earlier, this means that a new BHO is created (and destroyed) for each new top-level window. On the other hand, Internet Explorer 7 creates and destroys a new BHO for each tab. BHOs are not loaded by other applications that host the WebBrowser control or by windows such as HTML dialog boxes.

The primary requirement of a BHO is to implement the IObjectWithSite interface. This interface exposes a method, SetSite, that facilitates the initial communication with Internet Explorer and notifies the BHO when it is about to be released. We create a simple browser extension by implementing this interface, and then adding the CLSID of the BHO into the registry.

Let's get started.

Setting up the Project

To create a BHO project with Microsoft Visual Studio 2005:

  1. On the File menu, click New Project....
    The New Project dialog box appears. This dialog box lists the application types that Visual Studio can create.
  2. Under the Visual C++ node, select "ATL" if it is not already selected, then select "ATL Project" from the Visual C++ project types. Name the project "HelloWorld" and use the default location. Click OK.
  3. In the ATL Project Wizard, ensure that the server type is "Dynamic-link library (DLL)" and click Finish.

At this point, Visual Studio has created boilerplate for a DLL. We now add the COM object that implements the BHO.

  1. In the Solution Explorer panel, right-click on the project and select Class... from the Add submenu.
  2. Select "ATL Simple Object" and click Add. The ATL Simple Object Wizard appears.
  3. In Names of the ATL Simple Object Wizard, type "HelloWorldBHO" as a Short Name.The remaining names are filled in automatically.
  4. In Options of the ATL Simple Object Wizard, select "Apartment" for Threading Model, "No" for Aggregation, "Dual" for Interface, and "IObjectWithSite" for Support. ATL Simple Object Wizard Options
  5. Click Finish.

The following files are created as part of this project.

  • HelloWorldBHO.h – this header file contains the class definition for the BHO.
  • HelloWorldBHO.cpp – this source file is the main file for the project and contains the COM object.
  • HelloWorld.cpp – this source file implements the exports that expose the COM object through the DLL.
  • HelloWorld.idl – this source file can be used to define custom COM interfaces. For this article, we will not change this file.
  • HelloWorld.rgs – this resource file contains the registry keys that are written and removed when the DLL is registered and unregistered.

Implementing the Basics

The ATL Project Wizard provides a default implementation of SetSite. Although the interface contract of IObjectWithSite implies that this method may be called again and again as necessary, Internet Explorer invokes this method exactly twice; once to establish a connection, and again as the browser is exiting. Specifically, the SetSite implementation in our BHO performs the following actions:

  • Stores a reference to the site. During initialization, the browser passes a IUnknown pointer to the top-level WebBrowser Control, and the BHO stores a reference to it in a private member variable.
  • Releases the site pointer currently being held. When Internet Explorer passes NULL, the BHO must release all interface references and disconnect from the browser.

As part of the processing of SetSite, the BHO should perform other initialization and uninitialization as required. For example, you can establish a connection point to the browser in order to receive browser events.

HelloWorldBHO.h

Double-click to open HelloWorldBHO.h from the Visual Studio Solution Explorer.

First, include shlguid.h. This file defines interface identifiers for IWebBrowser2 and the events that are used later in the project.

#include <shlguid.h>     // IID_IWebBrowser2, DIID_DWebBrowserEvents2, etc.

Next, in a public section of the CHelloWorldBHO class, declare SetSite.

STDMETHOD(SetSite)(IUnknown *pUnkSite);

The STDMETHOD macro is an ATL convention that marks the method as virtual and ensures that it has the right calling convention for the public COM interface. It helps to demarcate COM interfaces from other public methods that may exist on the class. The STDMETHODIMP macro is likewise used when implementing the member method.

Finally, in a private section of the class declaration, declare a member variable to store the browser site.

private:
    CComPtr<IWebBrowser2>  m_spWebBrowser;

HelloWorldBHO.cpp

Switch now to HelloWorldBHO.cpp and insert the following code for SetSite.

STDMETHODIMP CHelloWorldBHO::SetSite(IUnknown* pUnkSite)
{
    if (pUnkSite != NULL)
    {
        // Cache the pointer to IWebBrowser2.
        pUnkSite->QueryInterface(IID_IWebBrowser2, (void**)&m_spWebBrowser);
    }
    else
    {
        // Release cached pointers and other resources here.
        m_spWebBrowser.Release();
    }

    // Return the base class implementation
    return IObjectWithSiteImpl<CHelloWorldBHO>::SetSite(pUnkSite);
}

During initialization, the browser passes a reference to its top-level IWebBrowser2 interface, which we cache. During uninitialization, the browser passes NULL. To avoid memory leaks and circular reference counts, it's important to release all pointers and resources at that time. Finally, we call the base class implementation so that it can fulfill the rest of the interface contract.

HelloWorld.cpp

When a DLL is loaded, the system calls the DllMain function with a DLL_PROCESS_ATTACH notification. Because Internet Explorer makes extensive use of multi-threading, frequent DLL_THREAD_ATTACH and DLL_THREAD_DETACH notifications to DllMain can slow the overall performance of the extension and the browser process. Since this BHO does not require thread-level tracking, we can call DisableThreadLibraryCalls during the DLL_PROCESS_ATTACH notification to avoid the overhead of new thread notifications.

In HelloWorld.cpp, code the DllMain function as follows:

extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        DisableThreadLibraryCalls(hInstance);
    }
    return _AtlModule.DllMain(dwReason, lpReserved); 
}

Register the BHO

All that remains is to add the CLSID of the BHO to the registry. This entry marks the DLL as a browser helper object and causes Internet Explorer to load the BHO at start-up. Visual Studio can register the CLSID when it builds the project.

Note  On Windows Vista, Visual Studio requires elevated privileges to interact with the registry. Make sure to start the development environment by right-clicking Microsoft Visual Studio 2005 in the Start menu and selecting Run as administrator.

The CLSID for this BHO is found in HelloWorld.idl, in a block of code similar to the following:

    importlib("stdole2.tlb");
    [
        uuid(D2F7E1E3-C9DC-4349-B72C-D5A708D6DD77),
        helpstring("HelloWorldBHO Class")
    ]

Note that this file contains three GUIDs; we need the CLSID for the class, not those of the library or interface ID.

To create a self-registering BHO:

  1. Open HelloWorld.rgs from the Solution Explorer in Visual Studio.
  2. Add the following code to the bottom of the file:
    HKLM {
      NoRemove SOFTWARE {
        NoRemove Microsoft {   
          NoRemove Windows {
            NoRemove CurrentVersion {
              NoRemove Explorer {
                NoRemove 'Browser Helper Objects' {
                  ForceRemove '{D2F7E1E3-C9DC-4349-B72C-D5A708D6DD77}' = s 'HelloWorldBHO' {
                    val 'NoExplorer' = d '1'
                  }
                }
              }
            }
          }
        }
      }
    }
    
  3. Replace the GUID that follows ForceRemove above with the CLSID of the BHO found in HelloWorld.idl.Do not replace the curly braces.
  4. Save the file, and rebuild the solution from the Build menu.Visual Studio registers the object automatically.

The NoRemove keyword indicates that the key should be not be deleted when the BHO is unregistered. Unless you specify this keyword, empty keys will be removed. The ForceRemove keyword indicates that the key and any values and sub-keys that it contains should be deleted. ForceRemove also causes the key to be recreated when the BHO is registered, if the key already exists.

Since this BHO is specifically designed for Internet Explorer, we specify the NoExplorer value to prevent Windows Explorer from loading it. Neither the value nor the type makes any difference—as long as the NoExplorer entry exists, Windows Explorer will not load the BHO.

If you haven't done so already, select Build Solution from the Build menu to build and register the BHO.

Take a Test Drive

For a quick test, set a breakpoint in SetSite and start the debugger by pressing F5. When the Executable for Debug Session dialog box appears, select the "Default Web Browser" and click OK. If Internet Explorer is not your default browser, you can browse for the executable.

Note   On Windows Vista, the Internet Explorer Protected Mode feature launches a separate process and exits, making it a little harder to debug. You can easily turn off Protected Mode for the current session in two ways: launch the browser from a administrative process (such as Visual Studio), or create a local HTML file and specify it as a command line parameter to Internet Explorer.

As the browser starts, it loads the DLL for the BHO. When the breakpoint is hit, note that the pUnkSite parameter is set. Press F5 again to continue loading the home page.

Close the browser to verify that SetSite is called again with NULL.

Responding to Events

Now that you've confirmed that Internet Explorer can load and run the BHO, let's take our example a little further by extending the BHO to react to browser events. In this section, we describe how to use ATL to implement an event handler for DocumentComplete that displays a message box after the page loads.

To be notified of events, the BHO establishes a connection point with the browser; to respond to these events, it implements IDispatch. According to the documentation for DocumentComplete, the event has two parameters: pDisp (a pointer to IDispatch) and pUrl. These parameters are passed to IDispatch::Invoke as part of the event; however, unpacking the event parameters by hand is a non-trivial and error-prone task. Fortunately, ATL provides a default implementation that helps to simplify the event-handling logic.

HelloWorldBHO.h

Start in HelloWorldBHO.h by including exdispid.h, which defines the dispatch IDs for browser events.

#include <exdispid.h> // DISPID_DOCUMENTCOMPLETE, etc.

Next, we indiciate that we want to handle events defined by the DWebBrowserEvents2 interface by adding a class definition for IDispEventImpl, which provides an easy and safe alternative to Invoke for handling events. IDispEventImpl works in conjunction with an event sink map to route events to the appropriate handler function.

class ATL_NO_VTABLE CHelloWorldBHO :
    . . . 
    public IDispEventImpl<1, CHelloWorldBHO, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 1>

Next, add ATL macros that route the event to a new OnDocumentComplete event handler method, which takes the same arguments, in the same order, as defined by the DocumentComplete event. Place the following code in a public section of the class.

BEGIN_SINK_MAP(CHelloWorldBHO)
    SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, OnDocumentComplete)
END_SINK_MAP()

    // DWebBrowserEvents2
    void STDMETHODCALLTYPE OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL); 

The number supplied to the SINK_ENTRY_EX macro (1) refers to the first parameter of the IDispEventImpl class definition and is used to distinguish between events from different interfaces, if necessary. Also note that you cannot return a value from the event handler; that's OK because Internet Explorer ignores values returned from Invoke anyway.

Finally, add a private member variable to track whether the object has established a connection with the browser.

private:
    BOOL m_fAdvised; 

HelloWorldBHO.cpp

To connect the event handler to the browser through the event map, call DispEventAdvise during the processing of SetSite. Likewise, use DispEventUnadvise to break the connection.

Here is the new implementation of SetSite:

STDMETHODIMP CHelloWorldBHO::SetSite(IUnknown* pUnkSite)
{
    if (pUnkSite != NULL)
    {
        // Cache the pointer to IWebBrowser2.
        HRESULT hr = pUnkSite->QueryInterface(IID_IWebBrowser2, (void **)&m_spWebBrowser);
        if (SUCCEEDED(hr))
        {
            // Register to sink events from DWebBrowserEvents2.
            hr = DispEventAdvise(m_spWebBrowser);
            if (SUCCEEDED(hr))
            {
                m_fAdvised = TRUE;
            }
        }
    }
    else
    {
        // Unregister event sink.
        if (m_fAdvised)
        {
            DispEventUnadvise(m_spWebBrowser);
            m_fAdvised = FALSE;
        }

        // Release cached pointers and other resources here.
        m_spWebBrowser.Release();
    }

    // Call base class implementation.
    return IObjectWithSiteImpl<CHelloWorldBHO>::SetSite(pUnkSite);
}

Finally, add a simple OnDocumentComplete event handler.

void STDMETHODCALLTYPE CHelloWorldBHO::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL)
{
    // Retrieve the top-level window from the site.
    HWND hwnd;
    HRESULT hr = m_spWebBrowser->get_HWND((LONG_PTR*)&hwnd);
    if (SUCCEEDED(hr))
    {
        // Output a message box when page is loaded.
        MessageBox(hwnd, L"Hello World!", L"BHO", MB_OK);
    }
}

Notice that the message box uses the top-level window of the site as its parent window, rather than simply passing NULL in that parameter. In Internet Explorer 6, a NULL parent window does not block the application, meaning that the user can continue to interact with the browser while the message box is waiting for user input. In some situations, this can cause the browser to hang or crash. In the rare case that a BHO needs to display a UI, it should always ensure that the dialog box is application modal by specifying a handle to the parent window.

Another Test Drive

Start up Internet Explorer again by pressing F5. After the document has loaded, the BHO displays its message.

The "Hello World!" Message Box

Continue browsing to observe when and how often the message box appears. Notice that the BHO alert is shown not only when the page is loaded, but also when the page is reloaded by clicking the Back button; however, it does not appear when you click the Refresh button. The message box also appears for every new tab and every document loaded in a frame or iframe.

The event is fired after the page is downloaded and parsed, but before the window.onload event is triggered. In the case of multiple frames, the event is fired multiple times followed by the top-level frame at the end. In the code that follows, we detect the final event of a series by comparing the object passed in the pDisp parameter of the event to the top-level browser that was cached in SetSite.

Manipulating the DOM

The following JavaScript code demonstrates a basic manipulation of the DOM. It hides images on the Web page by setting the display attribute of the image's style object to "none."
function RemoveImages(doc)
{
    var images = doc.images;
    if (images != null)
    {
        for (var i = 0; i < images.length; i++) 
        {
            var img = images.item(i);
            img.style.display = "none";
        }
    }
}

In this final section, we show you how to implement this basic logic in C++.

HelloWorldBHO.h

First, open HelloWorldBHO.h and include mshtml.h. This header file defines the interfaces we need for working with the DOM.

#include <mshtml.h>         // DOM interfaces

Next, define the private member method to contain the C++ implementation of the JavaScript above.

private:
    void RemoveImages(IHTMLDocument2 *pDocument);

HelloWorldBHO.cpp

The OnDocumentComplete event handler now does two new things. First, it compares the cached WebBrowser pointer to the object for which the event is fired; if they are equal, the event is for the top-level window and the document is fully loaded. Second, it retrieves a pointer to the document object and passes it to RemoveImages.

void STDMETHODCALLTYPE CHelloWorldBHO::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL)
{
    HRESULT hr = S_OK;

    // Query for the IWebBrowser2 interface.
    CComQIPtr<IWebBrowser2> spTempWebBrowser = pDisp;

    // Is this event associated with the top-level browser?
    if (spTempWebBrowser && m_spWebBrowser &&
        m_spWebBrowser.IsEqualObject(spTempWebBrowser))
    {
        // Get the current document object from browser...
        CComPtr<IDispatch> spDispDoc;
        hr = m_spWebBrowser->get_Document(&spDispDoc);
        if (SUCCEEDED(hr))
        {
            // ...and query for an HTML document.
            CComQIPtr<IHTMLDocument2> spHTMLDoc = spDispDoc;
            if (spHTMLDoc != NULL)
            {
                // Finally, remove the images.
                RemoveImages(spHTMLDoc);
            }
        }
    }
}

The IDispatch pointer in pDisp contains the IWebBrowser2 interface of the window or frame in which the document has loaded. We store the value in a CComQIPtr class variable, which performs a QueryInterface automatically. Next, to determine if the page is completely loaded, we compare the interface pointer to the one we cached in SetSite for the top-level browser. As a result of this test, we only remove images from documents in the top-level browser frame; documents that do not load into the top-level frame do not pass this test. (For more information, see How To Determine When a Page Is Done Loading in WebBrowser Control and How to get the WebBrowser object model of an HTML frame.)

It takes two steps to retrieve the HTML document object. Because get_Document retrieves a pointer for the active document even if the browser has hosted a document object of another type (such as a Microsoft Word document), we must further query the active document for an IHTMLDocument2 interface to determine if it is indeed an HTML page. The IHTMLDocument2 interface provides access to the contents of the DHTML DOM.

After confirming that an HTML document is loaded, we pass the value to RemoveImages. Note that the argument is passed as a pointer to IHTMLDocument2, not as a CComPtr.

void CHelloWorldBHO::RemoveImages(IHTMLDocument2* pDocument)
{
    CComPtr<IHTMLElementCollection> spImages;

    // Get the collection of images from the DOM.
    HRESULT hr = pDocument->get_images(&spImages);
    if (hr == S_OK && spImages != NULL)
    {
        // Get the number of images in the collection.
        long cImages = 0;
        hr = spImages->get_length(&cImages);
        if (hr == S_OK && cImages > 0)
        {
            for (int i = 0; i < cImages; i++)
            {
                CComVariant svarItemIndex(i);
                CComVariant svarEmpty;
                CComPtr<IDispatch> spdispImage;

                // Get the image out of the collection by index.
                hr = spImages->item(svarItemIndex, svarEmpty, &spdispImage);
                if (hr == S_OK && spdispImage != NULL)
                {
                    // First, query for the generic HTML element interface...
                    CComQIPtr<IHTMLElement> spElement = spdispImage;

                    if (spElement)
                    {
                        // ...then ask for the style interface.
                        CComPtr<IHTMLStyle> spStyle;
                        hr = spElement->get_style(&spStyle);

                        // Set display="none" to hide the image.
                        if (hr == S_OK && spStyle != NULL)
                        {
                            static const CComBSTR sbstrNone(L"none");
                            spStyle->put_display(sbstrNone);
                        }
                    }
                }
            }
        }
    }
}

Interacting with the DOM in C++ is more verbose than JavaScript, but the code flow is essentially the same.

The preceding code iterates over each item in the images collection. In script, it is clear whether the collection element is being accessed by ordinal or by name; however, in C++ you must manually disambiguate these arguments by passing an empty variant. We again rely on an ATL helper class—this time CComVariant—to minimize the amount of code that we have to write.

Final Notes

To facilitate scripting, all objects in the DOM use IDispatch to expose properties and methods that are derived from multiple interfaces. In C++, however, you must explicitly query for the interface that supports the property or method you want to use. For example, an image object supports both the IHTMLElement and IHTMLImgElement interfaces. Therefore, to retrieve a style object for an image, you first have to query for an IHTMLElement interface, which exposes the get_style method.

Also note that COM rules do not guarantee a valid pointer on failure; therefore, you need to check the HRESULT after every COM call. Moreover, for many DOM methods it is not an error to return a NULL value, so you need to be careful to check both the return value and the pointer value. To make the check even safer, always initialize the pointer to NULL beforehand. Adopting a defensive, verbose, and error-tolerant coding style can help to prevent unpredictable bugs later.

Summary

There are various types of BHOs with a wide range of purposes; however, all BHOs share one common feature: a connection to the browser. Because of their ability to tightly integrate with Internet Explorer, BHOs are valued by countless developers who want to extend the functionality of the browser. This article demonstrated how to create a simple BHO that modifies the style attributes of IMG elements in a loaded document. We invite you to extend this entry-level example as you like. You can further explore the possibilities by visiting the following links.

Related Topics


출처 : http://msdn.microsoft.com/en-us/library/bb250489(VS.85).aspx

Posted by SB패밀리

best practice is to write a BHO which will load your BandObject.

A BHO has to implement IObjectWithSite

--- IObjectWithSite.cs ---

using System;
using
System.Runtime.InteropServices;
namespace
<YOUR_NAMESPACE_HERE>
{
   [C
omVisible(true)]
   [
InterfaceType(ComInterfaceType
.InterfaceIsIUnknown)]
   [
Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352"
)]
  
public interface IObjectWithSite
  
{
     
[
PreserveSig]int SetSite([MarshalAs(UnmanagedType.IUnknown)] object site);
      [
PreserveSig]int GetSite(ref Guid guid, out IntPtr ppvSite);
  
}
}

--- BHO.cs ---

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using SHDocVw;

namespace <YOUR_NAMESPACE_HERE>
{

   [ComVisible(true)]
   [
Guid("<YOUR_GUID_HERE>")]
   [ClassInterface(ClassInterfaceType.None)]

   public class BHO : IObjectWithSite
  
{
     
private InternetExplorer explorer;
    

      #region ComRegisterFunction
     
public static string BHOKEYNAME = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Browser Helper Objects"

      [ComRegisterFunction]
     
public static void RegisterBHO(Type t)
      {
        
RegistryKey key = Registry.LocalMachine.OpenSubKey(BHOKEYNAME, true);
         if (key == null)
         
{
           
key = Registry.LocalMachine.CreateSubKey(BHOKEYNAME);
        
}

         string guidString = t.GUID.ToString("B");
        
RegistryKey bhoKey = key.OpenSubKey(guidString, true);

         if (bhoKey == null)
         {
            bhoKey = key.CreateSubKey(guidString);
         }

         // NoExplorer:dword = 1 prevents the BHO to be loaded by Explorer
        
string _name = "NoExplorer";
        
object _value = (object)1;
         bhoKey.SetValue(_name, _value);
         key.Close();
         bhoKey.Close();
      }

      [ComUnregisterFunction]
     
public static void UnregisterBHO(Type t)
      {
        
RegistryKey key = Registry.LocalMachine.OpenSubKey(BHOKEYNAME, true);
        
string guidString = t.GUID.ToString("B");

         if (key != null)
         {
            key.DeleteSubKey(guidString,
false);
         }
      }

      #endregion

      #region IObjectWithSite Members
      public int SetSite(object site)
      {
        
if (site != null)
         {
            explorer = (
InternetExplorer)site;
            ShowBrowserBar(
true);
         }
      
   return 0;
      }

      public int GetSite(ref Guid guid, out IntPtr ppvSite)
      {
        
IntPtr punk = Marshal.GetIUnknownForObject(explorer);
        
int hr = Marshal.QueryInterface(punk, ref guid, out ppvSite);
         
Marshal.Release(punk);
        
return hr;
      }

      #endregion

      #region Private Functions

      private void ShowBrowserBar(bool bShow)
      {
         
object pvaClsid = (object)(new Guid("GUID_OF_YOUR_BANDOBJECT_HERE").ToString("B"));
        
object pvarShow = (object)bShow;
        
object pvarSize = null;
        
        
if (bShow) /* hide Browser bar before showing to prevent erroneous behavior of IE*/
        
{
           
object pvarShowFalse = (object)false;
            explorer.ShowBrowserBar(
ref pvaClsid, ref pvarShowFalse, ref pvarSize);
         }

         explorer.ShowBrowserBar(ref pvaClsid, ref pvarShow, ref pvarSize);
      }

      #endregion

   }
}

Posted by SB패밀리