在自动化中使用早期绑定和后期绑定

摘要

如何绑定到自动化服务器可能会影响程序中的许多事项,例如性能、灵活性和可维护性。

本文介绍自动化客户端可用的绑定类型,并权衡每个方法的两侧。

更多信息

自动化是一个过程,其中一个软件组件使用 Microsoft 的组件对象模型 (COM) 与另一个软件组件通信和/或控制另一个软件组件。 它是在 Visual Basic 或 Visual Basic for Applications 等语言中使用的大多数跨组件通信的基础,并且已成为大多数程序的正常部分。

从历史上看,自动化对象是支持 IDispatch 接口的任何对象。 此接口允许客户端在运行时调用方法和属性,而无需知道他们在设计时与之通信的确切对象;称为后期绑定的进程。 但是,如今,“自动化”一词可以应用于几乎任何 COM 对象,即使是那些不支持 IDispatch 的对象 (,因此无法延迟绑定) 。 本文假定要自动执行的对象支持这两种绑定方法。

什么是绑定?

绑定是程序员写入的实际代码 (实现函数的内部或外部) 的函数调用的匹配过程。 在编译应用程序时完成此操作,并且必须在执行代码之前绑定代码中调用的所有函数。

要了解这一过程,请考虑在出版书籍方面进行“绑定”。 想象一下,你的代码就像书的文本,在某一段中,你写了类似“请参阅第 12 章,第 x 页了解更多详细信息”之类的内容。在完成书籍之前,你不知道页码是什么,因此,在按预期读取该段落之前,必须将书籍的所有页面绑定在一起,并将正确的页码插入到段落中。 你等待这本书被“绑定”,然后才能引用这本书的其他部分。

绑定软件类似。 代码由需要拉到一起的部件组成,然后才能“读取”代码。绑定是将函数名称替换为内存地址 (或内存偏移量的行为,更精确) 调用函数时代码将“跳转到”的位置。 对于 COM 对象,地址是指针表中的内存偏移量, (称为对象所持有的 v 表) 。 绑定 COM 函数时,它将通过 v 表绑定。

COM 对象的结构很简单。 当代码保存对对象的引用时,它包含指向 v 表顶部的间接指针。 v 表是内存地址的数组,其中每个条目是可对该对象调用的不同函数。 若要对 COM 对象调用第三个函数,请跳下表中的三个条目,然后跳转到给定的内存位置。 这会执行函数的代码,并在完成后返回准备执行下一行代码。

+-[Code]------------+  +.................................[COM Object]...+
|                   |  : +-------------+                                :
|Set obj = Nothing -|--->| obj pointer |                                :
|                   |  : +-|-----------+                                :
+-------------------+  :   |   +-----------------+                      :
                       :   +-->| v-table pointer |                      :
                       :       +--|--------------+                      :
                       :          |                                     :
                       :          |  +----------------------------+     :
                       :  (3rd)   |  | Function 1 Address pointer |     :
                       : (Offset) |  +----------------------------+     :
                       :          |  | Function 2 Address pointer |     :
                       :          |  +----------------------------+     :
                       :          +->| Function 3 Address pointer |     :
                       :             +----------------------------+     :
                       +................................................+

上面的示例演示释放 COM 对象时会发生什么情况。 由于所有 COM 对象都继承自 IUnknown,因此表中的前三个条目是 IUnknown 的方法。 当需要释放对象时,代码将调用 v 表中的第三个函数 (IUnknown::Release) 。

幸运的是,此工作由 Visual Basic 在幕后完成。 作为 Visual Basic 程序员,你永远不必直接处理 v 表。 但是,此结构是所有 COM 对象的绑定方式,必须熟悉它,了解什么是绑定。

早期绑定

上面的示例是所谓的早期 (或 v 表) 绑定。 对于所有 COM 对象,每当调用 COM 对象的 IUnknown 接口时,都会发生这种形式的绑定。 但是对象的其他函数呢? 如何调用其 Refresh 方法或其 Parent 属性? 这些是通常对对象唯一的自定义函数。 如果无法假定 V 表中的位置,如何找到调用它们所需的函数地址?

当然,答案取决于你是否事先知道对象的 v 表的外观。 如果执行此操作,则可以执行与对对象的 IUnknown 方法执行相同的早期绑定过程。 这通常是指“早期绑定”。

若要对对象使用早期绑定,需要知道其 v 表的外观。 在 Visual Basic 中,可以通过添加对描述对象的类型库、其接口 (v 表) 以及可对对象调用的所有函数的引用来执行此操作。 完成此操作后,可以将对象声明为特定类型,然后使用 v 表设置并使用该对象。 例如,如果想要使用早期绑定自动执行 Microsoft Office Excel,则会从 Project 添加对“Microsoft Excel 8.0 对象库”的引用|引用对话框,然后将变量声明为“Excel.Application”类型。从此开始,对对象变量进行的所有调用都将提前绑定:


' Set reference to 'Microsoft Excel 8.0 Object Library' in
' the Project|References dialog (or Tools|References for VB4 or VBA).

' Declare the object as an early-bound object
  Dim oExcel As Excel.Application

  Set oExcel = CreateObject("Excel.Application")

' The Visible property is called via the v-table
  oExcel.Visible = True

此方法在大多数情况下都非常有效,但如果不知道设计时要使用的确切对象,该怎么办? 例如,如果需要与多个版本的 Excel 或可能完全与“未知”对象通信,该怎么办?

