注册 登录
Office中国论坛/Access中国论坛 返回首页

ganlinlao的个人空间 http://www.office-cn.net/?230471 [收藏] [复制] [分享] [RSS]

日志

Freebasic菜鸟初学Freebasic基础教程九:了解com数据类型

已有 3765 次阅读2017-5-11 10:34 |个人分类:FreeBasic| FreeBasic教程, Freebasic入门, Freebasic基础, FreeBasic教程, Freebasic入门

FB在windows上使用,有两种东西是无法回避的,一种是api,一种是com,这是几乎所有在windows上使用的语言都必须用到的。
这是引用自csdn上的博客内容:
了解一下com数据类型,有助于在FB中使用com。

BSTR、
BSTR到底是什么。

BSTR是COM中的数据类型,在COM编程时,接口中定义的字符串类型都是BSTR类型,

而使用BSTR类型是极其容易出错的,同时,一不小心就有可能造成内存泄露。所以有如下建议:

  1. 在对接口进行实现时,从接口参数中传递过来的BSTR类型的变量,一定要在第一时刻将BSTR类型转变成_bstr_t类型的变量,就是因为BSTR有隐藏的危险,同时当使用BSTR出现bug时,而这种bug而不一定好找;
  2. 在对接口进行实现时,接口中的BSTR字符串参数是out时,在接口实现的内部不要定义BSTR类型变量,而是定义_bstr_t类型的变量,进行操作,操作完成以后,然后在转为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);
INT     
SysReAllocString(BSTR* pbstr,const OLECHAR* psz);
BSTR   
SysAllocStringLen(const OLECHAR * strIn,UINTui);
INT     
SysReAllocStringLen(BSTR* pbstr,constOLECHAR* psz,unsignedintlen);
void   SysFreeString
(BSTR bstrString);
 
UINT  SysStringLen(BSTR);
UINT  SysStringByteLen(BSTR bstr);
BSTR
SysAllocStringByteLen(LPCSTRpsz,UINTlen);

现在对以上的
API逐一的进行讲解和应用,大家可以结合着MSDN,看这篇博文,至少MSDN讲的比我这里更详细。
SysAllocString
分配内存,并创建BSTR字符串;
SysReAllocString
重新分配内存,并将第二个参数指定的OLECHAR同时放入新开辟的的内存中;
SysAllocStringLen
分配内存,将第一个参数指定的字符串的前ui(第二个参数指定的字符个数)个数放入开辟的内存中;
 
SysReAllocStringLen,经过上面两个API的讲解,这个API的就不需要更多的讲解了;
SysFreeString释放由以上的API函数开辟的内存;
SysStringLen
表示的是BSTR的字符个数;
SysStringByteLen
获得BSTR字符串表示的字节数,也就是BSTR的前4个字节表示的内容;
SysAllocStringByteLen
使用的是ANSI string进行创建BSTR对象,尽量少用该APIVARIANT

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 nameDescription
VT_EMPTYIndicates that a value was not specified.
VT_NULLIndicates a null value, similar to a null value in SQL.
VT_I2Indicates a short integer.
VT_I4Indicates a long integer.
VT_R4Indicates a float value.
VT_R8Indicates a double value.
VT_CYIndicates a currency value.
VT_DATEIndicates a DATE value.
VT_BSTRIndicates a BSTR string.
VT_DISPATCHIndicates an IDispatch pointer.
VT_ERRORIndicates an SCODE.
VT_BOOLIndicates a Boolean value.
VT_VARIANTIndicates a VARIANT far pointer.
VT_UNKNOWNIndicates an IUnknown pointer.
VT_DECIMALIndicates a decimal value.
VT_I1Indicates a char value.
VT_UI1Indicates a byte .
VT_UI2Indicates an unsigned short .
VT_UI4Indicates an unsigned long .
VT_I8Indicates a 64-bit integer.
VT_UI8Indicates an 64-bit unsigned integer.
VT_INTIndicates an integer value.
VT_UINTIndicates an unsigned integer value.
VT_VOIDIndicates a C style void .
VT_HRESULTIndicates an HRESULT.
VT_PTRIndicates a pointer type.
VT_SAFEARRAYIndicates a SAFEARRAY. Not valid in a VARIANT.
VT_CARRAYIndicates a C style array.
VT_USERDEFINEDIndicates a user defined type.
VT_LPSTRIndicates a null-terminated string.
VT_LPWSTRIndicates a wide string terminated by null Nothing nullptr a null reference (Nothing in Visual Basic) .
VT_RECORDIndicates a user defined type.
VT_FILETIMEIndicates a FILETIME value.
VT_BLOBIndicates length prefixed bytes.
VT_STREAMIndicates that the name of a stream follows.
VT_STORAGEIndicates that the name of a storage follows.
VT_STREAMED_OBJECTIndicates that a stream contains an object.
VT_STORED_OBJECTIndicates that a storage contains an object.
VT_BLOB_OBJECTIndicates that a blob contains an object.
VT_CFIndicates the clipboard format.
VT_CLSIDIndicates a class ID.
VT_VECTORIndicates a simple, counted array.
VT_ARRAYIndicates a SAFEARRAY pointer.
VT_BYREFIndicates that a value is a reference.

