分享
 
 
 

在COM中使用数组参数-ICollection

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

在COM中使用数组参数-ICollection

关键字:DCOM、数组、自定义类型、Marshal、SafeArray、ICollection

1 使用ICollection

ICollection是从 IDispatch继承的接口。ICollection还需要一个IEnumVARIANT接口配合实现功能。IEnumVARIANT是从IUnknown继承的,而不是从IDispatch接口继承。

ICollection接口提供了最大的面向对象的设计灵活性和可重用性。在数组指针和SafeArray方法中,数组的每个元素必须事先计算出来,并且保存在特定的数据结构中。使用ICollection接口,可以设计出动态生成的数组,就是说数组的元素在需要的时候才进行计算,以便减少内存使用并加快处理速度。

1.1 ICollection和IEnumVARIANT

ICollection接口用于定义数组对象,而IEnumVARIANT接口用于定义枚举对象。枚举对象的作用是按顺序读取数组元素,有时,通过枚举对象可以获得更高的效率。

ICollection和IEnumVARIANT的定义如下:

interface ICollection : IDispatch

{

[propget, id(DISPID_LISTITEM)] HRESULT Item(

[in] const VARIANT varIndex,

[out, retval] VARIANT *pVal);

[propget, id(DISPID_LISTCOUNT)] HRESULT Count(

[out, retval] long *pVal);

[propget, id(DISPID_COLLCOUNT)] HRESULT length(

[out, retval] long *pVal);

[propget, id(DISPID_NEWENUM), restricted, hidden]

HRESULT _NewEnum([out, retval] IUnknown* *pVal);

... // 其它方法或属性

};

interface IEnumVARIANT : IUnknown

{

HRESULT Next(

unsigned long celt,

VARIANT * rgvar,

unsigned long * pceltFetched);

HRESULT Skip(unsigned long celt);

HRESULT Reset();

HRESULT Clone(IEnumVARIANT ** ppenum);

};

有的时候,COM对象不但要实现数组功能,而且还要实现其它功能。所以,大多数时候,COM对象实现的接口是从ICollection继承来的。

通过ICollection操纵数组大体上有两种方法。一种是通过Item属性用数组下标取得元素。这种方式,每次只能取得一个元素,而且要传递下标对象,所以效率比较低下。另一种方法是通过枚举器。数组对象的枚举器通过_NewEnum属性取得。通过枚举器只能按顺序获取元素,但每次可以取得任意多的元素,所以效率较高。ICollection对象可以只实现其中的一种访问方法,也可以两种都实现。ICollection中还有一个重要属性:Count。Count属性返回数组的长度,对于无法确定长度的数组,也可以不实现Count属性。

IEnumVARIANT接口用于定义枚举器。枚举器用于顺序读取数组元素。通过Next方法,可以一次读取任意多的元素。由于枚举器只可以按顺序访问数组元素,所以Next方法不需要传递下标。Skip方法用于跳过若干元素,而不读取。Reset把当前元素设置到数组头,这样就可以重新开始枚举。Clone用于获得一个新的枚举器。两个枚举器可以互不干扰的工作。

要注意的事,可能有某些数组对象的实现方法使用不同的属性名称。实际上ICollection中的属性名称是不重要的,重要的是Dispatch ID。只要通过Dispatch ID就可以取得正确的属性。

1.2 数组对象

数组对象是实现了ICollection接口的COM对象。数组对象的使用者通过ICollection接口取得数组中的数据,而完全不需要知道数组的具体实现方式。这种设计的好处是使用数组的代码可以完全不理会数组的实现方法,而当数组的实现发生变化时,使用数组的代码可以在二进制代码上保持兼容,也就是说目标代码不用编译就可以使用。

最简单制作数组对象的方法是使用ATL的模板。CComEnumOnSTL模板用于生成实现IEnumVARIANT接口的枚举对象。当然,如果要实现数组对象的所有优点,最好自己编写数组对象的代码。

1.3 ICollection参数的IDL声明

在IDL声明中。数组对象应该声明成IDispatch *。如果是输出或输入输出参数,则应该使用双重指针。

[id(0)] GetNumber([out] IDispatch ** ppObj);

[id(1)] SetNumber([in] IDispatch * pObj);

目前,我们看到的ICollection数组都是只读的。实际上ICollection完全可以设计成可读写的数组对象,只要把ICollection的Item属性设置成可读写的就可以了。关于可读写的ICollection对象请参考相关资料。

1.4 通过ATL实现数组对象

ATL通过两个模板实现对ICollection的支持。它们就是CComEnumOnSTL和ICollectionOnSTLImpl。CComEnumOnSTL用于实现基于STL对象的枚举器。ICollectionOnSTLImpl用于实现ICollection接口。下面详细描述这两个模板的功能和用法。

1.4.1 CComEnumOnSTL

CComEnumOnSTL的定义如下:

template <class Base,

const IID* piid,

class T,

class Copy,

class CollType,

class ThreadModel = CComObjectThreadModel>

class ATL_NO_VTABLE CComEnumOnSTL :

