COM自动化.docx

上传人:b****5 文档编号:28488920 上传时间:2023-07-15 格式:DOCX 页数:23 大小:28.46KB
下载 相关 举报
COM自动化.docx_第1页
第1页 / 共23页
COM自动化.docx_第2页
第2页 / 共23页
COM自动化.docx_第3页
第3页 / 共23页
COM自动化.docx_第4页
第4页 / 共23页
COM自动化.docx_第5页
第5页 / 共23页
点击查看更多>>
下载资源
资源描述

COM自动化.docx

《COM自动化.docx》由会员分享,可在线阅读,更多相关《COM自动化.docx(23页珍藏版)》请在冰豆网上搜索。

COM自动化.docx

COM自动化

COM 自动化,第一部分

--------------------------------------------------------------------------------

在这部分和以后的内容中,我们将深入 COM 自动化世界。

希望用一个简要的专栏来

研究这个题目。

我们将谈论怎样进行自动化(IDispatch)调用和处理自动化对象需要做什

么。

然后,我们将讨论用于自动化的特殊 COM 数据类型和研究双重接口。

自动化(从前叫做 OLE 自动化)是一个和迄今为止我们曾认为标准 COM vtable 接口

完全不同的客户调用服务器方法。

自动化是使用标准 COM 接口 IDispatch 来存取对象的自动化接口。

因此,我们说任何

实现 IDispatch 的对象实现了自动化。

为什么要自动化?

最初开发自动化是作为一种应用程序(例如 Word 和 Excel)用以把其功能显露给其他应用,包

括脚本语言的方式。

目的是提供一种简单方式来访问属性和调用方法,这种方式尽可能少

的占用自动化客户的资源,并且不需要被访问对象的类型信息就可以进行调用的方法。

在 C++头文件中描绘接口的类型信息决不是浪费时间,描绘方法的 vtable 偏移量也很重要,

最困难的是,设置正确的 C++堆栈框架以便正确的执行方法调用。

对一个基于文本的解释

语言所有这些尤其需要技巧。

如果每个脚本语言都不得不做这个机灵的程序,那么很少有能存取 COM 对象的了。

使用

自动化,对象就可以提供一个简单的自动化接口,这样脚本语言作者只需掌握 IDispatch 和

几个 COM 应用程序接口就可以了。

VisualBasic 的第一个 32 位版使用自动化存取 OLE 控件(现在叫 ActiveX 控件),他代替

了 16 位的 Visual Basic 的 VBX 控件。

Visual Basic 仍然可以使用自动化存取一个控件的属

性和方法,但是更近的版本也支持使用标准 COM vtable 接口。

这次我们创建的例子将使用

自动化接口。

脚本语言,例如 Visual Basic for Applications、VBScript 和 J/Script,以独占模式使用自动化。

所以如果你想要你的对象可以被脚本语言使用,你必须实现一个自动化接口。

对象和属性和方法,噢,我的上帝!

世界上关于自动化有三个主要概念。

对象是最重要的概念。

对象显露属性和方法。

图 1. 自动化对象的属性和方法

把这个与更复杂的世界的 COM 观点对比,在这种观点中,是接口,而不是对象,是第一

位的,而属性是不存在的,并且每个对象能有多个包含多个方法的接口。

图 2. COM 对象,接口,方法(包括没有标签的 IUnknown)

方法与 C++成员函数相似,而自动化的属性则与 C++数据成员和实例数据(也叫属性)相

似。

注意接口没有独立的概念,每个对象有一个自动化接口。

进一步注意到 COM 接口没

有属性的概念,它们只有方法。

