65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.15/5 (3投票s)

2004年4月15日

3分钟阅读

viewsIcon

41890

downloadIcon

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++ 客户端访问组件要灵活得多。

© . All rights reserved.