CDHtmlDialog探索----WebBrowser扩展和网页Javascript错误处理 当WebBrowser控件(CDHtmlDialog自动创建了WebBrowser控件)加载的网页中含有错误Javascript代码时默认情况下控件会弹出错误信息提示对话框,相对于用户体验来说这样的提示完全不是开发人员想要的,针对这个问题有两个解决方案,一是完全屏蔽掉错误提示,二是控制错误的提示并且记录错误信息同时也可以控制出现错误后Javascript是否继续执行。

1、屏蔽错误信息提示

1
m_pBrowserApp->put_Silent(VARIANT_TRUE);

在CDHtmlDialog::OnInitDialog()的代码中首先了创建WebBrowser控件,然后把控件的Browser对象赋值给m_pBrowserApp(这是CDHtmlDialog完成的不需要自己处理)。WebBrowser的put_Silent函数在官方给出的说明是禁用所有的对话框,但例外情况是它不会影响SSL安全认证需要的进示对话框。绝大多数情况下这就可以解决问题了,记得很久以前我遇到过一种情况就是虽然调用了put_Silent但是还是有极个别的js错误是无法屏蔽掉的依然会显示出来(在网页含有嵌套页面时会错误无法屏蔽,不知道是否还有其它情况),现在找不到这样的网页了,如果谁遇到这种情况了建议给我发上个URL让我也重温一下当年阳光灿烂的时刻。

2、控制错误提示并进行记录

  这要比第一种方法复杂上许多,简短的来说就是自定义COleControlSite类并实现IOleCommandTarget接口,IOleCommandTarget接口是错误控制的关健,错误发生时会触发此接口的Exec函数并为nCmdID参数赋值为OLECMDID_SHOWSCRIPTERROR,这样就可以得到错误信息了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
IOleCommandTarget : public IUnknown
    {
    public:
        virtual /* [input_sync] */ HRESULT STDMETHODCALLTYPE QueryStatus(
            /* [unique][in] */ __RPC__in_opt const GUID *pguidCmdGroup,
            /* [in] */ ULONG cCmds,
            /* [out][in][size_is] */ __RPC__inout_ecount_full(cCmds) OLECMD prgCmds[  ],
            /* [unique][out][in] */ __RPC__inout_opt OLECMDTEXT *pCmdText) = 0;
         
        virtual HRESULT STDMETHODCALLTYPE Exec(
            /* [unique][in] */ __RPC__in_opt const GUID *pguidCmdGroup,
            /* [in] */ DWORD nCmdID,
            /* [in] */ DWORD nCmdexecopt,
            /* [unique][in] */ __RPC__in_opt VARIANT *pvaIn,
            /* [unique][out][in] */ __RPC__inout_opt VARIANT *pvaOut) = 0;
         
    };
    

现在我们开始实现自定义的COleControlSite

1
2
3
4
5
6
7
8
9
10
11
12
13
class CMyControlSite : public COleControlSite
 
public
    CMyControlSite(COleControlContainer *pCntr):COleControlSite(pCntr) {}
 
protected
 
    DECLARE_INTERFACE_MAP() 
    BEGIN_INTERFACE_PART(OleCommandTarget, IOleCommandTarget) 
        STDMETHOD(QueryStatus)(const GUID *pguidCmdGroup, ULONG cCmds, OLECMD prgCmds[], OLECMDTEXT *pCmdText); 
        STDMETHOD(Exec)(const GUID* pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANTARG* pvaIn, VARIANTARG* pvaOut); 
    END_INTERFACE_PART(OleCommandTarget) 
1
 

MFC提供了很多宏用于简化COM相关功能的开发,对COM接口的实现方式在MFC中具体体现方式是内嵌类,背后的设计思想是COM聚合,每个接口都产生一个内嵌类,所有的接口都聚合到外层的类。DECLARE_INTERFACE_MAP()  实际上就是定义一个数组以及查询操作,BEGIN_INTERFACE_PART定义一个命名为XOleCommandTarget的内嵌类(内嵌类的命名规则是XName)并定义IUnknown接口的三个方法AddRef、Release、QueryInterface。END_INTERFACE_PART定义一个m_xOleCommandTarget的成员类型为XOleCommandTarget(定义的成员命名规则就是m_xName),并把XOleCommandTarget类声明为外层类的友元类在示例中外层类指CMyControlSite。

