ATL 控件包含常见问题

哪些 ATL 类促进 ActiveX 控件包含?

ATL 的控件托管代码不需要你使用任何 ATL 类;你只需创建一个“AtlAxWin80”窗口并在必要时使用控件托管 API(有关更多信息,请参阅什么是 ATL 控件托管 API 但是,以下类使包含功能更易于使用。

说明
CAxWindow 包装“AtlAxWin80”窗口,这提供了创建窗口、创建控件和/或将控件附加到窗口以及检索主机对象上的接口指针的方法。
CAxWindow2T 包装“AtlAxWinLic80”窗口,这提供用于创建窗口、创建控件和/或将许可控件附加到窗口以及检索托管对象上的接口指针的方法。
CComCompositeControl 充当基于对话框资源的 ActiveX 控件类的基类。 此类控件可以包含其他 ActiveX 控件。
CAxDialogImpl 充当基于对话框资源的对话框类的基类。 这样的对话框可以包含 ActiveX 控件。
CWindow 提供一个方法 GetDlgControl,在给定控件的窗口 ID 的情况下,该方法可返回控件上的接口指针。 此外,CWindow 公开的 Windows API 包装器通常会让窗口管理更轻松。

什么是 ATL 控件承载 API?

ATL 的控件托管 API 是一组函数,它允许任何窗口充当 ActiveX 控件容器。 这些函数可以静态或动态链接到你的项目中,因为它们可以作为源代码使用并由 ATL90.dll 公开。 下表列出了控件托管功能。

函数 说明
AtlAxAttachControl 创建一个主机对象,将其连接到提供的窗口,然后附加一个现有控件。
AtlAxCreateControl 创建一个主机对象,将其连接到提供的窗口,然后加载一个控件。
AtlAxCreateControlLic 创建许可 ActiveX 控件,初始化它,并将其托管在指定窗口中,类似于 AtlAxCreateControl
AtlAxCreateControlEx 创建一个宿主对象,将其连接到提供的窗口,然后加载一个控件(也允许设置事件接收器)。
AtlAxCreateControlLicEx 创建许可 ActiveX 控件,初始化它,并将其托管在指定窗口中,类似于 AtlAxCreateControlLic
AtlAxCreateDialog 从对话框资源创建无模式对话框并返回窗口句柄。
AtlAxDialogBox 从对话框资源创建模态对话框。
AtlAxGetControl 返回窗口中托管的控件的 IUnknown 接口指针。
AtlAxGetHost 返回连接到窗口的主机对象的 IUnknown 接口指针。
AtlAxWinInit 初始化控件托管代码。
AtlAxWinTerm 取消初始化控件托管代码。

前三个函数中的 HWND 参数必须是(几乎)任何类型的现有窗口。 如果你显式调用这三个函数中的任何一个(通常不必这样做),请不要将句柄传递给已经充当主机的窗口(如果执行了此操作,则不会释放现有的主机对象)。

前七个函数隐式调用 AtlAxWinInit

注意

控件托管 API 构成了 ATL 支持 ActiveX 控件包含的基础。 但是,如果你利用或充分利用 ATL 的包装类,通常很少需要直接调用这些函数。 有关详细信息,请参阅哪些 ATL 类有利于 ActiveX 控件包含

什么是 AtlAxWin100?

AtlAxWin100 是帮助提供 ATL 的控件托管功能的窗口类的名称。 创建此类的实例时,窗口过程将自动使用控件托管 API 创建与窗口关联的托管对象,并使用你指定为窗口标题的控件加载此对象。

何时需要调用 AtlAxWinInit?

AtlAxWinInit 注册“AtlAxWin80”窗口类(加上一些自定义窗口消息),因此必须在尝试创建托管窗口之前调用此函数。 但是,你并非一定需要显式调用此函数,因为托管 API(以及使用它们的类)通常会为你调用此函数。 多次调用这个函数并没有什么坏处。

什么是宿主对象?

主机对象是一个 COM 对象,代表 ATL 为特定窗口提供的 ActiveX 控件容器。 主机对象将容器窗口子类化,以便容器窗口可以将消息反射给控件,容器窗口提供了控件使用的必要容器接口,并且公开了 IAxWinHostWindowIAxWinAmbientDispatch 接口以允许你配置控件的环境。

可以使用主机对象设置容器的环境属性。

是否可在单个窗口中承载多个控件?

在一个 ATL 宿主窗口中不能托管多个控件。 每个主机窗口都设计为一次只保存一个控件(这允许一种简单的机制来处理消息反射和每个控件的环境属性)。 但是,如果你需要用户在单个窗口中查看多个控件,则创建多个主机窗口作为该窗口的子窗口也很简单。

是否可重复使用宿主窗口?

不建议重复使用主机窗口。 为了确保代码的稳定性,应将主机窗口的生存期与单个控件的生存期绑定。

何时需要调用 AtlAxWinTerm?

AtlAxWinTerm 可取消注册“AtlAxWin80”窗口类。 在所有现有的主机窗口都被销毁后,你应该调用此函数(如果你不再需要创建主机窗口)。 如果不调用该函数,则进程终止后窗口类将被自动注销。

使用 ATL AXHost 承载 ActiveX 控件

本节中的示例展示了如何创建 AXHost 以及如何使用各种 ATL 函数承载 ActiveX 控件。 本节还展示了如何从托管的控件访问控件和接收事件(使用 IDispEventImpl)。 该示例在主窗口或子窗口中托管日历控件。

注意 USE_METHOD 符号的定义。 你可以将此符号的值更改为在 1 和 8 之间变化。 符号的值决定了控件的创建方式:

  • 对于 USE_METHOD 的偶数值,创建主机的调用将窗口子类化并将其转换为控制主机。 对于奇数值,代码会创建一个充当主机的子窗口。

  • 如果 USE_METHOD 的值介于 1 到 4 之间,则对事件的控制和接收的访问是在创建主机的调用中完成的。 如果值介于 5 和 8 之间,则会查询主机以获取接口并挂钩接收器。

摘要如下:

USE_METHOD 主机 控制访问和事件接收 演示的函数
1 子窗口 单步 CreateControlLicEx
2 主窗口 单步 AtlAxCreateControlLicEx
3 子窗口 单步 CreateControlEx
4 主窗口 单步 AtlAxCreateControlEx
5 子窗口 多个 步骤 CreateControlLic
6 主窗口 多个 步骤 AtlAxCreateControlLic
7 子窗口 多个 步骤 CreateControl
8 主窗口 多个 步骤 AtlAxCreateControl
// Your project must be apartment threaded or the (AtlAx)CreateControl(Lic)(Ex)
// calls will fail.
#define _ATL_APARTMENT_THREADED
#include <atlbase.h>
#include <atlwin.h>
#include <atlhost.h>

// Value of g_UseMethod determines the function used to create the control.
int g_UseMethod = 0; // 1 to 8 are valid values
bool ValidateUseMethod() { return (1 <= g_UseMethod) && (g_UseMethod <= 8); }

#import "PROGID:MSCAL.Calendar.7" no_namespace, raw_interfaces_only

// Child window class that will be subclassed for hosting Active X control
class CChildWindow : public CWindowImpl<CChildWindow>
{
public:
   BEGIN_MSG_MAP(CChildWindow)
   END_MSG_MAP()
};

class CMainWindow : public CWindowImpl<CMainWindow, CWindow, CFrameWinTraits>,
   public IDispEventImpl<1, CMainWindow, &__uuidof(DCalendarEvents), &__uuidof(__MSACAL), 7, 0>
{
public :

   CChildWindow m_wndChild;
   CAxWindow2 m_axwnd;
   CWindow m_wndEdit;

   static ATL::CWndClassInfo& GetWndClassInfo()
   {
      static ATL::CWndClassInfo wc =
      {
         { 
            sizeof(WNDCLASSEX), 
            CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, 
            StartWindowProc,
            0, 0, NULL, NULL, NULL, 
            (HBRUSH)(COLOR_WINDOW + 1), 
            0, 
            _T("MainWindow"), 
            NULL 
         },
         NULL, NULL, IDC_ARROW, TRUE, 0, _T("")
      };
      return wc;
   }
   
   BEGIN_MSG_MAP(CMainWindow)
      MESSAGE_HANDLER(WM_CREATE, OnCreate)
      MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
   END_MSG_MAP()

   BEGIN_SINK_MAP(CMainWindow)
      SINK_ENTRY_EX(1, __uuidof(DCalendarEvents), DISPID_CLICK, OnClick)
   END_SINK_MAP()

   // Helper to display events
   void DisplayNotification(TCHAR* pszMessage)
   {
      CWindow wnd;
      wnd.Attach(GetDlgItem(2));
      
      wnd.SendMessage(EM_SETSEL, (WPARAM)-1, -1);
      wnd.SendMessage(EM_REPLACESEL, 0, (LPARAM)pszMessage);
   }
   
   // Event Handler for Click
   STDMETHOD(OnClick)()
   {
      DisplayNotification(_T("OnClick\r\n"));
      return S_OK;
   }

   LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&)
   {
      HRESULT hr = E_INVALIDARG;

      _pAtlModule->Lock();

      RECT rect;
      GetClientRect(&rect);
      
      RECT rect2;
      rect2 = rect;
      
      rect2.bottom -=200;
      
      // if g_UseMethod is odd then create AxHost directly as the child of the main window
      if (g_UseMethod & 0x1) 
      {
         m_axwnd.Create(m_hWnd, rect2, NULL, WS_CHILD | WS_VISIBLE | WS_BORDER, 0, 1);
      }
      // if g_UseMethod is even then the AtlAx version is invoked.
      else
      {
         // Create a child window.
         // AtlAx functions will subclass this window.
         m_wndChild.Create(m_hWnd, rect2, NULL, WS_CHILD | WS_VISIBLE | WS_BORDER, 0, 1);
         // Attach the child window to the CAxWindow so we can access the 
         // host that subclasses the child window.
         m_axwnd.Attach(m_wndChild);
      }

      if (m_axwnd.m_hWnd != NULL)
      {
         CComPtr<IUnknown> spControl;

         // The calls to (AtlAx)CreateControl(Lic)(Ex) do the following:
         // Create Calendar control. (Passing in NULL for license key. 
         //   Pass in valid license key to the Lic functions if the 
         //   control requires one.)
         // Get the IUnknown pointer for the control.
         // Sink events from the control.
         
         // The AtlAx versions subclass the hWnd that is passed in to them 
         //   to implement the host functionality.

         // The first 4 calls accomplish it in one call.
         // The last 4 calls accomplish it using multiple steps.

         switch (g_UseMethod)
         {
            case 1:
            {
               hr = m_axwnd.CreateControlLicEx(
                  OLESTR("MSCAL.Calendar.7"), 
                  NULL, 
                  NULL, 
                  &spControl, 
                  __uuidof(DCalendarEvents), 
                  (IUnknown*)(IDispEventImpl<1, CMainWindow, 
                     &__uuidof(DCalendarEvents), &__uuidof(__MSACAL), 7, 0>*)this
               );
               break;
            }
            case 2:
            {
               hr = AtlAxCreateControlLicEx(
                  OLESTR("MSCAL.Calendar.7"), 
                  m_wndChild.m_hWnd, 
                  NULL, 
                  NULL, 
                  &spControl, 
                  __uuidof(DCalendarEvents), 
                  (IUnknown*)(IDispEventImpl<1, CMainWindow, 
                     &__uuidof(DCalendarEvents), &__uuidof(__MSACAL), 7, 0>*)this, 
                  NULL
               );
               break;
            }
            case 3:
            {
               hr = m_axwnd.CreateControlEx(
                  OLESTR("MSCAL.Calendar.7"), 
                  NULL, 
                  NULL, 
                  &spControl, 
                  __uuidof(DCalendarEvents), 
                  (IUnknown*)(IDispEventImpl<1, CMainWindow, 
                     &__uuidof(DCalendarEvents), &__uuidof(__MSACAL), 7, 0>*)this
               );
               break;
            }
            case 4:
            {
               hr = AtlAxCreateControlEx(
                  OLESTR("MSCAL.Calendar.7"), 
                  m_wndChild.m_hWnd, 
                  NULL, 
                  NULL, 
                  &spControl, 
                  __uuidof(DCalendarEvents), 
                  (IUnknown*)(IDispEventImpl<1, CMainWindow, 
                     &__uuidof(DCalendarEvents), &__uuidof(__MSACAL), 7, 0>*)this
               );
               break;
            }
            // The following calls create the control, obtain an interface to 
            // the control, and set up the sink in multiple steps.
            case 5:
            {
               hr = m_axwnd.CreateControlLic(
                  OLESTR("MSCAL.Calendar.7")
               );
               break;
            }
            case 6:
            {
               hr = AtlAxCreateControlLic(
                  OLESTR("MSCAL.Calendar.7"), 
                  m_wndChild.m_hWnd, 
                  NULL, 
                  NULL
               );
               break;
            }
            case 7:
            {
               hr = m_axwnd.CreateControl(
                  OLESTR("MSCAL.Calendar.7")
               );
               break;
            }
            case 8:
            {
               hr = AtlAxCreateControl(
                  OLESTR("MSCAL.Calendar.7"), 
                  m_wndChild.m_hWnd , 
                  NULL, 
                  NULL
               );
               break;
            }
         }

         // have to obtain an interface to the control and set up the sink
         if (g_UseMethod > 4)
         {
            if (SUCCEEDED(hr))
            {
               hr = m_axwnd.QueryControl(&spControl);
               if (SUCCEEDED(hr))
               {
                  // Sink events form the control
                  DispEventAdvise(spControl, &__uuidof(DCalendarEvents));
               }
            }
         }

         if (SUCCEEDED(hr))
         {
            // Use the returned IUnknown pointer.
            CComPtr<ICalendar> spCalendar;
            hr = spControl.QueryInterface(&spCalendar);
            if (SUCCEEDED(hr))
            {
               spCalendar->put_ShowDateSelectors(VARIANT_FALSE);
            }
         }
      }

      rect2 = rect;
      rect2.top = rect.bottom - 200 + 1;
      m_wndEdit.Create(_T("Edit"), m_hWnd, rect2, NULL, WS_CHILD | WS_VISIBLE | 
         WS_BORDER | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE, 0, 2);
      return 0;
   }

   LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL&)
   {
      _pAtlModule->Unlock();
      return 0;
   }
};

