웹, HTML

BrowserHelperObject(BHO) 개념 및 프로젝트 생성 예제

디버그정 2008. 7. 26. 15:44
COM(2) - BrowserHelperObject(BHO)
조회(127)
C/C++, MFC | 2008/05/04 (일) 11:06
추천하기 | 스크랩하기

브라우저 헬퍼 오브젝트

인터넷 익스플로러(버전 4.0 이상) 에서는 브라우저 헬퍼 오브젝트(Browser Helper Object)라는 DLL 컴포넌트를 통해 제3의 개발자가 인터넷 익스플로러에 자신이 원하는 기능을 추가시킬 수 있는 길을 열어놓고 있다. 인터넷 익스플로러는 자신이 새로 기동될 때마다 브라우저 헬퍼 오브젝트로 등록된 COM 객체를 생성하고 그로부터 특정 인터페이스를 얻고 그 인터페이스의 어떤 멤버함수를 호출하게 된다. 이는 곧 제3의 개발자가 개발한 DLL 컴포넌트를 인터넷 익스플로러 프로세스의 주소 공간 속으로 접근시키는 길을 열어놓은 것이다.
 
브라우저 헬퍼 오브젝트는 단 하나의 인터페이스, 바로 IObjectWithSite 인터페이스를 제공하기만 하면 되는 매우 간단한 DLL 컴포넌트이다. 그러나 IObjectWithSite 인터페이스를 제공하는 모든 DLL 컴포넌트가 전부 다 브라우저 헬퍼 오브젝트냐 하면 그건 아니다. 하지만 어떤 DLL 컴포넌트가 IObjectWithSite 인터페이스를 제공한다면 일단 브라우저 헬퍼 오브젝트가 될 조건 중 한 개를 만족시키고 있는 것이다.
브라우저 헬퍼 오브젝트가 되기 위한 또 다른 조건은 DLL 컴포넌트의 CLSID를 인터넷 익스플로러가 미리 정해놓은 어떤 레지스트리 키 밑에 등록시키는 것이다. IObjectWithSite 인터페이스를 제공하는 DLL 컴포넌트가 브라우저 헬퍼 오브젝트로 동작하기를 원한다면 레지스트리의 다음 키 밑에 자신의 CLSID를 등록시켜야 할 것이다.
 
l        레지스트리 경로 브라우저 헬퍼 오브젝트 등록위치
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects
 
ocidl.idl 파일에는 IObjectWithSite 인터페이스에 대한 정의를 볼 수 있다.
 
Interface IObjectWithSite : IUnknown
{
typedef IObjectWithSite *LPOBJECTWITHSITE;
 
HRESULT SetSite(
  [in] IUnknown *pUnkSite
);
 
HRESULT GetSite(
  [in] REFIID riid,
  [out, iid_is(riid)] void **ppvSite
);
}
 
SDK 문서를 열고 SetSite 함수의 설명을 찾아보면 사이트의 IUnknown 포인터를 객체에게 제공하는 함수 라고 나온다. 이 말이 무슨 말인가 하면 사이트는 객체를 생성한 놈인데 여기서는 인터넷 익스플로러가 되고 객체는 말 그대로 객체, 즉 브라우저 헬퍼 오브젝트가 된다. 인터넷 익스플로러가 SetSite 함수를 호출함으로써 브라우저 헬퍼 오브젝트에게 자신의 IUnknown 포인터를 전달한다는 말이다. IUnknown 포인터를 전달받은 브라우저 헬퍼 오브젝트는 이것을 잘 보관하고 있다가 나중에 유효 적절하게 사용할 수 있다. 객체가 사이트로부터 IUnknown 포인터를 받은 다음에는 반드시 AddRef 함수를 호출해서 참조카운트를 증가시켜 주어야 한다.
 
만약 SetSite 함수가 2회 이상 불릴 경우 객체가 이미 사이트의 IUnknown 포인터를 보관하고 있다면 가지고 있던 IUnknown Release 함수를 호출한 다음 새로운 IUnknown AddRef 함수를 호출하고 그 포인터를 보유하고 있으면 된다.
 