STDMETHOD宏定义为virtual __declspec(nothrow) HRESULT __stdcall,该宏定义函数为虚函数返回值为HRESULT,函数调用约定为__stdcall,并且在此函数上禁止异常。__declspec(nothrow)用定告诉编译器它修饰的函数以及此函数调用的函数不会产生C++异常调用从可以优化代码性能和代码尺寸(默认情况下C++编译器为了进行异常处理会在拥有throw调用的函数中自动生成相关的异常处理代码)。通常情况下HRESULT返回值就表达了错误信息,HRESULT是个32位值,不同的位域用于不同的目的,也可以使用自定义的位域,具体的信息可以参考http://en.wikipedia.org/wiki/HRESULT。由于COM本身的语言中立性所以不应该在COM组件对外公布的信息中掺杂特定语言相关的特性。如果需要提供更详尽的错误信息那么应该实现COM的IErrorInfo接口。言归正传以下是CMyControlSite的类实现代码

 BEGIN_INTERFACE_MAP(CMyControlSite, COleControlSite)   INTERFACE_PART(CMyControlSite, IID_IOleCommandTarget, OleCommandTarget)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<em id="__mceDel"><em id="__mceDel">END_INTERFACE_MAP() 
 
 
HRESULT CMyControlSite::XOleCommandTarget::Exec 
(const GUID* pguidCmdGroup, DWORD nCmdID, 
 DWORD nCmdexecopt, VARIANTARG* pvaIn, VARIANTARG* pvaOut ) 
    HRESULT hr = OLECMDERR_E_NOTSUPPORTED; 
    //return S_OK; 
    if (pguidCmdGroup && IsEqualGUID(*pguidCmdGroup, CGID_DocHostCommandHandler)) 
    
 
        switch (nCmdID)  
        
 
        case OLECMDID_SHOWSCRIPTERROR: 
            
                IHTMLDocument2*             pDoc = NULL; 
                IHTMLWindow2*               pWindow = NULL; 
                IHTMLEventObj*              pEventObj = NULL; 
                BSTR                        rgwszNames[5] =  
                {  
                    <span>SysAllocString(L"errorLine"), </span><br><span>SysAllocString(L"errorCharacter"), </span><br><span>SysAllocString(L"errorCode"), </span><br><span>SysAllocString(L"errorMessage"), </span><br><span>SysAllocString(L"errorUrl")</span>
                }; 
                DISPID                      rgDispIDs[5]; 
                VARIANT                     rgvaEventInfo[5]; 
                DISPPARAMS                  params; 
                BOOL                        fContinueRunningScripts = true
 
                params.cArgs = 0; 
                params.cNamedArgs = 0; 
                 
                hr = pvaIn->punkVal->QueryInterface(IID_IHTMLDocument2, (void **) &pDoc);     
                  
                hr = pDoc->get_parentWindow(&pWindow); 
                pDoc->Release(); 
                 
                hr = pWindow->get_event(&pEventObj); 
                 
                for (int i = 0; i < 5; i++)  
                {   
                     
                    hr = pEventObj->GetIDsOfNames(IID_NULL, &rgwszNames[i], 1,  
                        LOCALE_SYSTEM_DEFAULT, &rgDispIDs[i]); 
                 
                    hr = pEventObj->Invoke(rgDispIDs[i], IID_NULL, 
                        LOCALE_SYSTEM_DEFAULT, 
                        DISPATCH_PROPERTYGET, ¶ms, &rgvaEventInfo[i], 
                        NULL, NULL); 
                    //可以在此记录错误信息</em></em>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
                    //必须使用SysFreeString来释放SysAllocString分配的内存,SysAllocString在分配的内存中记录了字符的长度
                    SysFreeString(rgwszNames[i]); 
                
 
                // At this point, you would normally alert the user with  
                // the information about the error, which is now contained 
                // in rgvaEventInfo[]. Or, you could just exit silently. 
 
                (*pvaOut).vt = VT_BOOL; 
                if (fContinueRunningScripts) 
                
                    // 在页面中继续执行脚本
                    (*pvaOut).boolVal = VARIANT_TRUE; 
                
                else
                
                    // 停止在页面中执行脚本 
                    (*pvaOut).boolVal = VARIANT_FALSE;    
                }  
                break
            
        default
            hr =OLECMDERR_E_NOTSUPPORTED;
            break
        
    
    else
    
        hr = OLECMDERR_E_UNKNOWNGROUP;
    
    return (hr); 
 
 
