Protect your component from automation clients






4.15/5 (3 votes)
Apr 15, 2004
3 min read

41330

747
Protect your component from automation clients
Introduction
Sometimes we want to protect our control being accessed from automation clients.
Background
Some people ask on the microsoft.public.vc.atl NG:
"I have an Active X control that uses dispinterfaces for Source interfaces to support automation based clients. As a VC++ client if I use Sink implementation using either
IDispEventImpl
orIDispEventSimpleImpl
, my application is getting the events and working fine. ""I am wondering these only ways we can use or is there any way I can use the control to get the events. The other way I am trying as my class is deriving from
IDispathImpl<_Iabcevents>
and I am callingATLAdvise
/ATLUnadvise
on the object. I can seeAdvise
is success but I am not receiving any events. Is this because of dispInterface will not generate VTBL for events and works based on dispids or I am missing anything. Is this also proves that C++ clients can not use this until the control is modified to use the custom / Dual interfaces as outgoing interfaces."
So I had a look at it, and I made a sample to test my point.
How to make your control automation-incompatible ?
The following steps do the trick.
Step 1
You use ATL control wizard to generate control, remember to tick "supporting connection point" option, because we will fire control events in vc client. And also remember to choose custom Interface, let the automation compatible blank. As you know, this is what I want to prove.
#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; }; };
Note that here, the dispinterface attribute has been changed to a normal interface
which derived from IUnknown
. So client can only access our control using vtable. So several changes should be made to accommodate this.
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; } };
Another thing is in the onDraw
, textout
should look like this, the "text"
declared as CComBSTR
.
TextOut(di.hdcDraw, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2, pszText, text);From the above idl, you can see that I implemented an event called "
onclick
". I fired it in WM_LBUTTONDOWN
. In order to respond the client-click
event, I implement a property called "text", so that when click on our
control in the client side, the control will change it appearance, to draw
the new text assigned by client.
Step 2
Create a WTL dialog client to test the control. it's also a wizard generated project. Right click on the dlg template to insert the above control, add event hander in the normal way.
Note: from there, you should make maindlg to be an object. and it looks like the following.
#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; } };
Points of Interest
As you know, if you make maindlg to be an object, then you should do the following, not normally the way you do.
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; }
That's all!
Another way to hook up control events by way of dispatch
After finishing the sample, the OP also asked about using IDispatch
to access
control events. so I added a new control called control3, and I used
IDispatchImpl
to declare my client side have interest about its events.
So
I add an entry for _Icontrol3Events
in that com map. Now the maindlg has been
changed into the following:
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; } };
History
So from my view-point, VC++ client is a lot more flexible to access components.