GetSite 함수는 객체가 보유하고 있는 가장 최신의 IUnknown 포인터를 제공하는 함수이다. 객체는 SetSite 함수가 호출됨으로써 사이트의 IUnknown 포인터를 보유하게 되고, GetSite 함수가 호출됨으로써 보유하고 있던 사이트 IUnknown 포인터를 제공할 수 있게 된다. 만약 객체가 사이트의 IUnknown 포인터를 가지고 있지 않다면 실패코드를 리턴해 줄 수 있다.
 
이상은 SDK 문서에서 제시하는 구현 가이드 라인이다. IObjectWithSite 인터페이스를 구현할 때 우리는 이 가이드 라인을 지켜가면서 우리가 원하는 다른 기능을 추가시킬 수 있다.
 
인터넷 익스플로러뿐만 아니라 윈도우 탐색기는 자신이 새로 기동될 때마다 레지스트리의 Browser Helper Objects 키를 검색해서 그 밑에 등록된 CLSID를 가지고 CoCreateInstance() 를 호출하여 브라우저 헬퍼 오브젝트를 생성하게 된다.
그런 다음에는 QueryInterface() 를 호출해서 IObjectWithSite 인터페이스를 요구하고
이를 얻으면 IObjectWithSite 인터페이스의 SetSite() 를 호출한다. 이때 SetSite()의 매개변수로 사이트가 되는 인터넷 익스플로러(혹은 윈도우 탐색기) IUnknown 포인터가 전달된다.
 
브라우저 헬퍼 오브젝트는 사이트가 전해준 IUnknown 포인터를 통해서 사이트, 즉 인터넷 익스플로러(혹은 윈도우 탐색기)의 다른 모든 인터페이스들을 얻을 수 있다. 인터넷 익스플로러는 내부적으로 WebBrowser 라고 불리는 ActiveX 컨트롤을 포함하고 있다. 브라우저 헬퍼 오브젝트는 전달받은 IUnknown 포인터를 통해 WebBrowser 컨트롤의 모든 인터페이스를 얻을 수 있다.
 
WebBrowser 컨트롤이 제공하는 인터페이스 중 가장 대표적인 인터페이스로는 IWebBrowser2가 있다. IWebBrowser2 인터페이스는 인터넷 익스플로러의 메뉴와 버튼을 통해 제공되는 기능들(예를 들면 앞으로, 뒤로, 중지, 새로고침, 이동 등)을 똑같이 수행할 수 있도록 다양한 함수들을 제공한다.
 
 

ATL 프로젝트 마법사 시작하기

프로젝트 생성

새 프로젝트 à Visual C++ 프로젝트 à ATL 프로젝트
 

ATL 프로젝트 마법사

프록시/스텁 코드 병합 허용

MIDL에 의해 생성되는 프록시/스텁 소스 코드를 별도의 프로젝트로 분리할 것인가 아니면 현재 프로젝트에 병합시킬 것인가를 선택하는 옵션이다. 이 옵션을 선택하면 프로젝트의 결과 파일(서버 유형이 DLL일 경우에만 이 옵션을 선택할 수 있으므로 결과 파일은 DLL 파일이 되겠다)에 프록시/스텁 코드가 합쳐지게 된다. 이 옵션을 선택하지 않으면 프록시/스텁 DLL을 만들기 위한 별도의 프로젝트가 추가된다.
이 옵션을 선택하지 않고 컴포넌트를 만들 경우 만약 컴포넌트가 커스텀 인터페이스를 가지고 있다면 프로젝트를 만들고 나중에 따로 반드시 프록시/스텁 DLL을 빌드해야 한다.
그러나 만약 컴포넌트가 커스텀 인터페이스를 가지고 있지 않고 OLE Automation 호환 인터페이스와 표준 인터페이스를 가지고 있을 경우, 추가 프로젝트를 통해 프록시/스텁 DLL을 따로 빌드할 필요가 없다.
왜냐하면 그런 인터페이스에 대한 프록시/스텁 DLL은 시스템에 이미 존재하기 때문이다. 따라서 이런 경우에는 프록시/스텁 코드 병합 허용 옵션을 선택하지 않는 것이 코드 사이즈를 줄이는데 도움이 된다.
 

표준인터페이스란?

표준인터페이스란 간단히 말해서 마이크로소프트가 정의하고 공표한 인터페이스를 말한다. 만약 제3자가 표준인터페이스를 구현하였다면 이에 대한 별도의 프록시/스텁 DLL을 제작할 필요가 없다. 왜냐하면 마이크로소프트가 표준 인터페이스를 공표할 때 이미 프록시/스텁 DLL도 같이 배포했기 때문이다. 윈도우 운영체제를 갖는 모든 시스템에는 표준 인터페이스에 대한 프록시/스텁 DLL이 설치되어 있다고 봐도 무방하다.
 