ULONG FAR EXPORT CMyControlSite::XOleCommandTarget::AddRef()  
{  
    METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)  
        return pThis->ExternalAddRef();  
}  
 
 
ULONG FAR EXPORT CMyControlSite::XOleCommandTarget::Release()  
{  
    METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)  
        return pThis->ExternalRelease();  
}  
 
HRESULT FAR EXPORT CMyControlSite::XOleCommandTarget::QueryInterface(REFIID riid, void **ppvObj)  
{  
    METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)  
        HRESULT hr = (HRESULT)pThis->ExternalQueryInterface(&riid, ppvObj);  
    return hr;  
 
STDMETHODIMP CMyControlSite::XOleCommandTarget::QueryStatus(  
    /* [unique][in] */ const GUID __RPC_FAR *pguidCmdGroup,  
    /* [in] */ ULONG cCmds,  
    /* [out][in][size_is] */ OLECMD __RPC_FAR prgCmds[ ],  
    /* [unique][out][in] */ OLECMDTEXT __RPC_FAR *pCmdText  
    )  
{  
    METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)  
        return OLECMDERR_E_NOTSUPPORTED;  
}  

实现CMyControlSite后需要应用到CDHtmlDialog上,重写CreateControlSite虚函数既可

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
virtual BOOL CreateControlSite(COleControlContainer* pContainer,
        COleControlSite** ppSite, UINT  nID , REFCLSID  clsid )
{
        if(ppSite == NULL)
    {
        ASSERT(FALSE);
        return FALSE;
    }
 
    CMyControlSite *pBrowserSite =
        new CMyControlSite (pContainer, this);
    if (!pBrowserSite)
        return FALSE;
 
    *ppSite = pBrowserSite;
    return TRUE;
}

现在就可以去编译测试了。到目前还有一个问题没有考虑,如果这段代码整合到现有的CDHtmlDialog应用中而现有的代码使用了其它默认的设定比如说自定义WebBrowser的右健菜单或使用了GetIDispatch函数等情况下原有的代码就不能正常工作了。这部分功能是在MFC的CBrowserControlSite类中处理的,所以CMyControlSite应该把CBrowserControlSite的功能也实现一遍以使CDHtmlDialog的原有封装性不被破坏。在CMyControlSite中实现IDocHostUIHandler接口既可完成此功能。代码声明如下

1
public:
1
CMyControlSite(COleControlContainer *pCnt, CDHtmlDialog *pHandler):COleControlSite(pCnt),m_pHandler(pHandler) {}
1
protected:
1
CDHtmlDialog *m_pHandler;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
BEGIN_INTERFACE_PART(DocHostUIHandler, IDocHostUIHandler)
        STDMETHOD(ShowContextMenu)(DWORD, LPPOINT, LPUNKNOWN, LPDISPATCH);
        STDMETHOD(GetHostInfo)(DOCHOSTUIINFO*);
        STDMETHOD(ShowUI)(DWORD, LPOLEINPLACEACTIVEOBJECT,
            LPOLECOMMANDTARGET, LPOLEINPLACEFRAME, LPOLEINPLACEUIWINDOW);
        STDMETHOD(HideUI)(void);
        STDMETHOD(UpdateUI)(void);
        STDMETHOD(EnableModeless)(BOOL);
        STDMETHOD(OnDocWindowActivate)(BOOL);
        STDMETHOD(OnFrameWindowActivate)(BOOL);
        STDMETHOD(ResizeBorder)(LPCRECT, LPOLEINPLACEUIWINDOW, BOOL);
        STDMETHOD(TranslateAccelerator)(LPMSG, const GUID*, DWORD);
        STDMETHOD(GetOptionKeyPath)(OLECHAR **, DWORD);
        STDMETHOD(GetDropTarget)(LPDROPTARGET, LPDROPTARGET*);
        STDMETHOD(GetExternal)(LPDISPATCH*);
        STDMETHOD(TranslateUrl)(DWORD, OLECHAR*, OLECHAR **);
        STDMETHOD(FilterDataObject)(LPDATAOBJECT , LPDATAOBJECT*);
    END_INTERFACE_PART(DocHostUIHandler)

