WTL 7 心得筆記 (4): 使用 ActiveX Control

Last Update:06/30/2004

ATL 7.0/7.1 的 bug

WTL 7.x 在搭配 ATL 7.x 使用時,若用到了 ActiveX control,則會在執行的時候觸動 atlcom.h 中的某個 ASSERT:

ATLASSERT(!InlineIsEqualGUID(*m_plibid,GUID_NULL) && "Did you forget to pass the LIBID to CComModule::Init?");

這應是 ATL 7.x 中的 bug,解決的方法有兩種:

1. 將 Project Properties 的 Use of ATL 改成 Dynamic Link to ATL

2. 將 Project Properties 的 Use of ATL 改成 Static Link to ATL,同時將 tWinMain() 中的

hRes = _Module.Init(NULL, hInstance);

改成

GUID guid;
hRes = _Module.Init(NULL, hInstance, &guid);

這樣就可以解決這個討厭的 ASSERT bug 了

ActiveX Control Hosting

最簡單的方法是到 CodeProject 下載 Jesus Salas 寫的 WtlExt.h 來使用,底下是這個 .h 檔的部份列表:

// WtlExt.h : WTL Extensions/Helpers
// Copyright: Jesus Salas 2002 ( jesuspsalas@hotmail.com )
// License  : none, feel free to use it.
/////////////////////////////////////////////////////////////////////////////

#pragma once

template <class T,class Interface>
class CWTLDispEventHelper : public IDispEventImpl<0,T>
{
public:
	CComPtr<IUnknown> m_pUnk;
	HRESULT EasyAdvise(IUnknown* pUnk) 
	{  
		m_pUnk = pUnk;
		AtlGetObjectSourceInterface(pUnk,&m_libid, &m_iid, &m_wMajorVerNum, &m_wMinorVerNum);
		return DispEventAdvise(pUnk, &m_iid);
	}
	HRESULT EasyUnadvise() 
	{ 
		AtlGetObjectSourceInterface(m_pUnk,&m_libid, &m_iid, &m_wMajorVerNum, &m_wMinorVerNum);
		return DispEventUnadvise(pUnk, &m_iid);
	}

};


template <class T, class Interface>
class CWTLAxControl :	public CComPtr<Interface>,
			public CWindowImpl<CWTLAxControl,CAxWindow>, 
			public CWTLDispEventHelper<T,Interface>
{
public:

	BEGIN_MSG_MAP(CWTLAxControl)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
	END_MSG_MAP()

	LRESULT OnCreate( UINT uMsg, WPARAM wParam , LPARAM lParam, BOOL & bHandled )
	{
		LRESULT lRet;
		// We must call DefWindowProc before we can attach to the control.
		lRet = DefWindowProc( uMsg, wParam,lParam );
		// Get the Pointer to the control with Events (true)
		AttachControl(true);
		return lRet;
	}


	HRESULT AttachControl( BOOL bWithEvents = false ) 
	{
		HRESULT hr = S_OK;
		CComPtr<IUnknown> spUnk;
		// Get the IUnknown interface of control
		hr |= AtlAxGetControl( m_hWnd, &spUnk);

		if (SUCCEEDED(hr))
			// Query our interface
			hr |= spUnk->QueryInterface( __uuidof(Interface), (void**) (CComPtr<Interface>*)this);

		if ( bWithEvents && ! FAILED(hr) )
			 // Start events
			 hr|= EasyAdvise( spUnk );

		return hr;
	};
};

CWTLAxControl 這個模板同時做好了 hosting window 和 control advise 的動作,所以在使用時會比較方便些。例如要使用 browser control 的話,在 WtlExt.h 的後半有一個 sample class:

#include <ExDispid.h>

#define EVENTFN void __stdcall
class CWTLIExplorer : public CWTLAxControl<CWTLIExplorer,IWebBrowser2>
{
public:
	// BEGIN_MSG_MAP is optional, you don't need to define or use it if you don't want
	BEGIN_MSG_MAP( CWTLIExplorer )
		MESSAGE_HANDLER(WM_CREATE, OnCreate)			
	END_MSG_MAP()

	LRESULT OnCreate(UINT uMsg, WPARAM wParam , LPARAM lParam, BOOL& bHandled)
	{
		// First you must CWTLAxControl<...,...>::OnCreate ( it set this message Handled )
		return CWTLAxControl<CWTLIExplorer,IWebBrowser2>::OnCreate( uMsg, wParam, lParam, bHandled );
	}

	// BEGIN_SINK_MAP is optional, you don't need to define or use it if you don't want
	BEGIN_SINK_MAP( CWTLIExplorer )
		SINK_ENTRY(0, DISPID_NAVIGATECOMPLETE2, OnNavigateComplete2 )
	END_SINK_MAP()

	EVENTFN OnNavigateComplete2( IDispatch* pDisp,  VARIANT* URL )
	{
		MessageBox( "OnNavigateComplete2" );
	}
};

這個例子中它是透過 IWebBrowser2 來控制 browser control,所以它只能抓到 DWebBrowserEvents2 中的 event (上例中是抓 NavigateComplete2)。使用這個 class 的方法很簡單,如下例所示:

 

CWTLIExplorer* m_pBrowser;

// init web browser control
m_pBrowser = new CWTLIExplorer(this->m_hWnd);
if (::IsWindow(m_pBrowser->Create(m_hWnd, rcDefault, _T("about:blank"), WS_CHILD)))
{
	CComVariant vNull;
	CComVariant vUrl(sUrl);

	return (*m_pBrowser)->Navigate2(&vUrl, &vNull, &vNull, &vNull, &vNull);
}

至於要如何對付 IDocHostUIHandler、IDocHostShowUI、IOleCmdTarget 等等的 event 和 method 呢?這個就比較麻煩些了,你必須製作一個 implement 這些 interface 的 COM 物件,然後再透過 ATL COM Event sink 將對應的 event 串過去 (可參考 CodeProject 的 Pop-up Blocker 程式:http://www.codeproject.com/atl/PopupBlocker2.asp),並把 browser 的 host site 設到這個 COM object 去。

如果只要處理 event 而不管 callback method 的話,另一個可能是把 CWTLDispEventHelper 改寫成可以支援 multiple interface。