MFC지원

ATL 개발 환경에서 MFC를 사용할지 여부를 묻는 옵션이다. 기본적으로 ATL 프로젝트는 MFC를 지원하지 않는다.
 

COM+ 1.0 지원

COM+ 분산 트랜잭션 서비스(예전에는 MTS라고 불렀다)를 사용해야 하는 컴포넌트를 제작할 경우 이 옵션을 선택한다. 이 옵션을 선택하면 stdafx.h 파일에 #include <mtx.h> 라인이 추가되고 링커옵션에 mtx.lib, mtxguid.lib가 추가된다.
 

ATL 프로젝트 마법사의 생성 파일 확인

MyHelper.idl

이 프로젝트에서 만들게 될 모든 인터페이스와 컴포넌트 그리고 타입 라이브러리에 대한 IDL 코드가 여기에 작성된다. 이 파일은 프로젝트 빌드 과정에서 MIDL에 의해 컴파일되고 프록시/스텁 DLL을 제작하기 위한 소스 파일과 타입 라이브러리를 생성하는데 사용된다.
MIDL이 만들어내는 파일에는 MyHelper.h, MyHelper_i.c, MyHelper_p.c, MyHelper.tlb, dlldata.c 5개가 있다
 

MyHelper.cpp

DLL 진입코드 (DllMain) DLL 컴포넌트가 가지고 있어야 할 4개의 필수 내보내기(export) 함수 (DllCanUnloadNow, DllGetClassObject, DllRegisterServer, DllUnregisterServer)에 대한 구현 코드를 담고 있다.
 

MyHelper.rc

이 프로젝트에서 사용하는 모든 윈도우 리소스(메뉴, 문자열 테이블, 대화상자, 각종 컨트롤 등)에 대한 리소스 스크립트를 담고 있다.
 

MyHelper.rgs

컴포넌트를 레지스트리에 등록시키기 위해 필요한 레지스트라 스크립트를 담고 있다.
 

MyHelper.def

내보내기 함수를 위한 모듈 정의문을 담고 있다. DLL 컴포넌트는 DLLCanUnloadNow, DllGetClassObject, DllRegisterServer, DllUnregisterServer 등 이 4가지 함수를 내보내기 목록에 포함시켜야 한다.
 

stdafx.h / stdafx.cpp

Pre-Compiled Header 파일에 포함될 헤더파일을 stdafx.h 파일에 지정한다. 개발자가 stdafx.h 파일에 자주 사용하는 헤더파일을 포함시키면 stdafx.cpp 파일이 컴파일되면서 (프로젝트명.pch) 라는 Pre-Compiled Header 파일이 생성된다.
일단 한번 생성된 PCH 파일은 오브젝트 파일(컴파일러가 소스파일을 컴파일 할 때 생기는 중간 결과 파일)로서 링크 과정에서 계속 재사용 된다.
따라서, stdafx.h 파일이 수정되지 않는 한 PCH 파일은 계속 재사용된다. stdafx.h 파일에는 보통 MFC ATL의 필수 헤더파일들이 포함되는데, 개발자는 여기에 자주 사용하는 헤더파일을 추가시킬 수 있다.
 

Resource.h

리소스 ID (식별자)와 각종 상수에 대한 정의를 담고 있다.
 

ATL 프로젝트 마법사 생성 코드 확인

// MyHelper.cpp : DLL 내보내기의 구현입니다.
 
#include "stdafx.h"
#include "resource.h"
#include "MyHelper.h"
 
class CMyHelperModule : public CAtlDllModuleT< CMyHelperModule >
{
public :
             DECLARE_LIBID(LIBID_MyHelperLib)
             DECLARE_REGISTRY_APPID_RESOURCEID(IDR_MYHELPER, "{A5ACEAF5-31E4-47C6-84F8-8529BE2D4593}")
};
 