以下是实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
BEGIN_INTERFACE_MAP(CMyControlSite, COleControlSite) 
    INTERFACE_PART(CMyControlSite, IID_IDocHostUIHandler, DocHostUIHandler)
    INTERFACE_PART(CMyControlSite, IID_IOleCommandTarget, OleCommandTarget)
END_INTERFACE_MAP() 
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetExternal(LPDISPATCH *lppDispatch)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->GetExternal(lppDispatch);
}
 
 
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::ShowContextMenu(
    DWORD dwID, LPPOINT ppt, LPUNKNOWN pcmdTarget, LPDISPATCH pdispReserved)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->ShowContextMenu(dwID, ppt, pcmdTarget, pdispReserved);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetHostInfo(
    DOCHOSTUIINFO *pInfo)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->GetHostInfo(pInfo);
}
 
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::ShowUI(
    DWORD dwID, LPOLEINPLACEACTIVEOBJECT pActiveObject,
    LPOLECOMMANDTARGET pCommandTarget, LPOLEINPLACEFRAME pFrame,
    LPOLEINPLACEUIWINDOW pDoc)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->ShowUI(dwID, pActiveObject, pCommandTarget, pFrame, pDoc);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::HideUI(void)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->HideUI();
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::UpdateUI(void)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->UpdateUI();
}
 
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::EnableModeless(BOOL fEnable)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->EnableModeless(fEnable);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::OnDocWindowActivate(BOOL fActivate)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->OnDocWindowActivate(fActivate);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::OnFrameWindowActivate(
    BOOL fActivate)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->OnFrameWindowActivate(fActivate);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::ResizeBorder(
    LPCRECT prcBorder, LPOLEINPLACEUIWINDOW pUIWindow, BOOL fFrameWindow)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->ResizeBorder(prcBorder, pUIWindow, fFrameWindow);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::TranslateAccelerator(
    LPMSG lpMsg, const GUID* pguidCmdGroup, DWORD nCmdID)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->TranslateAccelerator(lpMsg, pguidCmdGroup, nCmdID);
}
 
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetOptionKeyPath(
    LPOLESTR* pchKey, DWORD dwReserved)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->GetOptionKeyPath(pchKey, dwReserved);
}
 
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetDropTarget(
    LPDROPTARGET pDropTarget, LPDROPTARGET* ppDropTarget)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->GetDropTarget(pDropTarget, ppDropTarget);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::TranslateUrl(
    DWORD dwTranslate, OLECHAR* pchURLIn, OLECHAR** ppchURLOut)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->TranslateUrl(dwTranslate, pchURLIn, ppchURLOut);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::FilterDataObject(
    LPDATAOBJECT pDataObject, LPDATAOBJECT* ppDataObject)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->FilterDataObject(pDataObject, ppDataObject);
}
 
 
STDMETHODIMP_(ULONG) CMyControlSite::XDocHostUIHandler::AddRef()
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
        return pThis->ExternalAddRef();
}
 
STDMETHODIMP_(ULONG) CMyControlSite::XDocHostUIHandler::Release()
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
        return pThis->ExternalRelease();
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::QueryInterface(
    REFIID iid, LPVOID far* ppvObj)    
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
        return pThis->ExternalQueryInterface(&iid, ppvObj);
}

STDMETHODIMP宏的定义是HRESULT __stdcall,STDMETHODIMP_宏指定了返回值,这两个宏用在cpp实现文件中,对应用于声明时使用的STDMETHOD和STDMETHOD_。

METHOD_PROLOGUE_EX_宏定义了pThis指针来指向外层类。

