||
BSTR是COM中的数据类型,在COM编程时,接口中定义的字符串类型都是BSTR类型,
而使用BSTR类型是极其容易出错的,同时,一不小心就有可能造成内存泄露。所以有如下建议:
既然是这样,就有人要问了,那么BSTR存在的必要是什么?
这个是由COM决定的,由于COM是跨系统及不同开发语言间实现互操作的技术,常规以NULL结尾的简单字符串在COM组件间传递不太方便。
所以,BSTR就这么出现了。BSTR作为指针类型,标准的BSTR是一个有长度前缀和NULL结束符的OLECHAR数组。BSTR的前4个字节是一个
表示字符串长度的前缀。BSTR长度域的值是字符串的字节数,但不包括字符串结束符。BSTR实际上包含的是Unicode串,
所以字符数是字节数的一半。
所以,在能不使用BSTR的情况下,就尽量不要使用BSTR类型,而是使用对应的_bstr_t类型。为了处理BSTR,Microsoft提供了以下API供使用:
BSTR SysAllocString(const OLECHAR * psz);VARIANT结构体主要是使用在COM(组件对象模型)中用于传递参数使用,
它的存在主要是为了保持一个在COM参数传递方法的统一性,它几乎包含了所有普通常用类型的数据类型的传递,
如整型,浮点型,布尔型等等,以及相应类型的指针类型,如整型指针。
它的使用也比较方便。先来看看这个结构体它的结构:
typedef struct tagVARIANT {
union {
struct __tagVARIANT {
VARTYPE vt;
WORD wReserved1;
WORD wReserved2;
WORD wReserved3;
union {
LONGLONG llVal;
LONG lVal;
BYTE bVal;
SHORT iVal;
FLOAT fltVal;
DOUBLE dblVal;
VARIANT_BOOL boolVal;
_VARIANT_BOOL bool;
SCODE scode;
CY cyVal;
DATE date;
BSTR bstrVal;
IUnknown *punkVal;
IDispatch *pdispVal;
SAFEARRAY *parray;
BYTE *pbVal;
SHORT *piVal;
LONG *plVal;
LONGLONG *pllVal;
FLOAT *pfltVal;
DOUBLE *pdblVal;
VARIANT_BOOL *pboolVal;
_VARIANT_BOOL *pbool;
SCODE *pscode;
CY *pcyVal;
DATE *pdate;
BSTR *pbstrVal;
IUnknown **ppunkVal;
IDispatch **ppdispVal;
SAFEARRAY **pparray;
VARIANT *pvarVal;
PVOID byref;
CHAR cVal;
USHORT uiVal;
ULONG ulVal;
ULONGLONG ullVal;
INT intVal;
UINT uintVal;
DECIMAL *pdecVal;
CHAR *pcVal;
USHORT *puiVal;
ULONG *pulVal;
ULONGLONG *pullVal;
INT *pintVal;
UINT *puintVal;
struct __tagBRECORD {
PVOID pvRecord;
IRecordInfo *pRecInfo;
} __VARIANT_NAME_4;
} __VARIANT_NAME_3;
} __VARIANT_NAME_2;
DECIMAL decVal;
} __VARIANT_NAME_1;
} VARIANT, *LPVARIANT, VARIANTARG, *LPVARIANTARG;
这个结构体呢,有5个成员,分别是 VARTYPE vt ,WORD wReserved1,WORD wReserved2,WORD wReserved3,和最后一个共用体。
其中vt用以指明最后一个共用体中哪一个成员有效,wReserved1,wReserved2,wReserved3,这三个我们使用的时候不用管,系统保留,
最后一个共用体根据vt的提示,对相应的成员进行值的存储。我们从两个不同的角度来理解,首先是使用VARIANT来存储参数,
首先是声明一个这个结构体的对象,然后对对象的vt进行赋值,它可接受的值是一个枚举值,也就说只能在枚举这个范围内取值,
比如我要用VARIANT传递一个整数,现在我对vt的赋值为VT_INT,这样就说明了我要使用这个结构体中共用体的整型变量,
接着对INT变量进行赋值,赋我们要传递的值。这样就完成VARIANT的传递。现在我们从另外一个角度来理解VARIANT,
刚才是我们对VARIANT对象进行赋值传递,现在我们是这个VARIANT对象的接收者,我们从参数中获得这个对象之后,
我们首先检查这个结构体的vt成员,看它哪个类型的变量有效,比如就这个例子而言,检查到vt的值是VT_INT,
因此,我直接去获取这个结构体中VT_INT所对应的变量,获取它的值。这样,我们从传递到使用两个角度来理解了VARIANT结构体,
概括起来说,就是vt指明了我要传递的变量的类型,结构体中共用体的成员用来存储vt指明的类型的值。
下面来看看具体VARIANT结构体是如何使用赋值的,首先是第一种方法:
首先我们声明一个VARIANT结构体的对象vr1,然后使用VariantInit函数对其进行初始化,它的作用就是对vt赋VT_EMPTY,
对别的变量值附空,否则就是一个随机值,这个过程和我们声明一个int变量一样,如果声明的时候不初始化,就是一个随机值。
编程过程中,不管是指针还是什么变量,都应该在声明之后对其进行初始化。接着就是我赋VT_INT给vt,
这里的V_VT就是代表要对vr1结构体中的vt成员进行幅值,接着对vr1中的INT成员赋值,这里的V_INT就是表示要对INT赋值,
因此这里有一个规律,就是V_,它的后面加成员类型就可以对相应的成员赋值,而这里的成员类型有一个对照表,在文章的最后给出,
比如,我要对BSTR成员赋值,我就用V_BSTR。这时候,就可以使用vr1了,使用完成之后,我们应该调用VariantClear函数,
这个函数的作用就是将vt赋值为VT_EMPTY,以及释放使用这个结构体中的内存中的内容,如果是com对象,该函数是不会进行对象的release操作的,
不管怎么样,我们都应该在使用完了VARIANT结构体之后,调用这个函数。
下面是第二种方法,原理和过程和地中方法类似,有一点微小的区别,
就是VARIANT结构体的赋值上面来说,有点不同,如下:
接下来是字符串的操作,BSTR,我们不能直接将字符串传递一个VARIANT结构体对象,而是要用到函数SysAllocString,
它的返回值就是一个BSTR,由它分配一个字符串,供VARIANT,对于为什么要这么做,可以自己查看MSDN COM的Automation那部分:
完成之后,我们还应该调用SysFreeString来释放由SysAllocString分配的内存。
另外,我们可以在类型与变量的对照表中发现,同一类型,对应了两种不同的变量,如,INT对应了变量有intVal和pintVal,其实这很简单,后者是指针性,如果要使用,说明我们赋值的对象是一个指针,如下:
最后给出类型与变量的对照表,如果是使用地中方法用V_加类型,就直接使用下表中VT_后的名称就可以了:
Member name | Description |
---|---|
VT_EMPTY | Indicates that a value was not specified. |
VT_NULL | Indicates a null value, similar to a null value in SQL. |
VT_I2 | Indicates a short integer. |
VT_I4 | Indicates a long integer. |
VT_R4 | Indicates a float value. |
VT_R8 | Indicates a double value. |
VT_CY | Indicates a currency value. |
VT_DATE | Indicates a DATE value. |
VT_BSTR | Indicates a BSTR string. |
VT_DISPATCH | Indicates an IDispatch pointer. |
VT_ERROR | Indicates an SCODE. |
VT_BOOL | Indicates a Boolean value. |
VT_VARIANT | Indicates a VARIANT far pointer. |
VT_UNKNOWN | Indicates an IUnknown pointer. |
VT_DECIMAL | Indicates a decimal value. |
VT_I1 | Indicates a char value. |
VT_UI1 | Indicates a byte . |
VT_UI2 | Indicates an unsigned short . |
VT_UI4 | Indicates an unsigned long . |
VT_I8 | Indicates a 64-bit integer. |
VT_UI8 | Indicates an 64-bit unsigned integer. |
VT_INT | Indicates an integer value. |
VT_UINT | Indicates an unsigned integer value. |
VT_VOID | Indicates a C style void . |
VT_HRESULT | Indicates an HRESULT. |
VT_PTR | Indicates a pointer type. |
VT_SAFEARRAY | Indicates a SAFEARRAY. Not valid in a VARIANT. |
VT_CARRAY | Indicates a C style array. |
VT_USERDEFINED | Indicates a user defined type. |
VT_LPSTR | Indicates a null-terminated string. |
VT_LPWSTR | Indicates a wide string terminated by null Nothing nullptr a null reference (Nothing in Visual Basic) . |
VT_RECORD | Indicates a user defined type. |
VT_FILETIME | Indicates a FILETIME value. |
VT_BLOB | Indicates length prefixed bytes. |
VT_STREAM | Indicates that the name of a stream follows. |
VT_STORAGE | Indicates that the name of a storage follows. |
VT_STREAMED_OBJECT | Indicates that a stream contains an object. |
VT_STORED_OBJECT | Indicates that a storage contains an object. |
VT_BLOB_OBJECT | Indicates that a blob contains an object. |
VT_CF | Indicates the clipboard format. |
VT_CLSID | Indicates a class ID. |
VT_VECTOR | Indicates a simple, counted array. |
VT_ARRAY | Indicates a SAFEARRAY pointer. |
VT_BYREF | Indicates that a value is a reference. |
VARIANT使用起来是很简单的,但是有些问题,我们还必须要去注意:
SAFEARRAY的主要目的是用于automation中的数组型参数的传递,我们都知道,在网络环境中,数组是不能直接传递的,
所以我们必须将数组封装成SAFEARRAY类型,这样才能进行传递,在COM编程时,SAFEARRAY类型是可以存放在VARIANT类型中,
指定vt为VT_ARRAY|*或者VT_BYREF|VT_ARRAY。对于SAFEARRAY,说白了,就是普通的数组,添加了一些额外的说明,
当我第一次遇到这个类型时,也是有点恐惧的,后来,用惯了,也就无所谓了。SAFEARRAY单独用的时候很少,就像我前面说的,
一般都是搭配着VARIANT一起使用,指定vt类型以后,parray成员就是指向SAFEARRAY的指针。SAFEARRAY中元素的类型可以
是VARIANT能封装的任何类型,包括VARIANT类型本身。
访问SAFEARRAY访问SAFEARRAY的方法大体上有两种:
关于这两种方法,在上面的例子中都有涉及。
HRESULT 函数返回值
每个人在做程序设计的时候,都有他们各自的哲学思想。拿函数返回值来说,就有好多种形式。
函数 | 返回值 | 返回值信息 |
double sin(double) | 浮点数值 | 计算正玄值 |
BOOL DeleteFile(LPCTSTR) | 布尔值 | 文件删除是否成功。如失败,需要GetLastError()才能取得失败原因 |
void * malloc(size_t) | 内存指针 | 内存申请,如果失败,返回空指针 NULL |
LONG RegDeleteKey(HKEY,LPCTSTR) | 整数 | 删除注册表项。0表示成功,非0失败,同时这个值就反映了失败的原因 |
UINT DragQueryFile(HDROP,UINT,LPTSTR,UINT) | 整数 | 取得拖放文件信息。以不同的参数调用,则返回不同的含义: 一会儿表示文件个数,一会儿表示文件名长度,一会儿表示字符长度 |
...... ...... | ... | ...... ...... |
如此纷繁复杂的返回值,如此含义多变的返回值,使得大家在学习和使用的过程中,增加了额外的困难。好了,COM 的设计规范终于对他们进行了统一。组件API及接口指针中,除了IUnknown::AddRef()和IUnknown::Release()两个函数外,其它所有的函数,都以 HRESULT 作为返回值。大家想象一个组件的接口函数比如叫Add(),完成2个整数的加法运算,在C语言中,我们可以如下定义:
还记得刚才我们说的原则吗?COM 组件是运行在分布式环境中的。也就是说,这个函数可能运行在“地球另一边”的计算机上,既然运行在那么遥远的地方,就有可能出现服务器关机、网络掉线、运行超时、对方不在服务区......等异常。于是,这个加法函数,除了需要返回运算结果以外,还应该返回一个值------函数是否被正常执行了。
如果函数正常执行,则返回 S_OK,同时真正的函数运行结果则通过参数指针返回。如果遇到了异常情况,则COM系统经过判断,会返回相应的错误值。常见的返回值有:
HRESULT | 值 | 含义 |
S_OK | 0x00000000 | 成功 |
S_FALSE | 0x00000001 | 函数成功执行完成,但返回时出现错误 |
E_INVALIDARG | 0x80070057 | 参数有错误 |
E_OUTOFMEMORY | 0x8007000E | 内存申请错误 |
E_UNEXPECTED | 0x8000FFFF | 未知的异常 |
E_NOTIMPL | 0x80004001 | 未实现功能 |
E_FAIL | 0x80004005 | 没有详细说明的错误。一般需要取得 Rich Error 错误信息(注1) |
E_POINTER | 0x80004003 | 无效的指针 |
E_HANDLE | 0x80070006 | 无效的句柄 |
E_ABORT | 0x80004004 | 终止操作 |
E_ACCESSDENIED | 0x80070005 | 访问被拒绝 |
E_NOINTERFACE | 0x80004002 | 不支持接口 |
图一、HRESULT 的结构
HRESULT 其实是一个双字节的值,其最高位(bit)如果是0表示成功,1表示错误。具体参见 MSDN 之"Structure of COM Error Codes"说明。我们在程序中如果需要判断返回值,则可以使用比较运算符号;switch开关语句;也可以使用VC提供的宏:
|站长邮箱|小黑屋|手机版|Office中国/Access中国 ( 粤ICP备10043721号-1 )
GMT+8, 2024-11-25 07:42 , Processed in 0.119712 second(s), 17 queries .
Powered by Discuz! X3.3
© 2001-2017 Comsenz Inc.