Web Win32/WTL 混合






4.83/5 (14投票s)
2002年8月30日
4分钟阅读

139351

2092
如何在 IExplorer 和 WTL 代码之间实现双向通信路径
引言
通常,当我们看到像 MS Money 这样的应用程序时,它看起来像 Internet Explorer 的布局,并且具有 Win32 应用程序的功能,我们不知道微软是否对我们隐藏了一些技术概念(我不知道任何,我向你保证 ;)),或者我们是否无法正确阅读微软 MSDN 文档。
本文将介绍完全重用 IExplorer 的方法,并尝试解释此示例的实现。实际上,它只是一个示例,而不是要用于“真实”应用程序的“真实”库。我只想展示一种执行此类实现的方法。代码没有完整的错误处理,因此,如果您想在“真实”应用程序中使用它,请记住,您需要审查一些关键步骤。
在进入下一部分之前,我邀请您下载示例源代码并进行编译。我认为这将有助于您更好地理解以下部分。
编译
代码有两个版本,一个是 VC6/WTL7.0,另一个是 VC7/WTL7.0。
您应该已安装最新的 Platform SDK,否则将在接口声明(如 IHTMLElement2
)中收到错误。
您可以在 msdn.microsoft.com 上免费下载 Platform SDK。
编译程序后,您可能会收到“库未注册”的错误;如果发生这种情况,您只需转到项目目录并执行 RegTLib WebWin32Sample.tlb。有时,您可能需要以这种方式注册 COM 服务器:WebWin32Sample.exe /RegServer
,只运行一次。
最终类
首先,我想向您展示“宿主”类是如何工作的,然后我将解释每一段代码和底层技巧。
class CWebWin32SampleView : public CWindowImpl<CWebWin32SampleView,CAxWindow> { public: DECLARE_WND_SUPERCLASS(NULL, CAxWindow::GetWndClassName()) // Our internet explorer control CWTLIExplorer m_pBrowser; // Our vars to Exchange information with HTML page... CString m_FirstName; CString m_LastName; CString m_Address; CString m_Country; BOOL m_ILikeThisSample; // html-hosted-page-form-based DDX BEGIN_HTML_DDX() BEGIN_FORM( "testForm" ) // Our Form Name ( at HTML page ) // Our exchange definitions... DDX_HTML_STRING( m_FirstName ,"firstName" ) DDX_HTML_STRING( m_LastName ,"lastName" ) DDX_HTML_STRING( m_Address,"address" ) DDX_HTML_LISTBOX( m_Country, "country" ); DDX_HTML_CHECKBOX( m_ILikeThisSample, "ILikeIt" ) END_FORM() END_HTML_DDX() // HTML Handlers // Here we can define HTML handlers hooks related to element at the // page loaded BEGIN_HTML_MSG_MAP(CWebWin32SampleView,m_pBrowser) // Set an event handler on buttonSumbmit.onclick COMMAND_HTML_HANDLER( ID_HTML_CLICK, "buttonSubmit", OnSubmit ) END_HTML_MSG_MAP() LRESULT OnSubmit( BOOL & bHandled ) { // Do Data Exchange with HTML page... DDX_HTML_EXCHANGE( m_pBrowser, false /* false=get | true=set */ ) // MessageBox text Composite CString totalMessage = m_FirstName + CString(" - ") + m_LastName + CString(" - " ) + m_Address; if ( m_ILikeThisSample ) totalMessage += CString("\n\nYou have checked it." ); else totalMessage += CString("\n\nYou have not checked it." ); totalMessage += CString("\n\nCountry Selected: ") + m_Country; // Show a message box MessageBox( totalMessage, "From CWebWin32SampleView::OnSubmit event handler.", MB_ICONINFORMATION ); // set handled bHandled = true; return 0; } BOOL PreTranslateMessage(MSG* pMsg); BEGIN_MSG_MAP(CWebWin32SampleView) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_SIZE, OnSize) // We receive this message when IExplorer is on a ready state for us // and we can Set handlers to the page... MESSAGE_HANDLER(WM_HTML_SETHANDLERS /* = WM_USER + 1 */ , OnHTMLSetHandlers ) END_MSG_MAP() LRESULT OnHTMLSetHandlers(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/ ) { // Activate event handlers map SET_HTML_MESSAGE_MAP(); return 0; } // Handler prototypes (uncomment arguments if needed): // LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, // LPARAM /*lParam*/, BOOL& /*bHandled*/) // LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, // HWND /*hWndCtl*/, BOOL& /*bHandled*/) // LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, // BOOL& /*bHandled*/) LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); };
BEGIN_HTML_MAP() ... END_HTML_MAP()
实际上,IExplorer 中的所有 HTML 元素都派生自 IHTMLElement
。在此接口中,我们可以找到使用某些方法在元素级别获取/设置事件处理程序的可能性,例如 put_onclick( VARIANT handler)
,variant 应包含一个 VT_IDISPATCH
变量,该变量引用一个 COM 对象。当触发此事件时,IExplorer 会尝试调用此 IDispatch
对象的默认方法。这非常适合我们的目的,因此我们需要定义一个包含默认方法的 COM 对象,当调用此方法时,我们只需“跳转”到我们的事件处理程序。
我用来执行此工作的对象是 WebWin32.WebWin32EventHandler
,它只有两个方法 SetHandler(LONG,LONG)
和 CallHandler
,后者是对象的默认方法。
SetHandler(LONG,LONG)
用于设置“回调”地址。为此,我们需要传递我们的对象地址(this
)和回调入口点的成员地址。
我们需要进行一些代码级别的“破解”,将持有成员地址的 Pointer
成员变量转换为 LONG
。
// ( This is a simulation ) [...] DWORD pointer_part_2 = (DWORD) this; DWORD pointer_part_1; [...] // pointer member prototype LRESULT (CWebWin32SampleView::*pVar) ( BOOL & ); // load our member pointer pVar = OnSubmit; // extract address memcpy( &pointer_part_1, ( char * ) &pVar, 4 ); [...] pHandler->SetHandler( pointer_part_1, pointer_part_2 ); [...] SetHandlerIntoHtmlElementOnClick( pHandler, "addressField" ); [...]
稍后,当 IExplore 触发我们的方法时,我们需要执行另一个“技巧”来“跳转”到我们的“回调”。
STDMETHODIMP CWebWin32EventHandler::CallHandler(void) { // __thiscall asm implementation DWORD pMember; DWORD pThis; pThis = m_pThis; pMember = m_pMember; BOOL bHandled=false; // this the same like do pObject->MemeberToCall( bParam ) // but we don't have // problems about what type of object or method prototype was defined, // this is a generic "thiscall" _asm { lea eax,[bHandled] ; BOOL & push eax ; push it mov ecx,[pThis] ; "this" object into ecx call pMember ; call the member pointer } return S_OK; }
这就是我们所需要知道的,以便将事件从 HTML 元素触发到我们的代码。
BEGIN_HTML_DDX() ... END_HTML_DDX()
由于我们拥有 IExplorer 实例的引用,因此这是最简单的事情,我们只需要查询当前文档的集合以找到正确的元素并查询其中的值。
Web -> Win32 路径
正如您在示例中看到的,通过按下 HTML 按钮,您可以隐藏/显示树视图,设置状态栏文本等。
好了,我们可以通过两种方式完成这项工作:使用上面描述的处理程序,或者使用另一种技术。
您可以使用 CAxWindow::SetExternalDisptach()
方法放置对象引用(COM 对象双接口)。一旦您将引用传递给此方法,您就可以通过 Explorer 页面的脚本代码访问此对象的 o方法 external.MyMethod)()
。
为此所需的步骤是
EVENTFN CWTLIExplorer::OnDocumentComplete( IDispatch* pDisp, VARIANT* URL ) { [.....] CAxWindow::SetExternalDispatch( (IDispatch*) _Module.m_LibraryObject ); // ignore about:blank if ( _bstr_t( bstrLocation ) != _bstr_t("about:blank") ) // Notify parent window we are ready and loaded... CWindow(GetParent()).SendMessage( WM_HTML_SETHANDLERS ,0,0 ); }
一旦设置了主对象库,正如您在 sample.html 脚本代码中所见,您就可以调用 o方法。
<script>
[...]
window.external.MyMessageBox "Hello!","Bye!"
[...]
window.external.HideTree()
[...]
window.external.ShowTree()
[...]
window.external.statusbartext = text
[...]
window.external.AddChildToTree( window.document.all("newitem").value )
</script>
您可以使用 VBScript 或 JScript,这没问题。
结束语
我确信我很快就会更新这篇文章,所以任何评论都将不胜感激。
好了,就这样,我希望您觉得这篇文章有用且有帮助。如果您认为您发现了任何错误,请随时与我联系或自行解决,我将非常乐意修复任何问题。再见!!