以上代码基于VS2008,由于不同版本的VS所带的MFC库版本不尽一致所以需要根本具体的版本来处理,目前已知的不同部分主要集中在CreateControlSite上。

1、屏蔽错误信息提示

1
m_pBrowserApp->put_Silent(VARIANT_TRUE);

在CDHtmlDialog::OnInitDialog()的代码中首先了创建WebBrowser控件,然后把控件的Browser对象赋值给m_pBrowserApp(这是CDHtmlDialog完成的不需要自己处理)。WebBrowser的put_Silent函数在官方给出的说明是禁用所有的对话框,但例外情况是它不会影响SSL安全认证需要的进示对话框。绝大多数情况下这就可以解决问题了,记得很久以前我遇到过一种情况就是虽然调用了put_Silent但是还是有极个别的js错误是无法屏蔽掉的依然会显示出来(在网页含有嵌套页面时会错误无法屏蔽,不知道是否还有其它情况),现在找不到这样的网页了,如果谁遇到这种情况了建议给我发上个URL让我也重温一下当年阳光灿烂的时刻。

2、控制错误提示并进行记录

  这要比第一种方法复杂上许多,简短的来说就是自定义COleControlSite类并实现IOleCommandTarget接口,IOleCommandTarget接口是错误控制的关健,错误发生时会触发此接口的Exec函数并为nCmdID参数赋值为OLECMDID_SHOWSCRIPTERROR,这样就可以得到错误信息了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
IOleCommandTarget : public IUnknown
    {
    public:
        virtual /* [input_sync] */ HRESULT STDMETHODCALLTYPE QueryStatus(
            /* [unique][in] */ __RPC__in_opt const GUID *pguidCmdGroup,
            /* [in] */ ULONG cCmds,
            /* [out][in][size_is] */ __RPC__inout_ecount_full(cCmds) OLECMD prgCmds[  ],
            /* [unique][out][in] */ __RPC__inout_opt OLECMDTEXT *pCmdText) = 0;
         
        virtual HRESULT STDMETHODCALLTYPE Exec(
            /* [unique][in] */ __RPC__in_opt const GUID *pguidCmdGroup,
            /* [in] */ DWORD nCmdID,
            /* [in] */ DWORD nCmdexecopt,
            /* [unique][in] */ __RPC__in_opt VARIANT *pvaIn,
            /* [unique][out][in] */ __RPC__inout_opt VARIANT *pvaOut) = 0;
         
    };
    

现在我们开始实现自定义的COleControlSite

1
2
3
4
5
6
7
8
9
10
11
12
13
class CMyControlSite : public COleControlSite
 
public
    CMyControlSite(COleControlContainer *pCntr):COleControlSite(pCntr) {}
 
protected
 
    DECLARE_INTERFACE_MAP() 
    BEGIN_INTERFACE_PART(OleCommandTarget, IOleCommandTarget) 
        STDMETHOD(QueryStatus)(const GUID *pguidCmdGroup, ULONG cCmds, OLECMD prgCmds[], OLECMDTEXT *pCmdText); 
        STDMETHOD(Exec)(const GUID* pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANTARG* pvaIn, VARIANTARG* pvaOut); 
    END_INTERFACE_PART(OleCommandTarget) 
1
 

MFC提供了很多宏用于简化COM相关功能的开发,对COM接口的实现方式在MFC中具体体现方式是内嵌类,背后的设计思想是COM聚合,每个接口都产生一个内嵌类,所有的接口都聚合到外层的类。DECLARE_INTERFACE_MAP()  实际上就是定义一个数组以及查询操作,BEGIN_INTERFACE_PART定义一个命名为XOleCommandTarget的内嵌类(内嵌类的命名规则是XName)并定义IUnknown接口的三个方法AddRef、Release、QueryInterface。END_INTERFACE_PART定义一个m_xOleCommandTarget的成员类型为XOleCommandTarget(定义的成员命名规则就是m_xName),并把XOleCommandTarget类声明为外层类的友元类在示例中外层类指CMyControlSite。