后期绑定

COM 包括 IDispatch。 如果实现 IDispatch 的对象还具有可提前绑定到) 的自定义接口,则其唯一支持) 或双接口 (的接口,则表示其具有分页 (。 绑定到 IDispatch 的客户端据说是“延迟绑定”的,因为它们调用的确切属性或方法是在运行时使用 IDispatch 方法来确定的。 回到之前的书籍示例,将其视为一个脚注,指示你到目录,你必须在“阅读时”“查找”页码,而不是在文本中打印它。

接口的魔力由两个函数控制:GetIDsOfNames 和 Invoke。 第一个将函数名称 (字符串) 映射到标识符 (称为表示函数的不温不火) 。 知道要调用的函数的 ID 后,可以使用 Invoke 函数调用它。 这种形式的方法调用称为“后期绑定”。

同样,在 Visual Basic 中,指定对象绑定方式的方式也是通过对象声明。 如果将对象变量声明为“对象”,则实际上告知 Visual Basic 使用 IDispatch,因此绑定较晚:

' No reference to a type library is needed to use late binding.
' As long as the object supports IDispatch, the method can 
' be dynamically located and invoked at run-time.

' Declare the object as a late-bound object
  Dim oExcel As Object

  Set oExcel = CreateObject("Excel.Application")

' The Visible property is called via IDispatch
  oExcel.Visible = True

如你所见,代码的其余部分是相同的。 在编写) 的代码方面,早期绑定与后期绑定 (之间的唯一区别在于变量声明。

请务必注意,“后期绑定”是要调用的函数,而不是调用函数的方式。 从前面关于绑定的一般讨论中,你应该注意到 IDispatch 本身是“早期绑定”的:“也就是说,Visual Basic 通过 v 表条目 (IDispatch::Invoke) 调用来调用 Visible 属性,就像任何 COM 调用一样。 COM 对象本身负责将调用转发到正确的函数,使 Excel 可见。 此间接允许将 Visual Basic 客户端编译 (,即绑定到有效的函数地址) 但仍然不知道实际执行该工作的确切函数。

不温不火的绑定

某些自动化客户端 (最明显的 MFC 和 Visual Basic 3.0,但在 ActiveX 控件方面也使用 Visual Basic 5.0 和 6.0,) 使用名为 dispid 绑定的后期绑定的混合形式。 如果在设计时知道 COM 对象,则可以缓存调用的函数的消散,并直接传递给 IDispatch::Invoke,而无需在运行时调用 GetIDsOfNames。 这可以大大提高性能,因为无需对每个函数进行两次 COM 调用,而只需执行一次。

不平淡绑定不是通常可以在 Visual Basic 5.0 或 6.0 中选择的选项。 它用于在类型库中引用但不包含自定义接口 (的对象,即,对于仅) 和聚合 ActiveX 控件的对象,但一般而言,Visual Basic 使用早期绑定,任何通常使用不平淡绑定的对象。

我应使用哪种形式的绑定?

此问题的答案与项目设计一样多。 Microsoft 建议在几乎所有情况下提前绑定。 但是,可能有理由选择延迟绑定。

早期绑定是首选方法。 它表现最佳,因为应用程序直接绑定到调用的函数的地址,并且在执行运行时查找时没有额外的开销。 就总体执行速度而言,它至少是后期绑定的两倍。

早期绑定还提供类型安全性。 如果对组件的类型库设置了引用,Visual Basic 将提供 IntelliSense 支持,以帮助你正确编码每个函数。 如果参数或返回值的数据类型不正确,Visual Basic 还会警告你,在编写和调试代码时节省了大量时间。

在设计时不知道对象的确切接口的情况下,后期绑定仍然很有用。 如果应用程序寻求与多个未知服务器通信,或者需要使用 Visual Basic 6.0 CallByName 函数(例如) )按名称 (调用函数,则需要使用后期绑定。 后期绑定还可用于解决组件的多个版本之间的兼容性问题,这些组件在版本之间不正确地修改或调整了其接口。

为早期绑定提供的优势使其尽可能成为最佳选择。

在多个版本之间保持兼容性

如果你将使用不与安装程序包重新分发的组件,并且无法确保你将在运行时与之通信的确切版本,则应特别注意提前绑定到与组件的所有版本兼容的接口,或者在某些情况下 () 使用后期绑定来调用特定版本中可能存在的方法,并在某些情况下正常失败客户端系统上安装的版本中不存在该方法。

Microsoft Office 应用程序提供了此类 COM 服务器的一个很好的示例。 Office 应用程序通常会扩展其接口,以添加新功能或更正版本之间的先前缺陷。 如果需要自动执行 Office 应用程序,建议提前绑定到客户端系统上可能安装的最早版本的产品。 例如,如果需要能够自动执行 Excel 95、Excel 97、Excel 2000 和 Excel 2002,则应使用 Excel 95 (XL5en32.olb) 的类型库来保持与所有三个版本的兼容性。

Office 应用程序还表明,具有大型双接口的对象模型可能会受到某些平台上的封送限制。 若要使代码在所有平台上运行最佳,请使用 IDispatch。

参考

有关 COM、v 表和自动化的详细信息,请参阅以下书籍:

罗杰森, 戴尔, 内部 COM, MSPRESS, ISBN: 1-57231-349-8.

Curland, Matt, Advanced Visual Basic 6, DevelopMentor, 0201707128.