Delphi 编程 -- 再谈变体类型(操作 Excel)
关于 Delphi 的变体类型,我们说主要是用来与外部世界打交道的。今天我们就来讨论一下 Delphi 代码操作 Excel 的问题。微软的 Office 软件有一个非常好的特点,就是可以通过程序代码对软件的各个部分进行操作,特别是 Excel,由于 Excel 主要是用来处理数据,所以特别适合通过代码来操作。Excel(以及其他 Office 的组成部分)之所以能够通过代码来操作,是因为微软的一项特别技术:OLE Automation。原则上,只要一个软件支持 OLE Automation,就可以通过代码(VB/VBA、Delphi、C/C++)来操作该软件。当然 OLE Automation 是一项比较复杂的技术,我们今天不深入技术细节,主要是讨论 Delphi 如何通过 OLE Automation 来控制 Excel。Excel 通过 IDispatch 接口将其内部的对象呈现给外界,而 Delphi 代码通过这个接口可以访问 Excel 暴露出的对象,从而通过调用这些对象的方法来操作 Excel。那 Delphi 通过什么样的数据类型来保存 IDispatch 接口并进而通过该接口访问相关的对象呢?答案是:变体类型。Delphi 的变体类型可以接纳 varDispatch 类型,而这个就是 OLE Automation 的 IDispatch 接口类型。这个简单的例子是生成一张 Excel 数学考试成绩单。程序一开始,首先声明两个变体类型的变量,xl,用来代表Excel 的 Application 对象,sheet,用来代表 Excel 的当前表单。因为 Excel 是一个 OLE Automation 服务器,它的运行需要 COM 的支持,所以操作 Excel 的 Delphi 代码要在程序开始时启动(CoInitialize) COM,程序结束时终止(CoUninitialize) COM。启动 COM 以后,就可以通过 CreateOleObject 来启动 Excel。CreateOleObject 是创建 OLE Automation 服务器对象的标准函数,其声明如下:functionCreateOleObject(const ClassName: string): IDispatch;
CreateOleObject 接受一个字符串形式的类名(本例中的 'Excel.Application'),返回该类名所代表的 OLE Automation 对象的 IDispatch 接口。在我们的例子中,将这个接口赋值给变体类型变量 xl 的过程是通过调用 VarFromDisp 过程完成的。如果 CreateOleObject 成功返回,则 xl 即获得了 Excel.Application 对象的 IDispatch 接口,随后就可以通过该接口操作 Excel 中的对象了,正如我们接下来所看到的。Application 是 Excel 最顶层的对象,Application 对象下面包含有 Excel 的工作簿集合(Workbooks)、工作簿(Workbook)、表单集合(Worksheets)、表单(Worksheet)等等各种对象。Delphi 代码操作 Excel 就是调用这些对象的方法以完成各种任务。我们的例子中,第 24 行到第 34 行,就是通过 Excel 的表单完成对单元格(Cells)的操作的。具体结果如下:前面说过,Delphi 将 CreateOleObject 返回的 IDispatch 接口赋值给变体类型变量 xl,是通过调用 VarFromDisp 完成的。下面是 VarFromDisp 的源代码(取自 System.Variants.pas):procedure _VarFromDisp(var V: TVarData; const Value: IDispatch);begin if (V.VType and varDeepData) <> 0 then VarClearDeep(V); V.VDispatch := nil; V.VType := varDispatch; IInterface(V.VDispatch) := Value;end;
在将 Value 赋值给 V 之前,首先判断 V 是否有值并且需要深度清理,若是,则调用 VarClearDeep 对 V 进行深度清理。接着,对 VDispatch 字段赋以空指针,即对 V 本身赋以空值。然后再对 V 进行赋值:- 将 varDispatch 赋给 VType,说明这是一个 IDispatch 类型的接口。
- 将 Value 赋给 VDispatch,因为 VDispatch 是一个通用的指针类型(Pointer),所以要通过 IInterface 对其进行类型转换,以保证赋值号两边的赋值相容性:IInterface 就是 IUnknown,IDispatch 则是从 IUnknown 衍生而来(IDispatch = interface(IUnknown) ...)。