STDMETHOD宏定义为virtual __declspec(nothrow) HRESULT __stdcall,该宏定义函数为虚函数返回值为HRESULT,函数调用约定为__stdcall,并且在此函数上禁止异常。__declspec(nothrow)用定告诉编译器它修饰的函数以及此函数调用的函数不会产生C++异常调用从可以优化代码性能和代码尺寸(默认情况下C++编译器为了进行异常处理会在拥有throw调用的函数中自动生成相关的异常处理代码)。通常情况下HRESULT返回值就表达了错误信息,HRESULT是个32位值,不同的位域用于不同的目的,也可以使用自定义的位域,具体的信息可以参考http://en.wikipedia.org/wiki/HRESULT。由于COM本身的语言中立性所以不应该在COM组件对外公布的信息中掺杂特定语言相关的特性。如果需要提供更详尽的错误信息那么应该实现COM的IErrorInfo接口。言归正传以下是CMyControlSite的类实现代码

 BEGIN_INTERFACE_MAP(CMyControlSite, COleControlSite)   INTERFACE_PART(CMyControlSite, IID_IOleCommandTarget, OleCommandTarget)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<em id="__mceDel"><em id="__mceDel">END_INTERFACE_MAP() 
 
 
HRESULT CMyControlSite::XOleCommandTarget::Exec 
(const GUID* pguidCmdGroup, DWORD nCmdID, 
 DWORD nCmdexecopt, VARIANTARG* pvaIn, VARIANTARG* pvaOut ) 
    HRESULT hr = OLECMDERR_E_NOTSUPPORTED; 
    //return S_OK; 
    if (pguidCmdGroup && IsEqualGUID(*pguidCmdGroup, CGID_DocHostCommandHandler)) 
    
 
        switch (nCmdID)  
        
 
        case OLECMDID_SHOWSCRIPTERROR: 
            
                IHTMLDocument2*             pDoc = NULL; 
                IHTMLWindow2*               pWindow = NULL; 
                IHTMLEventObj*              pEventObj = NULL; 
                BSTR                        rgwszNames[5] =  
                {  
                    <span>SysAllocString(L"errorLine"), </span><br><span>SysAllocString(L"errorCharacter"), </span><br><span>SysAllocString(L"errorCode"), </span><br><span>SysAllocString(L"errorMessage"), </span><br><span>SysAllocString(L"errorUrl")</span>
                }; 
                DISPID                      rgDispIDs[5]; 
                VARIANT                     rgvaEventInfo[5]; 
                DISPPARAMS                  params; 
                BOOL                        fContinueRunningScripts = true
 
                params.cArgs = 0; 
                params.cNamedArgs = 0; 
                 
                hr = pvaIn->punkVal->QueryInterface(IID_IHTMLDocument2, (void **) &pDoc);     
                  
                hr = pDoc->get_parentWindow(&pWindow); 
                pDoc->Release(); 
                 
                hr = pWindow->get_event(&pEventObj); 
                 
                for (int i = 0; i < 5; i++)  
                {   
                     
                    hr = pEventObj->GetIDsOfNames(IID_NULL, &rgwszNames[i], 1,  
                        LOCALE_SYSTEM_DEFAULT, &rgDispIDs[i]); 
                 
                    hr = pEventObj->Invoke(rgDispIDs[i], IID_NULL, 
                        LOCALE_SYSTEM_DEFAULT, 
                        DISPATCH_PROPERTYGET, ¶ms, &rgvaEventInfo[i], 
                        NULL, NULL); 
                    //可以在此记录错误信息</em></em>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
                    //必须使用SysFreeString来释放SysAllocString分配的内存,SysAllocString在分配的内存中记录了字符的长度
                    SysFreeString(rgwszNames[i]); 
                
 
                // At this point, you would normally alert the user with  
                // the information about the error, which is now contained 
                // in rgvaEventInfo[]. Or, you could just exit silently. 
 
                (*pvaOut).vt = VT_BOOL; 
                if (fContinueRunningScripts) 
                
                    // 在页面中继续执行脚本
                    (*pvaOut).boolVal = VARIANT_TRUE; 
                
                else
                
                    // 停止在页面中执行脚本 
                    (*pvaOut).boolVal = VARIANT_FALSE;    
                }  
                break
            
        default
            hr =OLECMDERR_E_NOTSUPPORTED;
            break
        
    
    else
    
        hr = OLECMDERR_E_UNKNOWNGROUP;
    
    return (hr); 
 
 