VARIANT使用起来是很简单的,但是有些问题,我们还必须要去注意:

  • 建立VARIANT变量时,必须使用VariantInit进行初始化
  • 对于VT_UI1, VT_I2, VT_I4, VT_R4, VT_R8, VT_BOOL, VT_ERROR, VT_CY, VT_DECIMAL, 和VT_DATE这些类型,
  • 数据的值是直接储存在VARIANT结构中的,当VARIANT的类型发生变化时,指向这些数据的指针会变得无效。
  • 例如: VARIANT varParam;::VariantInit(&varParam);short iVal =20; varParam.vt = VT_I2;
  • varParam.iVal = iVal;short*pVal =&varParam.iVal; varParam.vt = VT_I4; varParam.lVal =200;
  • 此时,*pVal指向的值与你期望的可能是不一样的。
  • 对于VT_BYREF | any type数据类型,而VARIANT只是拥有这些数据指向内存的指针,而内存的释放需要由函数的调用者负责。
  • 例如: VARIANT varParam;::VariantInit(&varParam);int*p =newint;*p =10; varParam.vt = VT_BYREF | VT_I4;
  • varParam.plVal =(long*)p;delete p;// If you donnot delete the memory, this may cause memory leak
  • 对于VT_BSTR类型,在VARIANT中的字符串必须要用SysAllocString进行分配内存,当释放时,或者VARIANT的类型发生改变时,
  • 都需要调用SysFreeString进行内存释放,否则就会发生内存泄露;
  • 对于VT_ARRAY | any type类型,这个规则和VT_BSTR是类似的,在VARIANT中的数组必须使用SafeArrayCreate进行开辟内存空间,
  • 然后必须使用SafeArrayDestroy进行内存空间的释放;
  • 对于VT_DISPATCH和VT_UNKNOWN,我们考虑的更多的就是引用计数器的增加与减少了,
  • 是的,在进行赋值时需要进行引用计数的增加,释放时,则需要对应的减少。
SAFEARRAY的使用

SAFEARRAY的主要目的是用于automation中的数组型参数的传递,我们都知道,在网络环境中,数组是不能直接传递的,

所以我们必须将数组封装成SAFEARRAY类型,这样才能进行传递,在COM编程时,SAFEARRAY类型是可以存放在VARIANT类型中,

指定vt为VT_ARRAY|*或者VT_BYREF|VT_ARRAY。对于SAFEARRAY,说白了,就是普通的数组,添加了一些额外的说明,

当我第一次遇到这个类型时,也是有点恐惧的,后来,用惯了,也就无所谓了。SAFEARRAY单独用的时候很少,就像我前面说的,

一般都是搭配着VARIANT一起使用,指定vt类型以后,parray成员就是指向SAFEARRAY的指针。SAFEARRAY中元素的类型可以

是VARIANT能封装的任何类型,包括VARIANT类型本身。

访问SAFEARRAY

访问SAFEARRAY的方法大体上有两种:

  1. 使用SafeArrayAccessData方法;
  2. 使用SafeArrayGetElement和SafeArrayPutElement方法。

关于这两种方法,在上面的例子中都有涉及。

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语言中,我们可以如下定义:

  1. long Add( long n1, long n2 )  
  2. {  
  3. return n1 + n2;  
  4. }  

还记得刚才我们说的原则吗?COM 组件是运行在分布式环境中的。也就是说,这个函数可能运行在“地球另一边”的计算机上,既然运行在那么遥远的地方,就有可能出现服务器关机、网络掉线、运行超时、对方不在服务区......等异常。于是,这个加法函数,除了需要返回运算结果以外,还应该返回一个值------函数是否被正常执行了。

  1. HRESULT Add( long n1, long n2, long *pSum )  
  2. {  
  3. 3*pSum = n1 + n2;  
  4.  return S_OK;  
  5. }  

如果函数正常执行,则返回 S_OK,同时真正的函数运行结果则通过参数指针返回。如果遇到了异常情况,则COM系统经过判断,会返回相应的错误值。常见的返回值有:

HRESULT含义
S_OK0x00000000成功
S_FALSE0x00000001函数成功执行完成,但返回时出现错误
E_INVALIDARG0x80070057参数有错误
E_OUTOFMEMORY0x8007000E内存申请错误
E_UNEXPECTED0x8000FFFF未知的异常
E_NOTIMPL0x80004001未实现功能
E_FAIL0x80004005没有详细说明的错误。一般需要取得 Rich Error 错误信息(注1)
E_POINTER0x80004003无效的指针
E_HANDLE0x80070006无效的句柄
E_ABORT0x80004004终止操作
E_ACCESSDENIED0x80070005访问被拒绝
E_NOINTERFACE0x80004002不支持接口

图一、HRESULT 的结构

HRESULT 其实是一个双字节的值,其最高位(bit)如果是0表示成功,1表示错误。具体参见 MSDN 之"Structure of COM Error Codes"说明。我们在程序中如果需要判断返回值,则可以使用比较运算符号;switch开关语句;也可以使用VC提供的宏:

  1. HRESULT hr = 调用组件函数;  
  2. if( SUCCEEDED( hr ) ){...} // 如果成功  
  3. ......  
  4. if( FAILED( hr ) ){...} // 如果失败  
  5. ...... 


评论 (0 个评论)

facelist doodle 涂鸦板

您需要登录后才可以评论 登录 | 注册

QQ|站长邮箱|小黑屋|手机版|Office中国/Access中国 ( 粤ICP备10043721号-1 )  

GMT+8, 2024-4-26 05:04 , Processed in 0.063560 second(s), 17 queries .

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

返回顶部