Last Update:06/30/2004
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 了
最簡單的方法是到 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。