ULONG FAR EXPORT CMyControlSite::XOleCommandTarget::AddRef()  
{  
    METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)  
        return pThis->ExternalAddRef();  
}  
 
 
ULONG FAR EXPORT CMyControlSite::XOleCommandTarget::Release()  
{  
    METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)  
        return pThis->ExternalRelease();  
}  
 
HRESULT FAR EXPORT CMyControlSite::XOleCommandTarget::QueryInterface(REFIID riid, void **ppvObj)  
{  
    METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)  
        HRESULT hr = (HRESULT)pThis->ExternalQueryInterface(&riid, ppvObj);  
    return hr;  
 
STDMETHODIMP CMyControlSite::XOleCommandTarget::QueryStatus(  
    /* [unique][in] */ const GUID __RPC_FAR *pguidCmdGroup,  
    /* [in] */ ULONG cCmds,  
    /* [out][in][size_is] */ OLECMD __RPC_FAR prgCmds[ ],  
    /* [unique][out][in] */ OLECMDTEXT __RPC_FAR *pCmdText  
    )  
{  
    METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)  
        return OLECMDERR_E_NOTSUPPORTED;  
}  

实现CMyControlSite后需要应用到CDHtmlDialog上,重写CreateControlSite虚函数既可

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
virtual BOOL CreateControlSite(COleControlContainer* pContainer,
        COleControlSite** ppSite, UINT  nID , REFCLSID  clsid )
{
        if(ppSite == NULL)
    {
        ASSERT(FALSE);
        return FALSE;
    }
 
    CMyControlSite *pBrowserSite =
        new CMyControlSite (pContainer, this);
    if (!pBrowserSite)
        return FALSE;
 
    *ppSite = pBrowserSite;
    return TRUE;
}

现在就可以去编译测试了。到目前还有一个问题没有考虑,如果这段代码整合到现有的CDHtmlDialog应用中而现有的代码使用了其它默认的设定比如说自定义WebBrowser的右健菜单或使用了GetIDispatch函数等情况下原有的代码就不能正常工作了。这部分功能是在MFC的CBrowserControlSite类中处理的,所以CMyControlSite应该把CBrowserControlSite的功能也实现一遍以使CDHtmlDialog的原有封装性不被破坏。在CMyControlSite中实现IDocHostUIHandler接口既可完成此功能。代码声明如下

1
public:
1
CMyControlSite(COleControlContainer *pCnt, CDHtmlDialog *pHandler):COleControlSite(pCnt),m_pHandler(pHandler) {}
1
protected:
1
CDHtmlDialog *m_pHandler;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
BEGIN_INTERFACE_PART(DocHostUIHandler, IDocHostUIHandler)
        STDMETHOD(ShowContextMenu)(DWORD, LPPOINT, LPUNKNOWN, LPDISPATCH);
        STDMETHOD(GetHostInfo)(DOCHOSTUIINFO*);
        STDMETHOD(ShowUI)(DWORD, LPOLEINPLACEACTIVEOBJECT,
            LPOLECOMMANDTARGET, LPOLEINPLACEFRAME, LPOLEINPLACEUIWINDOW);
        STDMETHOD(HideUI)(void);
        STDMETHOD(UpdateUI)(void);
        STDMETHOD(EnableModeless)(BOOL);
        STDMETHOD(OnDocWindowActivate)(BOOL);
        STDMETHOD(OnFrameWindowActivate)(BOOL);
        STDMETHOD(ResizeBorder)(LPCRECT, LPOLEINPLACEUIWINDOW, BOOL);
        STDMETHOD(TranslateAccelerator)(LPMSG, const GUID*, DWORD);
        STDMETHOD(GetOptionKeyPath)(OLECHAR **, DWORD);
        STDMETHOD(GetDropTarget)(LPDROPTARGET, LPDROPTARGET*);
        STDMETHOD(GetExternal)(LPDISPATCH*);
        STDMETHOD(TranslateUrl)(DWORD, OLECHAR*, OLECHAR **);
        STDMETHOD(FilterDataObject)(LPDATAOBJECT , LPDATAOBJECT*);
    END_INTERFACE_PART(DocHostUIHandler)