class CHostActiveXModule : public CAtlExeModuleT<CHostActiveXModule>
{
public :

   CMainWindow m_wndMain;

   // Create the Main window
   HRESULT PreMessageLoop(int nCmdShow)
   {
      HRESULT hr = CAtlExeModuleT<CHostActiveXModule>::PreMessageLoop(nCmdShow);
      if (SUCCEEDED(hr))
      {
         AtlAxWinInit();
         hr = S_OK;
         RECT rc;
         rc.top = rc.left = 100;
         rc.bottom = rc.right = 500;
         m_wndMain.Create(NULL, rc, _T("Host Calendar") );
         m_wndMain.ShowWindow(nCmdShow);         
      }
      return hr;
   }

   // Clean up. App is exiting.
   HRESULT PostMessageLoop()
   {
      AtlAxWinTerm();
      return CAtlExeModuleT<CHostActiveXModule>::PostMessageLoop();
   }
};

CHostActiveXModule _AtlModule;

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
   UNREFERENCED_PARAMETER(hInstance);
   UNREFERENCED_PARAMETER(hPrevInstance);

   g_UseMethod = _ttoi(lpCmdLine);

   if (ValidateUseMethod())
   {
      return _AtlModule.WinMain(nCmdShow);
   }
   else
   {
      return E_INVALIDARG;   
   }
}