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

ATL回调和连接点简介

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.33/5 (8投票s)

2005年1月23日

4分钟阅读

viewsIcon

62793

本文提供了关于实现回调接口和可连接对象及连接点的通用概念的视角。

A general callback scenario

引言

使用 ATL 和 MFC 编程 COM 一直以来都有些神秘,特别是与 Visual Basic 为我们轻易创建它们相比。也许最常用但又最令人畏惧的便是回调功能。本文试图更深入地理解回调,而不是用“源”(source)和“宿”(sink)这样晦涩的术语来迷惑读者。

背景

我假设大多数读者都倾向于使用 ATL 进行 COM 开发,并且熟悉接口,能够编辑 IDL 文件,并在没有向导过多帮助的情况下操作接口的方法和属性!

描述

考虑一个非常典型的客户端-服务器场景,其中服务器是 COM 组件(可以是进程内、进程外、服务等任何东西),客户端是 VC++ 或 VB 程序,它希望利用您的 COM 服务器提供的服务。假设您的客户端程序调用服务器的方法,例如 `add()` (哇!!!)。假设服务器开始执行耗时的“添加”任务。您的客户端应用程序可以继续工作,当它需要从服务器获取结果时……这才是真正的问题!传统的接口提供从客户端到服务器的单向通信。现在,服务器如何通知客户端它已准备好结果呢?在典型的 COM 术语中,我们将服务器接口称为“源”(SOURCE),将客户端事件处理程序称为“宿”(SINK)。在适当的地方我们会使用这些术语,在讨论的其余部分,我们可以继续使用我们习惯的“客户端-服务器”通用名称。现在我们有了一系列选择。如果您精通计算机组成和体系结构,您可能知道轮询、中断驱动 I/O 等技术。

  1. 我们可用的第一个选择类似于轮询技术。客户端持续检查一个属性或一个布尔方法,该属性或方法将指示操作是否完成。客户端会一直停留在循环中,直到返回一个“true”,该值标志着服务器端操作的完成(成功或失败)。虽然简单,但这绝对不是一个高效或优雅的解决方案。更好的选择是让服务器能够通知客户端它已完成工作,客户端可以取回结果。
  2. 这种方法类似于中断驱动的 I/O(不期望直接比较)。在服务器的 .idl 文件中添加一个接口。这个接口声明了当服务器端有结果可用时要执行的函数。服务器的 IDL 文件声明了接口,但没有实现它。

    这个接口的实现工作留给了客户端。这样的客户端需要提供函数定义来处理服务器返回的结果。假设服务器提供一个名为 `_IServerEvents` 的接口,VB6 客户端代码可以这样写:

    Option Explicit
    
    Implements _IServerEvents
    
    Private Sub _IServerEvents_CallbackFunction()
      ...
    End Sub

    类似地,对于 C++ 客户端,假设我们有一个可用的 `CMyClient` 类。该类继承自您的 `_IServerEvents` 接口。该类重写 `CallbackFunction()` 方法以处理回调后的处理。

            class CMyClient: public _IServerEvents
            {
            public:
                STDMETHODIMP CallbackFunction()
                {
                    //Your code goes here
                    ...
                }
            };
  3. 尽管上一步的解决方案已经足够令人满意,但它对客户端实现提供的接口增加了额外的限制。当我们在项目中添加类时,ATL 向导提供了一个更透明的解决方案。

    Enable connection points in out CoClass

现在打开您项目的 IDL 文件并添加以下内容:

        dispinterface _IServerEvents
        {
        properties:
        methods:
        };

在 `coclass` 标签中,添加以下几行:

        [
        uuid(xxxxxxxxx),
        helpstring("...")
        ]
        [default,source] dispinterface _IServerEvents;

像传统的接口一样,向 `dispinterface` 添加一个方法。在本例中,我们称之为 `CallbackFunction`。现在最重要的事情是,在类视图中,右键单击 `CoClass` 并选择“实现连接点…”(Implement Connection Point...)。会弹出一个对话框。按“确定”即可完成。

Implement the connection point

您会看到 `Fire_CallbackFunction(...)` 函数已为您实现。无论何时需要通知客户端,您都可以调用此函数并传递所需的参数。请记住,IDL 文件中的回调函数参数应具有 `[in]` 属性。

最后一部分是用 VB6 实现客户端。这非常容易。在声明服务器类变量时,使用“WithEvents”来启用您的应用程序接收来自服务器的事件。这样在 VB 中一切都变得非常简单。

但是,用 VC++ 实现客户端的代码并不那么直接。由于本文只是为了让您初步了解回调,我将尽量避免陷入不必要的细节。不过,如果有人决心要用 VC++ 编写客户端,我将非常乐意提供帮助。请给我留言,我将回复您,甚至发布一篇单独的文章来描述这个过程。

我的观察

在编写项目代码时,我需要通过回调函数传递一个 **variant**。当我编译我的代码(VC++ 6.0 with SP5 !!!)时,我收到一个警告:转换为 `bool`,可能丢失数据。运行我的客户端后,我发现它实际上收到了“True”值。回调函数对 BSTRdoubleint 等数据类型都工作得很好!最终我发现,我必须使用 CComVariant 类的 InternalCopy() 方法将参数值复制到 CComVariant 数组中。请务必小心,切勿假设向导生成的代码永远不会出错:)

© . All rights reserved.