以下是实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
BEGIN_INTERFACE_MAP(CMyControlSite, COleControlSite) 
    INTERFACE_PART(CMyControlSite, IID_IDocHostUIHandler, DocHostUIHandler)
    INTERFACE_PART(CMyControlSite, IID_IOleCommandTarget, OleCommandTarget)
END_INTERFACE_MAP() 
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetExternal(LPDISPATCH *lppDispatch)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->GetExternal(lppDispatch);
}
 
 
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::ShowContextMenu(
    DWORD dwID, LPPOINT ppt, LPUNKNOWN pcmdTarget, LPDISPATCH pdispReserved)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->ShowContextMenu(dwID, ppt, pcmdTarget, pdispReserved);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetHostInfo(
    DOCHOSTUIINFO *pInfo)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->GetHostInfo(pInfo);
}
 
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::ShowUI(
    DWORD dwID, LPOLEINPLACEACTIVEOBJECT pActiveObject,
    LPOLECOMMANDTARGET pCommandTarget, LPOLEINPLACEFRAME pFrame,
    LPOLEINPLACEUIWINDOW pDoc)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->ShowUI(dwID, pActiveObject, pCommandTarget, pFrame, pDoc);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::HideUI(void)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->HideUI();
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::UpdateUI(void)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->UpdateUI();
}
 
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::EnableModeless(BOOL fEnable)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->EnableModeless(fEnable);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::OnDocWindowActivate(BOOL fActivate)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->OnDocWindowActivate(fActivate);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::OnFrameWindowActivate(
    BOOL fActivate)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->OnFrameWindowActivate(fActivate);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::ResizeBorder(
    LPCRECT prcBorder, LPOLEINPLACEUIWINDOW pUIWindow, BOOL fFrameWindow)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->ResizeBorder(prcBorder, pUIWindow, fFrameWindow);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::TranslateAccelerator(
    LPMSG lpMsg, const GUID* pguidCmdGroup, DWORD nCmdID)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->TranslateAccelerator(lpMsg, pguidCmdGroup, nCmdID);
}
 
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetOptionKeyPath(
    LPOLESTR* pchKey, DWORD dwReserved)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->GetOptionKeyPath(pchKey, dwReserved);
}
 
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetDropTarget(
    LPDROPTARGET pDropTarget, LPDROPTARGET* ppDropTarget)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->GetDropTarget(pDropTarget, ppDropTarget);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::TranslateUrl(
    DWORD dwTranslate, OLECHAR* pchURLIn, OLECHAR** ppchURLOut)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->TranslateUrl(dwTranslate, pchURLIn, ppchURLOut);
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::FilterDataObject(
    LPDATAOBJECT pDataObject, LPDATAOBJECT* ppDataObject)
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    return pThis->m_pHandler->FilterDataObject(pDataObject, ppDataObject);
}
 
 
STDMETHODIMP_(ULONG) CMyControlSite::XDocHostUIHandler::AddRef()
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
        return pThis->ExternalAddRef();
}
 
STDMETHODIMP_(ULONG) CMyControlSite::XDocHostUIHandler::Release()
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
        return pThis->ExternalRelease();
}
 
STDMETHODIMP CMyControlSite::XDocHostUIHandler::QueryInterface(
    REFIID iid, LPVOID far* ppvObj)    
{
    METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
        return pThis->ExternalQueryInterface(&iid, ppvObj);
}

STDMETHODIMP宏的定义是HRESULT __stdcall,STDMETHODIMP_宏指定了返回值,这两个宏用在cpp实现文件中,对应用于声明时使用的STDMETHOD和STDMETHOD_。

METHOD_PROLOGUE_EX_宏定义了pThis指针来指向外层类。

以上代码基于VS2008,由于不同版本的VS所带的MFC库版本不尽一致所以需要根本具体的版本来处理,目前已知的不同部分主要集中在CreateControlSite上。