CMyHelperModule _AtlModule;
 
 
// DLL 진입점입니다.
extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
             hInstance;
    return _AtlModule.DllMain(dwReason, lpReserved);
}
 
 
// DLL OLE에 의해 언로드될 수 있는지 결정하는 데 사용됩니다.
STDAPI DllCanUnloadNow(void)
{
    return _AtlModule.DllCanUnloadNow();
}
 
 
// 클래스 팩터리를 반환하여 요청된 형식의 개체를 만듭니다.
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
    return _AtlModule.DllGetClassObject(rclsid, riid, ppv);
}
 
 
// DllRegisterServer - 시스템 레지스트리에 항목을 추가합니다.
STDAPI DllRegisterServer(void)
{
    // 개체, 형식 라이브러리 및 형식 라이브러리에 들어 있는 모든 인터페이스를 등록합니다.
    HRESULT hr = _AtlModule.DllRegisterServer();
             return hr;
}
 
 
// DllUnregisterServer - 시스템 레지스트리에서 항목을 제거합니다.
STDAPI DllUnregisterServer(void)
{
             HRESULT hr = _AtlModule.DllUnregisterServer();
             return hr;
}
 
 

ATL 단순 개체 마법사 시작하기

클래스 추가 마법사

추가 à 클래스 추가 à ATM 단순개체 à 열기
 

ATL 단순 개체 마법사

약식이름

보통 만들고자 하는 클래스 이름과 동일한 이름을 입력

CoClass

IDL 코드에 들어가는 coclass 이름, COM 클래스(혹은 객체)의 이름을 넣는다.

형식

IDL 코드에서 coclass에 대한 helpstring으로 들어가는 문자열을 지정한다.

인터페이스

이 클래스에서 기본적으로 구현하고자 하는 인터페이스명을 지정한다.

ProgID

이 클래스를 통해 구현되는 컴포넌트의 ProgID를 지정한다.
 

ATL 단순 개체 마법사 옵션 설정 스레딩 모델

단일

COM 클래스는 CComObjectRootEx<CComSingleThreadModel> 템플릿 클래스를 상속받는다. 개발자 입장에서는 이 COM 클래스를 만들 때 멀티 스레드에 의해 동시에 접근되는 상황에 대비한 코드의 안정성에 대해 전혀 신경 쓰지 않아도 된다.
이 옵션을 선택하면 서버 타입이 DLL인 경우, 레지스트리의 InprocServer32 키 밑에 아무런 이름값도 들어가지 않는다.
런타임시 COM 서브 시스템은 이름값이 없다는 사실을 바탕으로 이 컴포넌트는 여러 스레드에 의해서 동시에 접근되는 상황을 가정하지 않은 구식(Legacy) 컴포넌트라고 판단하고 동시 접근이 일어날 경우 중간에서 동시 접근을 막고 접근 자체를 동기화 시킨다.
 

아파트

COM 클래스는 CComObjectRootEx<CComSingleThreadModel> 템플릿 클래스를 상속받는다. 개발자 입장에서는 이 COM 클래스를 만들 때 멀티 스레드에 의해 동시에 접근되는 상황에 대비하여 코드를 안전하게 만드는 것에 대해 전혀 신경 쓰지 않아도 된다.
이 옵션을 선택하면 서버 타입이 DLL인 경우, 레지스트리의 InprocServer32키 밑에 ThreadingModel이란 이름으로 apartment라는 문자열 값이 들어간다.
런타임시 COM 서브 시스템은 apartment란 값을 바탕으로 이 컴포넌트는 여러 스레드에 의해서 동시에 접근 될 경우 이를 대비한 안전한 코드가 준비되지 않았다고 판단하고 동시 접근이 일어날 경우 중간에서 동시 접근을 막고 접근 자체를 동기화 시킨다.
이렇게 COM 서브 시스템이 여러 스레드에 의한 동시 접근을 동기화시킴으로써 컴포넌트는 안전한 코드인 것처럼 동작할 수 있는데 이때 컴포넌트는 아파트 라는 추상적인 보호 구역 들어갔다고 말한다. 이를 다른 말로 STA(Single Threaded Apartments) 라고 부르기도 한다.
 

자유형