public IEnumOnSTLImpl<Base, piid, T, Copy, CollType>,

public CComObjectRootEx< ThreadModel >

模板参数中,Base是枚举器所实现的接口,通常是IEnumVARIANT。piid是枚举器接口的IID,通常是IID_IEnumVARIANT。T是枚举器输出数值的类型,通常是VARIANT。Copy是复制类,用于将STL对象中的值转换成枚举器输出参数。CollType是用于存储数据的STL类型。ThreadModel是线程模式参数,可以是CComSingleThreadModel或CcomMultiThreadModel,缺省值是当前缺省的线程模式。

假设使用vector类保存数组元素。而vector参数是long型数据。可以通过以下方法实现枚举器。

1. 定义CollType

typedef std::vector<long> CollType;

2. 定义Copy类

Copy类用于在STL类的元素类型和枚举器类型之间进行参数转换。每个Copy类必须有三个静态函数:init、copy、destroy。Init用于初始化枚举器类、copy用于把STL元素复制到枚举器参数、destroy用于销毁枚举器参数。

下面是用于在long和VARIANT之间转换的Copy类实例。

class CopyVariantLong

{

public:

static void init(VARIANT * p)

{

VariantInit(p);

}

static HRESULT copy(VARIANT * pTo, const LONG * pFrom)

{

pTo->vt = VT_I4;

pTo->lVal = *pFrom;

return S_OK;

}

static void destroy(VARIANT * p)

{

VariantClear(p);

}

};

3. 定义枚举器

通过以上定义的类就可以方便的定义枚举器类型了。

typedef CComEnumOnSTL<IEnumVARIANT,

&IID_IEnumVARIANT,

VARIANT,

CopyVariantLong,

CollType> EnumType;

1.4.2 ICollectionOnSTLImpl

ICollectionOnSTLImpl用于帮助实现ICollection接口。ICollectionOnSTLImpl定义如下:

template <class T,

class CollType,

class ItemType,

class CopyItem,

class EnumType>

class ICollectionOnSTLImpl : public T

在ICollectionOnSTLImpl模板中,T是要实现的接口,一般会使用从ICollection继承的接口。CollType参数是用于保存数据的STL类型,这个类型应该和枚举器中的相同。ItemType是ICollection中Item属性的类型,一般是VARIANT。CopyItem是Item属性的Copy类,和枚举器中的Copy类是相同的。EnumType是枚举器的类型。

可以通过以下步骤实现ICollection接口。

1. 定义ICollection类型

typedef ICollectionOnSTLImpl<INumberCollection,

CollType,

VARIANT,

CopyVariantLong,

EnumType> CollectionType;

2. 定义数组对象

定义数组对象和定义普通ATL的COM对象是类似的。只要把IDispatchImpl中的接口参数(第一个参数)变成刚刚完成的ICollectionOnSTLImpl参数就可以了。

class ATL_NO_VTABLE CNumberCollection :

public CComObjectRootEx<CComSingleThreadModel>,

public CComCoClass<CNumberCollection, &CLSID_NumberCollection>,

public IDispatchImpl<CollectionType,

&IID_INumberCollection,

&LIBID_COLLECTIONOBJLib>

{

...

}

1.5 使用数组对象

对于通用的ICollection对象,只能够通过IDispatch访问。也就是说通过IDispatch::Invoke方法访问数组中的元素。

另一方面,ICollection对象通常指通过VARIANT类型传递数据。所以,我们也必须了解如何访问VARIANT类型的变量。

1.5.1 调用IDispatch

IDispatch是Automation中定义的接口。通过IDispatch,COM客户可以取得接口中每个方法和属性的类型、参数和返回值等信息。通过IDispatch的Invoke方法,COM客户还可以直接调用接口中的方法和属性。IDispatch的内容非常丰富,这里不可能做全面地介绍,所以指对如何通过Invoke方法调用IDispatch做一个简单的说明。

1. Invoke方法的定义

HRESULT Invoke(

DISPID dispIdMember,

REFIID riid,

LCID lcid,

WORD wFlags,

DISPPARAMS FAR* pDispParams,

VARIANT FAR* pVarResult,

EXCEPINFO FAR* pExcepInfo,

unsigned int FAR* puArgErr

);

Invoke的参数如下:

l dispIdMember:所调用的属性或方法的dispatch id

l riid:保留,必须是IID_NULL

l lcid:语言环境。一般使用LOCALE_THREAD_DEFAULT

l wFlags:可以是以下四个参数之一:

DISPATCH_METHOD方法调用

DISPATCH_PROPERTYGET()读属性

DISPATCH_PROPERTYPUT()写属性

DISPATCH_PROPERTYPUTREF()通过引用写属性

l pDispParams:参数数组

l pVarResult:返回值

l pExcepInfo:被调用方法或属性内部异常(如果发生异常)

l puArgErr:当返回DISP_E_PARAMNOTFOUND或DISP_E_TYPEMISMATCH时,返回出错的参数序号。

以下是使用Invoke的例子。下例返回一个dispatch id是DISPID_LISTCOUNT的简单参数,实际上就是数组的长度。

