Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Protect your component from automation clients

0.00/5 (No votes)
14 Apr 2004 3  
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 or IDispEventSimpleImpl, 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 calling ATLAdvise / ATLUnadvise on the object. I can see Advise 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here