COM 클래스는 CComObjectRootEx<CComMultiThreadModel> 템플릿 클래스를 상속받는다. 개발자 입장에서는 이 COM 클래스를 만들 때 멀티 스레드에 의해 동시 접근되는 상황에 대비하여 코드를 안전하게 만들어야 할 의무가 있다.
이 옵션을 선택하면 서버 타입이 DLL인 경우, 레지스트리의 InprocServer32키 밑에 ThreadingModel 이란 이름으로 free라는 값이 들어간다.
런타임시 COM 서브 시스템은 free란 값을 바탕으로 이 컴포넌트는 여러 스레드에 의해서 동시에 접근될 경우 이를 대비한 안전한 코드가 준비되었다고 보고 여러 스레드가 동시에 접근하는 것을 허용하게 된다.
이때도 컴포넌트는 안전한 구역을 의미하는 아파트에 들어갔다고 말할 수 있는데, 이 경우엔 MTA(Multi Threaded Apartment)라고 부른다.
 

모두

COM 클래스는 CComObjectRootEx<CComMultiThreadModel> 템플릿 클래스를 상속받는다. 개발자 입장에서는 이 COM 클래스를 만들 때 멀티 스레드에 의해 동시에 접근되는 상황에 대비하여 코드를 안전하게 만들어야 할 의무가 있다.
이 옵션을 선택하면 서버 타입이 DLL인 경우, 레지스트리의 InprocServer32키 밑에 ThreadingModel이란 이름으로 Both라는 값이 들어간다.
런타임시 COM 서브 시스템은 Both란 값을 바탕으로 이 컴포넌트는 여러 스레드에 의해서 동시에 접근될 경우 이를 대비한 안전한 코드가 준비되었다고 보고 클라이언트가 원하는 바에 따라 MTA 혹은 STA에 컴포넌트를 생성하게 된다.
 

중립(Windows 2000 전용)

COM 클래스는 CComObjectRootEx<CComMultiThreadModel> 템플릿 클래스를 상속받는다. 개발자 입장에서는 이 COM 클래스를 만들 때 멀티 스레드에 의해 동시에 접근되는 상황에 대비하여 코드를 안전하게 만들어야 할 의무가 있다.
이 옵션을 선택하면 서버 타입이 DLL인 경우, 레지스트리의 InprocServer32키 밑에 ThreadingModel이란 이름으로 Neutral이란 값이 들어간다.
런타임시 COM 서브 시스템은 Neutral이란 값을 바탕으로 이 컴포넌트는 여러 스레드에 의해서 동시에 접근될 경우 이를 대비한 안전한 코드가 준비되었다고 보고 클라이언트가 원하는 바에 따라 MTA 혹은 STA에 컴포넌트를 생성하게 된다.
Neutral 모델은 Windows2000에서 지원되는 것으로 컨텍스트 전환시 발생하는 스레드 스위칭이 일어나지 않도록 컨텍스트를 중립 영역(TNA(Thread Neutral Apartments)라고 부른다)에 놓는다.
 

ATL 단순 개체 마법사 옵션 설정 인터페이스

이중

구현하고자 하는 인터페이스가 이중(dual) 인터페이스임을 나타낸다. 이중 인터페이스는 IUnknown이 아닌 IDispatch라는 표준 인터페이스를 상속받는 인터페이스를 말한다.
 

사용자 지정

구현하고자 하는 인터페이스가 커스텀 인터페이스임을 나타낸다. 커스텀 인터페이스란 표준 인터페이스와 반대되는 개념으로, 3자가 만든 인터페이스를 말한다.
만약 커스텀 인터페이스에서 사용하는 데이터 타입이 OLE Automation 호환 데이터 타입만으로 되어 있다면 자동화 호환 옵션을 선택할 수 있다. OLE Automation 호환 데이터 타입이란 VARIANT 구조체에 정의된 데이터 타입을 말한다.
만약 자동화 호환 옵션을 체크하면 IDL 파일의 interface 키워드 앞에 oleautomation 특성이 추가 될 것이다. oleautomation 특성을 가진 인터페이스는 별도의 프록시/스텁 DLL을 만들지 않아도 된다. 왜나하면 이러한 인터페이스들은 유니버셜 마샬러(oleaut32.dll)에 의해서 마샬링이 처리되기 때문이다.
 

ATL 단순 개체 마법사 옵션 설정 집합체