VARIANT varResult;

DISPPARAMS DispParams;

EXCEPINFO excepInfo;

UINT errArg;

VariantInit(&varResult);

DispParams.cArgs = 0;

DispParams.cNamedArgs = 0;

DispParams.rgdispidNamedArgs = NULL;

DispParams.rgvarg = NULL;

hr = pObj->Invoke(

DISPID_LISTCOUNT,

IID_NULL,

LOCALE_USER_DEFAULT,

DISPATCH_PROPERTYGET,

&DispParams,

&varEnum,

&excepInfo,

&errArg);

if (FAILED(hr))

{

goto CleanUp;

}

下例返回一个带参数的属性。

VARIANT varIndex;

VARIANT varResult;

DISPPARAMS DispParams;

EXCEPINFO excepInfo;

UINT errArg;

VariantInit(&varIndex);

VariantInit(&varResult);

DispParams.cArgs = 1;

DispParams.cNamedArgs = 0;

DispParams.rgdispidNamedArgs = NULL;

DispParams.rgvarg = &varIndex;

VariantClear(&varIndex);

VariantClear(&varResult);

varIndex.vt = VT_I2;

varIndex.iVal = (short) Index;

hr = pObj->Invoke(

DISPID_LISTITEM,

IID_NULL,

LOCALE_USER_DEFAULT,

DISPATCH_PROPERTYGET,

&DispParams,

&varResult,

&excepInfo,

&errArg);

if (FAILED(hr))

{

...

}

1.5.2 使用IEnumVARIANT枚举数据

要使用IEnumVARIANT枚举数据,首先必须取得IEnumVARIANT指针。取得IEnumVARIANT指针是通过ICollection的_NewEnum属性。具体操作可以参考上一节关于Invoke的说明。

在取得了IEnumVARIANT之后,就可以通过IEnumVARIANT顺序读取数组元素了。

请参考以下代码枚举数据:这段代码是将数组中的元素相加求总和。

ULONG Result = 0;

ULONG res;

while (1)

{

hr = pEnum->Next(1, &var, &res);

if (FAILED(hr))

{

goto CleanUp;

}

if (hr != S_OK || res != 1)

{

break;

}

hr = VariantChangeType(&var, &var, 0, VT_I4);

if (FAILED(hr))

{

goto CleanUp;

}

Result += var.lVal;

}

1.5.3 使用Item和Count

除了使用枚举器,还可以使用Item和Count属性读取元素。和使用枚举器相比,使用Item和Count可以随时取得任一个元素,但是速度会比使用枚举器慢。

可以参考通过Invoke读取Automation属性的方法取得数组元素。

1.5.4 VARIANT类型

在ICollection中,大量使用VARIANT数据。这里把VARIANT的使用方法总结一下:

1. 直接使用VARIANT变量

a. 定义VARIANT变量

可以直接定义VARIANT类型的变量。

VARIANT val;

b. 初始化VARIANT变量

在使用VARIANT变量之前,一定要初始化。

VariantInit(&val);

c. 设置变量值

设置变量值前如果VARIANT变量中已经有值,先要清除原有数据。

VariantClear(&val);

val.vt = VT_I4; // 设置类型

val.lVal = 10; // 设置变量值

d. 清除VARIANT变量

在使用完VARIANT变量后,要清除变量,否则会发生内存泄漏。

VariantClear(&val);

e. 动态分配VARIANT变量

如果要动态分配VARIANT变量,应该使用标准的COM内存管理函数。

标准COM内存管理函数包括CoTaskMemAlloc、CoTaskMemFree和CoTaskMemRealloc。

VARIANT * pVal;

pVal = (VARIANT *)CoTaskMemAlloc(size_of(VARIANT));

VariantInit(pVal);

pVal->vt = VT_I4;

pVal->lVal = 10;

...

VariantClear(pVal);

CoTaskMemFree(pVal);

2. 通过CComVariant使用VARIANT变量

CComVariant是ATL对于VARIANT的简单包装。通过CComVariant可以更简单的使用VARIANT,而不必担心没有进行初始化或清除。如果没有特殊情况,应该尽量使用CComVariant而不要使用VARIANT。

以下是使用CComVariant的代码实例。

CComVariant Val;

Val.vt = VT_I4;

Val.lVal = 10;

// Val 不必清除

以下是使用CComVariant数组的例子。

CComVariant * pVal;

pVal = new CComVariant[10];

for (int i = 0; i < 10; ++i)

{

pVal[i].vt = VT_I4;

pVal[I].lVal = i + 1;

}

...

delete[] pVal;

2 后记

由于时间关系,以及COM规范本身的复杂性。本文不可能面面俱到,只能起到抛砖引玉的作用。我这里有关于本文内容的实例代码,大家可以通过email索取。我的email地址是nelsonc@online.sh.cn

大家如果有什么不清楚的地方,也可以通过email探讨。如果大家想了解关于COM或dotNet的其它内容也可以告诉我。我以后会发表更多的文章,希望能对大家有所帮助。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有