(但是我们可以使用 get/set 方法对模拟属性。

自动化对象怎样被创建?

创建一个自动化对象是一个简单的操作。

这儿我将使用 VisualBasic 作为例子,但是在任

何兼容自动化的语言中,方法基本上一样。

在 Visual Basic,你应先创建一个对象变量:

Dim Beeper as Object

……接着设置它指向一个特殊的对象:

Set Beeper = CreateObject("BeepCntMod.BeepCnt")

在这个例子中我们创建了一个 BeepCnt 对象(见 第一篇 ATL 文章,这篇文章将会在以后刊

出-译者注)。

我们可以接着调用对象上的方法控制它的属性,就像我们不久将看到的。

但是首先,让我们讨论 Visual Basic(或者任何自动化客户程序)在幕后真正做什么。

我们早已知道我们将通过 IDispatch COM 接口访问自动化对象。

所以 DIM 语句只显示集合

至少需要的内存,因此 Visual Basic 能为我们即将创建的对象访问 IDispatch 指针。

CreateObject 调用需要有一点技巧。

首先,GUID 在哪里?

对象的 CLSID 没有 GUID 我们

怎样创建它?

你可以重新调用,这样我们可以通过对象的 ProgID 引用对象类型。

你也可以重新调用我们

在注册表用 ProgID 作为键名注册的一个键。

该键用一个 CLSID 作为子键。

COM 提供一个叫 CLSIDFromProgID 的函数,它根据给出的 ProgID 查找 CLSID。

Visual

Basic 用我们传送到 CreateObject 的字符串调用这个函数。

在这个例子中,Visual Basic 将传

送"BeepCntMod.BeepCnt"。

CLSIDFromProgID 查阅那个键和返回与它相关的 CLSID。

(顺

便说一句,ProgID 的第一部分是模块或应用程序名,第二部分是模块或应用程序中的对象

名。

在这一点上 VisualBasic 调用我们的老朋友 CoCreateInstanceEx,传送 CLSID 和请求

IDispatch 接口。

如果 CoCreateInstanceEx 成功,VB 创建一个包含由 CoCreateInstanceEx 收

到的 IDispatch 指针的对象变量,并且把它分配给我们的对象变量。

如果因任何原因创建失败:

对象不存在,或者它没实现 IDispatch,则对 CreateObject 的调

用失败。

就像你所看到的,Visual Basic(或任何自动化客户)的开销是最小的,所有必须知道的是

用两个简单的 COM 函数创建对象。

那么你怎样访问自动化属性和方法?

访问我们的对象的 Visual Basic 源代码可能像下面这样:

BC = Beeper.Count

Beeper.Count = 5

Beeper.Beep

这三个语句分别访问一个属性、设置一个属性和调用一个方法,都只使用了两种 IDispatch

方法:

GetIDsOfNames 和 Invoke。

IDispatch:

:

GetIDsOfNames 获得与方法或属性的文本名有

关的整型 ID。

Visual Basic 调用它发现"Beep(嘟嘟响)"对应 ID 1 和"Count(计数)"对应

ID 2。

当我们调用 IDispatch:

:

Invoke 时,我们需要这些叫做 dispids 的 ID。

所有现行的自动化属性和方法访问都是通过调用 IDispatch:

:

Invoke 实现。

换句话说,你的

自动化客户要访问自动化对象所必须知道是几个简单的 COM 调用。

如果你的执行语言不

是 C 或 C++,你可以为你的运行时间编写帮助者来做那些调用,所以从任何程序使用自动

化是简单的。

也许很简单,但并不是不重要:

IDispatch:

:

Invoke 接收一批参数,所有的参数必须被正确设

置。

最重要的是:

 

一个叫 dispid 的整型 ID,它指定要被访问的属性和方法(我们通过调用包含属性或方法名

的字符串的 GetIDsOfNames 获得它)。

一个包含一列参数指针的结构。

(每个参数被存储到包含一个典型标记和一个叫 variant 的

共用体的结构中。

一个包含指向属性(设置它、获得它、用一个引用设置它)或者方法(调用它)数列的指

针的结构。

一个作为属性获取的或者是方法调用返回的返回值参数,也是一个变量。

噢,万一你想本地化方法、属性、名字化参数名或参数值,Invoke 和 GetIDsOfNames 都接

受一个本地 ID。

Invoke 也有几个其他参数可以把错误信息传递给自动化客户。

在这里我们将假定我们处在

一个完美的世界,暂时不考虑它们。

变量(Variant)以 16 个字节存储。

前两个字节是一个标记,包含一个代表变量类型的数,

其次的六个字节填满,最后的八个字节是变量的值。

值的格式取决于标记的值。

在 C/C++

中,我们使用一个共用体表示变量的值。

变量可以拥有大多数 C++数据类型,包括指针、

数组、串、日期和当前对象。

下一次我们将对包括变量 COM 数据类型做完整的处理。

那么为什么它比常规接口容易?

注意不一定是必须通过 IDispatch:

:

Invoke 进行一次调用:

没有偏移量--我们使用一个 dispid,我们通过请求对象本身获得它。

没有 C/C++参数列表和调用约定--我们使用变量的数组。

不用 C/C++头文件告诉你上面的东西--但是典型库是可选择的。

你不能完全使用 C/C++来进行分发。

很显然,这四个调用需要使用 C/C++调用约定完成。

但这是你作为一个自动化客户唯一需要担心的地方。

那么,所有需要为调用做的是一个对象的 IDispatch 指针,你想访问的属性、方法名和参数

的列表。

出于同样的原因,如果你想用脚本语言中编写 COM 对象,只有用 IDispatch(噢,一种创

建对象的方式)代替试图处理常规接口的无数变量的无数细节,这会使你的语言运行时间

实现更方便。

COM 接口和自动化间的不同

从前面的描述,你可以直接的看到自动化与 COM 接口间不同的几个方式:

自动化接口不必是永恒的,虽然在运行的时候你不必改变它们,因为客户能缓存 dispids。

但是自动化接口的改变是平常的,尤其是从对象的一个版本到另一个版本时添加方法。

(如

果你删除方法或改变参数,你可能破坏现有的客户代码。

自动化方法(和属性)可以获取包括不同类型的可变长度参数列表。

在运行时解析参数和

执行任何必需的类型转变是 IDispatch:

:

Invoke 的事。

(如果不能实现参数的转换,对象的

IDispatch:

:

Invoke 实现将返回一个错误,HRESULT。

自动化方法和属性的访问是最后限制的;换句话说,确定要被访问的方法/属性被推迟到调

用时才进行。

因为所有这个后期连接,自动化方法和属性是多形态的,在某种意义上更像 Smalltalk 而非

C++;你可以存取任何对象上任何方法或属性,拒绝有害的调用是对象的责任。

例如,你

可以在一个任意对象上调用一个 Print 方法。

任何对象调用 Print 方法大概都会打印它的值;

没有任何对象会发生调用失败。

在 C++中,你只能在那些定义了 Print 方法的类的对象上调

用 Print:

在编译时检查名字和参数,而不是运行时。

那么看一些例子怎么样?

作为一个快速例子,让我们着眼于刚展示的三个调用怎样使用。

调用一个方法

因为没有参数传递到 Beep 方法,也没有从 Beep 方法或返回值,让我们先调用它。

记住调

用被写作:

Beeper.Beep

首先你需要知道一件事:

参数指针传递到 Invoke 实际上是一个指向一个 DISPPARAMS 结

构的指针,就像下面定义的:

typedef struct FARSTRUCT tagDISPPARAMS{

// Pointer to array of arguments, named and unnamed

VARIANTARG FAR* rgvarg;

// Array of Dispatch IDs of named arguments

DISPID FAR* rgdispidNamedArgs;

// Total number of arguments, named and unnamed

unsigned int cArgs;

// Number of named arguments.

unsigned int cNamedArgs;

} DISPPARAMS;

我们将不会使用名字化的参数,所以 rgdispidNamedArgs 将为空,而 cNamedArgs 将为零。

进行这个简单调用的代码为:

DISPID dispid;

OLECHAR * szMember = "Beep";

// No parameters, so no array

DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};

// pdisp is an IDispatch pointer

// to the Beeper object

hresult = pdisp->GetIDsOfNames(IID_NULL, &szMember, 1,

LOCALE_USER_DEFAULT, &dispid);

hresult = pdisp->Invoke(

dispid,

IID_NULL,

LOCALE_USER_DEFAULT,

DISPATCH_METHOD,

&dispparamsNoArgs, NULL, NULL, NULL);

首先,我们通过调用 GetIDsOfNames 获得 dispid。

在两次调用中,IID_NULL 值是保留参

数的值。

我们传递一个指向字符指针的数组的指针(在这个例子中,只有一个字符指针)

和数组中指针的数目、地区(万一我们想要本地化名字)和 dispid 数组的一个指针(同样,

这儿只有一个指针)。

当调用返回时,dispid 将包含与"Beep"对应的 dispid。

如果我们提供一种方式记住"Beep"的 dispid,我们不用多次调用 GetIDsOfNames。

一旦我们有 dispid,我们可以调用 Invoke。

你将注意即使参数列表是空的,我们仍不得不提供一个最小的 DISPPARAMS 结构。

也会

注意我们不得不说明我们想通过 DISPATCH_METHOD 执行方法调用。

你也将注意到这比只调用 Beep( )更加复杂的多。

自动化对非 C/C++客户来说容易使用,但

是他总比直接调用来得慢。

然而,如果服务器在另一个进程中或另一个机器上,设置(和

执行)自动化调用的额外时间变的可以忽略。

获取一个属性的值

获取一个属性的值和调用一个方法很相似,唯一的不同是当调用返回时我们将注意返回值。

Visual Basic 中获得一个属性的值的代码写为:

BC = Beeper.Count

调用 Invoke 的 C++代码是:

VARIANT varResult;

// No parameters, so no array

DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};

// dispid set by call to GetIDsOfNames

// (omitted for brevity)

hresult = pdisp->Invoke(

dispid,

IID_NULL,

LOCALE_USER_DEFAULT,

DISPATCH_PROPERTYGET,

&dispparamsNoArgs, &varResult, NULL, NULL);

// Property's value stored in varResult

通过传递参数获取参数化属性(或者"属性数组")很容易,就象当获取(和设置)属性时

用到的一个索引或查找关键字。

注意 VisualBasic 语法不能区分获取属性和调用没有参数但有返回值的方法之间的区别。

换句话说,不可能从语法上区别 BC = Beeper.Count 是存储一个叫 Count 的属性或是调用一

个叫 Count 的方法。

作为一个结果,一些自动化客户将为一个属性访问传送两个标记,在一个属性获取的情况

下,可能传送 DISPATCH_PROPERTYGET | DISPATCH_METHOD 因为它不能区别是否它

在做一个属性存取或一个方法调用。

自动化对象不得不通过根据 dispid 是指向一个属性或

是一个方法来正确运行。

设置一个属性的值

设置一个属性与获取一个属性有三种区别:

属性的新值有一个参数。

使用 dispid DISPID_PROPERTYPUT 为参数命名。

返回的值参可以忽略。

设置一个属性与调用一个方法的不同之处在于属性设置的参数使用特殊的 dispid 命名。

为什么我们不得不进行这么详细的讨论?

记住属性可以被参数化的。

属性的值有一个特殊

的名字使自动化对象容易认出哪个参数是属性的值。

(好大夫真正想避免使用命名的参数,

但是这是我们绝对需要的例子)

回想在 Beeper 对象上设置 Count 属性的 VB 代码是:

Beeper.Count = 5

调用的 C++代码是:

// parameter structure

DISPPARAMS dispparams;

// one-element array of parameter names

DISPID mydispid[1] = { DISP_PROPERTYPUT };

// one-element array of parameters

VARIANTARG vararg[1];

dispparams.rgvarg = vararg; // 1-element array

VariantInit(&rgvarg[0]);

dispparams.rgvarg[0].vt = VT_I4; // 32-bit integer

dispparams.rgvarg[0].iVal = 5; // here's our 5!

dispparams.rgdispidNamedArgs = mydispid; // name array

dispparams.cArgs = 1; // total args

dispparams.cNamedArgs = 1; // named args

// dispid set by call to GetIDsOfNames

// (omitted for brevity)

hresult = pdisp->Invoke(

dispid,

IID_NULL,

LOCALE_USER_DEFAULT,

DISPATCH_PROPERTYPUT,

&dispparams, NULL, NULL, NULL);

所有这些代码是一个小调用!

对于实现自动化调用需要多少代码和为什么自动化可能很缓慢,你可能开始有一个概念了。

如果有更多的参数,这三行设置每个变量:

VariantInit(&rgvarg[0]);

dispparams.rgvarg[0].vt = VT_I4; // 32-bit integer

dispparams.rgvarg[0].iVal = 5; // here's our 5!

……每个参数会被重复--一个语句初始化变量、一个语句设置它的类型、一个语句设定它

的值。

如果参数需被命名,在 rgdispidNamedArgs 中有一个附加的语句设置每个参数名。

并且参数的大小应被正确设置。

所以自动化是用代码的尺寸作为代价实现了灵活调用。

并且它还放弃一件事情:

你唯一能

传递的参数是那些能在变量(variant)中被描述的参数。

而最大的损失是你不能使用变量

描述结构。

我们不久将讨论变量。

如果你想知道更多关于自动化参数传递的信息,查找你最喜欢的

COM 参考书。

准备写你自己的 IDispatch:

:

Invoke 调用?

阅读本部分……

除非你正在编写你自己的脚本语言或直接调用 IDispatch:

:

Invoke,否则你不会遇到我们将要

讨论的问题,对于一个 C++客户来讲是一个纯自动化对象。

在阅读关于可选择的自变量时,会发现一个令人迷惑的情形:

现在对 IDispatch:

:

Invoke 的

COM 文档中,你不得不为每个遗漏的没命名的变量传送带有 VT_ERROR 标记的变量。

果你跳过一个没命名的自变量,就像在 object.method(a,,c)中,是明显正确的。

但是如果可

选择的自变量是在最后呢?

脚本语言能否根本不使用类型库就知道要传递多少哑元自变量?

答案:

不能。

如果你最后想省略没命名的可选择的自变量,你可以省略它们。

使用实现了 IDispatch 的

COM 对象将会通过向在 IDL 中指定的双重接口函数提供自变量来正确处理这种情况。

是有一个 gotcha:

如果你在调用的对象本身实现了 IDispatch:

:

Invoke 本身,而不依赖

COM,它可以被写入,所以它假设所有可选择的和缺省值的自变量为哑元自变量,--所以

如果你能获取类型库信息,你应该可以确定参数的数目,,因此你能提供哑元自变量。

自动化:

服务器端

关于怎样调用方法、设置和获取属性、存取返回值和传送参数,我们现在看到的比我们曾

经想知道的还要多。

自动化对象怎样处理所有这些问题?

简而言之,对象通过实现 IDispatch:

:

Invoke(当然还有 IDispatch 其他部分,包括

GetIDsOfNames)处理所有这些问题。

但是让我们假定你正在实现这个对象。

你怎样实现

这些方法?

实现 IDispatch 困难的方式

如果你的方法没有任何参数(或者可能只有一个参数)和没有参数化的属性,你自己实现

IDispatch:

:

Invoke 就非常简单。

全部需要做的是一个开关语句,它为每个 dispid 和类型的组

合调用正确的函数。

(你也可以使用一个调用表。

)你将不得不把参数转换为你需要的类型,

但是调用 VariantChangeType 可以很容易的从任何类型转换变量到你需要的任何类型。

(如

果转换失败,就返回一个错误给调用者。

但是可以想象如果你有多个参数的混乱状况。

首先,未命名的参数在数组中是颠倒次序的。

如果他们被命名,顺序由数组的 dispids 决定,意味着你将不得不把它挑选出。

但是等一下

--那不是全部--你可能需要支持可选择的参数和带有缺省值可选择的参数。

给出所有的这些

的描绘是一巨大的混乱。

它不仅仅困难--你很可能在做它时产生错误。

如果你去做,在你

能处理的调用上你将多半以对一个你本来能处理的调用给出一个错误而结束---这不是在你

的用户当中鼓舞信心的一种伟大的方式。

有比较容易的方式吗?

你可以打赌一定有。

不要用困难方式做它。

让 COM 帮你做它!

如果你的服务器遇到两个相对简单的要求,你能利用 COM 的内建的 IDispatch 的实现。

求是:

·你的自动控制接口必须是一个双重接口(作为 ATL 产生的),不是一个纯粹的

dispinterface。

 

· 你必须产生和利用一个类型库告诉 COM 是哪些方法和属性。

ATL 甚至不支持引入的纯 disp 接口,所以你的选择是双重的(包括自动控制)和自定义接口。

一个双重接口和等价的常规接口之间主要的差别是双重接口派生自 IDispatch 而不是

IUnknown。

这意味着除了 QueryInterface、Release 和 AddRef 外,双重接口也必须实现所

有的 IDispatch 方法(包括 GetIDsOfNames 和 Invoke)。

正如 ATL 为你提供了 IUnknown 方法

的实现,它也提供了 IDispatch 方法的实现。

双重接口的 IDL

双重接口的 IDL 看起来非常像常规接口的 IDL。

对 BeepCnt 对象,IbeepCnt 接口的 IDL 是:

[

object,

uuid(4F74530F-3943-11D2-A2B5-00C04F8EE2AF),

dual,

helpstring("IBe

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 人文社科 > 法律资料

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1