다른 컴포넌트가 이 컴포넌트를 집합체(Aggregation)로 만들 수 있도록 COM 클래스에 DECLARE_AGGREGATABLE 매크로를 추가시킨다. 이 매크로는 IClassFactory::CreateInstance 멤버함수가 호출될 때 pUnkOuter 매개변수에 NULL이 아닌 값이 넘어올 경우(, 바깥쪽 컴포넌트의 IUnknown 포인터가 NULL이 아닌 경우) CComAggObject<>를 사용하여 COM 클래스를 생성하고, NULL 값이 넘어오면 CComObject<>를 사용하여 COM 클래스를 생성한다.
 

아니오

다른 컴포넌트가 이 컴포넌트를 집합체(Aggregation)로 만들 수 없도록 COM 클래스에 DECLARE_NOT_AGGREGATABLE 매크로를 추가시킨다. 이 매크로는 IClassFactory::CreateInstance 맴버함수가 호출될 때 pUnkOuter 매개변수에 NULL이 아닌 값이 넘어올 경우 CLASS_E_NOAGGREGATION 에러값을 리턴하도록 하고, NULL값이 넘어오면 CComObject<>를 사용하여 COM 클래스를 생성한다.
 

전용

다른 컴포넌트가 Aggregation 할 수 있도록 COM 클래스에 DECLARE_ONLY_AGGREGATABLE 매크로를 추가시킨다. 이 매크로는 ICLassFactory::CreateInstance 멤버함수가 호출될 때 pUnkOuter 매개변수에 NULL이 아닌 값이 넘어올 경우 CComAggObject<>를 사용하여 COM 클래스를 생성하고, NULL 값이 넘어오면 E_FAIL 값을 리턴하도록 만든다.
 

ATL 단순 개체 마법사 옵션 설정 지원

ISupportErrorInfo

COM 클래스는 ISupportErrorInfo 인터페이스를 상속받는다. 아울러 ISupportErrorInfo 인터페이스의 구현 코드가 추가된다.
 

연결지점

COM 클래스는 이벤트 소스로서 기능을 수행하기 위한 클래스를 상속받는다. , IConnectionPointContainerImpl<T> 클래스와 CProxy_XXXXEvents<T> 클래스를 상속받게 된다.
 

자유 스레드된 마샬러

COM 클래스에 자유 스레드된 마샬러를 생성하는 코드가 추가된다. 이 옵션은 스레딩 모델이 모두 중립 일 경우에만 선택할 수 있다.
 

IObjectWithSite(IE 객체 지원)

COM 클래스는 IObjectWithSite 인터페이스를 구현한 IObjectWithSiteImpl<T> 클래스를 상속받는다.
 
 

ATL COM 클래스 프레임워크

 
// MyObject.h : CMyObject의 선언입니다.
 
#pragma once
#include "resource.h"       // 주 기호입니다.
 
#include "MyHelper.h"
#import "shdocvw.dll" raw_interfaces_only
 
// CMyObject
 
class ATL_NO_VTABLE CMyObject :
             public CComObjectRootEx<CComSingleThreadModel>,
             public CComCoClass<CMyObject, &CLSID_MyObject>,
             public IObjectWithSiteImpl<CMyObject>,
             public IMyObject
{
public:
             CMyObject()
             {
             }
 
DECLARE_REGISTRY_RESOURCEID(IDR_MYOBJECT)
 
DECLARE_NOT_AGGREGATABLE(CMyObject)
 
BEGIN_COM_MAP(CMyObject)
             COM_INTERFACE_ENTRY(IMyObject)
             COM_INTERFACE_ENTRY(IObjectWithSite)
END_COM_MAP()
 
 
             DECLARE_PROTECT_FINAL_CONSTRUCT()
 
             HRESULT FinalConstruct()
             {
                           return S_OK;
             }
            
             void FinalRelease()
             {
             }
 
public:
 
             CComQIPtr<SHDocVw::IWebBrowser2> m_spBrowser2;
 
             static void CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
             {
                           SYSTEMTIME st;
                           GetLocalTime(&st);
                           TCHAR szBuf[1024];
                           GetWindowText(hwnd, szBuf, 1024);
                          
                           TCHAR* szTemp = _tcsstr(szBuf, " - Time: ");
                           if(szTemp != NULL)
                                        szTemp[0] = '\0';
 
                           wsprintf(szBuf, "%s - Time: %2d:%2d:%2d", szBuf, st.wHour, st.wMinute, st.wSecond);
                           SetWindowText(hwnd, szBuf);
 
             }
 
             STDMETHOD(SetSite)(IUnknown* pUnkSite)
             {
                           if(pUnkSite != NULL)
                           {
                                        m_spBrowser2 = pUnkSite;
                                        HWND hWnd = NULL;
                                        m_spBrowser2->get_HWND((long*)&hWnd);
                                        SetTimer(hWnd, 0, 60000, TimerProc);
                           }
                          
                           return IObjectWithSiteImpl<CMyObject>::SetSite(pUnkSite);
             }
};
 
