保护您的组件免受自动化客户端的侵害






4.15/5 (3投票s)
2004年4月15日
3分钟阅读

41890

784
保护您的组件免受自动化客户端的侵害
引言
有时我们希望保护我们的控件不被自动化客户端访问。
背景
有些人会在 microsoft.public.vc.atl NG 上提问
"我有一个 Active X 控件,它使用 dispinterfaces 作为源接口来支持基于自动化的客户端。 如果我使用 VC++ 客户端,并且使用 Sink 实现,使用
IDispEventImpl
或IDispEventSimpleImpl
,我的应用程序可以收到事件并且运行良好。""我想知道这是否是我们可以使用的唯一方式,或者是否有其他方法可以使用控件来获取事件。我尝试的另一种方法是我的类派生自
IDispathImpl<_Iabcevents>
,并且我在该对象上调用ATLAdvise
/ATLUnadvise
。我可以看到Advise
成功了,但我没有收到任何事件。这是因为 dispInterface 不会为事件生成 VTBL 并且基于 dispids 工作吗?还是我遗漏了什么?这也证明了 C++ 客户端无法使用此控件,除非修改该控件以使用自定义/双接口作为传出接口。"
所以我看了一下,并制作了一个示例来测试我的观点。
如何使您的控件与自动化不兼容?
以下步骤可以解决问题。
步骤 1
您可以使用 ATL 控件向导生成控件,请记住勾选“支持连接点”选项,因为我们将在 vc 客户端中触发控件事件。 并且还要记住选择自定义接口,让自动化兼容留空。 如您所知,这就是我想证明的。
#include "olectl.h" import "oaidl.idl"; import "ocidl.idl"; [ object, uuid(C86E745D-D971-41E1-AA85-E63C8B26EE48), helpstring("Icontrol2 Interface"), pointer_default(unique) ] interface Icontrol2 : IUnknown{ [propget, helpstring("property text")] HRESULT text([out, retval] BSTR* pVal); [propput, helpstring("property text")] HRESULT text([in] BSTR newVal); }; [ uuid(97338ED1-60EC-40DF-9CE5-24F2F2B317AC), helpstring("_Icontrol2Events Interface") ] interface _Icontrol2Events : IUnknown { [id(1), helpstring("method onclick")] HRESULT onclick(void); }; [ uuid(232E5CB8-8D52-4A3A-BF01-726E8C213C75), version(1.0), helpstring("testvtable 1.0 Type Library") ] library testvtableLib { importlib("stdole2.tlb"); [ uuid(CDC37F32-27EE-402F-B0A1-57BF7DE54F27), helpstring("control2 Class") ] // _Icontrol2Events has been moved out. coclass control2 { [default] interface Icontrol2; [default, source] interface _Icontrol2Events; }; };
请注意,在这里,dispinterface 属性已更改为派生自 IUnknown
的普通接口。 因此,客户端只能使用 vtable 访问我们的控件。 因此,应该进行一些更改以适应这种情况。
template<class T> class CProxy_Icontrol2Events : public IConnectionPointImpl<T, &__uuidof(_Icontrol2Events)> { public: HRESULT Fire_onclick() { HRESULT hr = S_OK; T * pThis = static_cast<T *>(this); int cConnections = m_vec.GetSize(); for (int iConnection = 0; iConnection < cConnections; iConnection++) { pThis->Lock(); CComPtr<IUnknown> punkConnection = m_vec.GetAt(iConnection); pThis->Unlock(); // no dispatch in this control, so use vtable here, // remember punkConnection is just _Icontrol2Events //IDispatch * pConnection = static_cast<IDispatch *>(punkConnection.p); CComQIPtr<_Icontrol2Events> pEvents(punkConnection); hr = pEvents->onclick(); //if (pConnection) //{ // CComVariant varResult; // DISPPARAMS params = { NULL, NULL, 0, 0 }; // hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, &varResult, NULL, NULL); //} } return hr; } };
另一件事是在 onDraw
中,textout
应该像这样,将 "text" 声明为 CComBSTR
。
TextOut(di.hdcDraw, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2, pszText, text);从上面的 idl,您可以看到我实现了一个名为 "
onclick
" 的事件。 我在 WM_LBUTTONDOWN
中触发了它。 为了响应客户端点击事件,我实现了一个名为“text”的属性,这样当在客户端点击我们的控件时,控件将更改其外观,以绘制客户端分配的新文本。第二步
创建一个 WTL 对话框客户端来测试控件。 这也是一个向导生成的项目。 右键单击 dlg 模板以插入上述控件,并以常规方式添加事件处理程序。
注意: 从那里,您应该将 maindlg 设为一个对象。 并且它看起来像这样。
#import "..\testvtable\debug\testvtable.dll" no_namespace,raw_interfaces_only class CMainDlg : public CComObjectRootEx<CComSingleThreadModel>, public CAxDialogImpl<CMainDlg>, public CUpdateUI<CMainDlg>, public CMessageFilter, public CIdleHandler, public _Icontrol2Events //public IDispEventImpl*/ { public: CComPtr<Icontrol2> spUnk; DWORD dwSink; enum { IDD = IDD_MAINDLG }; ... BEGIN_COM_MAP(CMainDlg) COM_INTERFACE_ENTRY(_Icontrol2Events) END_COM_MAP() ... LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { ... UIAddChildWindowContainer(m_hWnd); HWND h = GetDlgItem(IDC_CONTROL21); HRESULT hr = AtlAxGetControl(h, reinterpret_cast(&spUnk)); hr = AtlAdvise(spUnk,this,__uuidof(_Icontrol2Events),&dwSink); ///AdviseSinkMap(true); return TRUE; } ... void CloseDialog(int nVal) { ///AdviseSinkMap(false); AtlUnadvise(spUnk,__uuidof(_Icontrol2Events),dwSink); DestroyWindow(); ::PostQuitMessage(nVal); } //BEGIN_SINK_MAP(CMainDlg) // SINK_ENTRY(IDC_CONTROL21, 1, onclickControl21) //END_SINK_MAP() //HRESULT __stdcall onclickControl21() //{ // // TODO: Add your message handler code here // return 0; //} HRESULT __stdcall onclick ( ) { spUnk->put_text(L"clicked"); return S_OK; } };
关注点
如您所知,如果您将 maindlg 设为一个对象,那么您应该执行以下操作,而不是通常您执行的方式。
int Run(LPTSTR /*lpstrCmdLine*/ = NULL, int nCmdShow = SW_SHOWDEFAULT) { CMessageLoop theLoop; _Module.AddMessageLoop(&theLoop); CComObject<CMainDlg>* dlgMain = NULL; int nRet = 0; if(SUCCEEDED(CComObject<CMainDlg>::CreateInstance(&dlgMain))) { dlgMain->AddRef(); if(dlgMain->Create(NULL) == NULL) { ATLTRACE(_T("Main dialog creation failed!\n")); return 0; } dlgMain->ShowWindow(nCmdShow); nRet = theLoop.Run(); } dlgMain->Release(); _Module.RemoveMessageLoop(); return nRet; }
就是这样!
另一种通过调度来连接控件事件的方式
完成示例后,OP 也询问了使用 IDispatch
访问控件事件的问题。 所以我添加了一个名为 control3 的新控件,并且我使用 IDispatchImpl
来声明我的客户端对它的事件感兴趣。 因此,我在该 com 映射中为 _Icontrol3Events
添加了一个条目。 现在 maindlg 已更改为以下内容
GUID LIBID_testvtableLib = {0x232E5CB8,0x8D52,0x4A3A, {0xBF,0x01,0x72,0x6E,0x8C,0x21,0x3C,0x75}}; class CMainDlg : public CComObjectRootEx<CComSingleThreadModel>, public CAxDialogImpl<CMainDlg>, public CUpdateUI<CMainDlg>, public CMessageFilter, public CIdleHandler, public _Icontrol2Events, public IDispatchImpl<_Icontrol3Events, &__uuidof(_Icontrol3Events), &LIBID_testvtableLib, 1,0> { public: enum { IDD = IDD_MAINDLG }; CComPtr<Icontrol2> spUnk; DWORD dwSink; CComPtr<Icontrol3> spUnk3; DWORD dwSink3; ... BEGIN_UPDATE_UI_MAP(CMainDlg) END_UPDATE_UI_MAP() BEGIN_COM_MAP(CMainDlg) COM_INTERFACE_ENTRY(_Icontrol2Events) COM_INTERFACE_ENTRY(_Icontrol3Events) END_COM_MAP() ... LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { ... HWND h = GetDlgItem(IDC_CONTROL21); HRESULT hr = AtlAxGetControl(h, reinterpret_cast<IUnknown**>(&spUnk)); hr = AtlAdvise(spUnk,GetUnknown(),__uuidof(_Icontrol2Events),&dwSink); HWND h3 = GetDlgItem(IDC_CONTROL31); hr = AtlAxGetControl(h3, reinterpret_cast<IUnknown**>(&spUnk3)); hr = AtlAdvise(spUnk3,GetUnknown(),__uuidof(_Icontrol3Events),&dwSink3); ///AdviseSinkMap(true); return TRUE; } void CloseDialog(int nVal) { ///AdviseSinkMap(false); AtlUnadvise(spUnk,__uuidof(_Icontrol2Events),dwSink); AtlUnadvise(spUnk3,__uuidof(_Icontrol3Events),dwSink3); DestroyWindow(); ::PostQuitMessage(nVal); } //BEGIN_SINK_MAP(CMainDlg) // SINK_ENTRY(IDC_CONTROL21, 1, onclickControl21) //END_SINK_MAP() //HRESULT __stdcall onclickControl21() //{ // // TODO: Add your message handler code here // return 0; //} HRESULT __stdcall onclick ( ) { spUnk->put_text(L"clicked"); return S_OK; } //// control3 STDMETHOD(Invoke)(DISPID dispidMember, REFIID /*riid*/,LCID lcid, WORD /*wFlags*/, DISPPARAMS* pdispparams, VARIANT* pvarResult,EXCEPINFO* /*pexcepinfo*/, UINT* /*puArgErr*/) { spUnk3->put_text(L"dual clicked"); return S_OK; } };
历史
因此,从我的观点来看,VC++ 客户端访问组件要灵活得多。