OBJECT_ENTRY_AUTO(__uuidof(MyObject), CMyObject)
 
CMyObject 클래스를 보면 CComObjectRootEx, CComCoClass, IObjectWithSiteImpl, IMyObject를 상속받고 있음을 알 수 있다.
CMyObject 클래스가 첫 번째로 상속받는 CComObjectRootEx 템플릿 클래스는 내부적으로 IUnknown 인터페이스를 구현하고 있다(비록 완벽하게 구현한 것은 아니지만 일단 그렇게 이해하고 있자). 따라서 CComObjectRootEx 템플릿 클래스를 상속받는 CMyObject 클래스는 IUnknown 인터페이스의 구현에 대해 신경쓰지 않아도 된다. , CMyObject 클래스는 반드시 인터페이스 맵을 가지고 있어야 한다. 위의 코드에서 BEGIN_COM_MAP, END_COM_MAP 매크로를 볼 수 있는데, 이것을 인터페이스 맵 이라한다.
 
ATL COM 클래스를 통해 어떤 인터페이스를 구현하고자 할 때는 그 인터페이스를 상속 받고 나서 이를 인터페이스 맵에 반드시 등록시켜 줘야 한다. 인터페이스 맵에 인터페이스를 등록할 때는 COM_INTERFACE_ENTRY 매크로를 인터페이스 맵 사이에 넣으면 된다. 이런식으로 번거롭게 인터페이스 맵을 관리하는 까닭은 IUnknown::QueryInterface 함수가 제대로 동작하게 하기 위해서다.
CMyObject 클래스는 IObjectWithSite 인터페이스를 구현하고자 하므로 인터페이스 맵에 COM_INTERFACE_ENTRY(IObjectWithSite) 매크로를 반드시 넣어주어야 한다. 만약 CMyObject 클래스에 IFirstKiss 라는 인터페이스를 추가로 구현하고자 한다면 우리는 다음과 같은 작업을 해야 할 것이다.
 
l        CMyObject 클래스로 하여금 IFirstKiss 인터페이스를 상속받도록 한다.
l        인터페이스 맵에 COM_INTERFACE_ENTRY(IFirisKiss)를 추가한다.
l        IFirstKiss 인터페이스의 멤버함수를 구현한다.
 
CComObjectRootEx 템플릿 클래스가 제공하는 또 한 가지 중요한 기능은 멀티 스레드에 의한 동시 접근에 대비하여 코드를 안전하게 하기 위한 방법의 일환으로 Lock, Unlock 함수를 통해 크리티컬 섹션(Critical Section)을 제공하는 것이다. 동기화를 요구하는 코드의 시작과 끝에 Lock 함수와 Unlock 함수를 넣어 줌으로써 간단하게 멀티 스레드에 의한 동시 접근을 동기화 시킬 수 있다.
 
CMyObject 클래스가 두 번째로 상속받는 CComCoClass 템플릿 클래스는
컴포넌트의 CLSID를 얻기 위한 함수와 에러 정보를 전달하는 함수,
COM 클래스의 인스턴스를 생성하는 함수 등을 제공한다.
CComCoClass 템플릿 클래스는 내부적으로 DECLARE_CLASSFACTORY 매크로와 DECLARE_AGGREGATABLE 매크로를 선언함으로써 기본 클래스 팩토리와 집합체(Aggregation) 모델을 정의하고 있다.
만약 CComCoClass 템플릿 클래스에 정의된 기본 클래스 팩토리와 집합체 모델을 변경하고 싶으면 유도 클래스에 다른 매크로를 선언해 주면 된다. 앞서 ATL 단순 개체 마법사를 통해 집합체를 허용하지 않기로 했으므로 CMyObject 클래스에 DECLARE_NOT_AGGREGATABLE 매크로가 선언된 것을 볼 수 있다.
 
 
 
출처: Visual C++.Net Programming Bible(삼양출판사)