COM的数据类型BSTR,Variant

COM的特性是语言中立、硬件结构中立,很明显,它需要一个语言中立、硬件结构中立的文本数据类型。
OLECHAR和BSTR就是干这个用的。
OLECHAR:在编译源代码的目标操作系统上COM使用的字符类型。
对于Win32操作系统,这是wchar_t字符类型。
对于Win16操作系统,这是char字符类型。
对于MacOS,这是char类型。
对于Solaris OS,这是wchar_t字符类型。
对于其他未知OS,没人知道是什么类型。
OLECHAR是抽象数据类型。COM使用它,不管实际映射到什么特定的底层数据类型。
很多语言,比如VBScript和jscript使用BSTR(出于性能考虑)。
BSTR是一个指向包含长度前缀的OLECHAR字符数组的指针。
BSTR是一个指针数据类型,指向数组的第一个字符,
长度前缀以整数的形式恰好存储在数组的第一个字符之前。
BSTR字符数组以NUL字符作为结尾,
长度前缀以字节为单位而不是字符,并且不包括NUL终止符。

COM的数据类型BSTR,Variant_第1张图片
字符数组内部可以包含嵌入的NUL字符。
必须使用SysAllocString和SysFreeString函数族来分配和释放。
NULL BSTR指针意味着一个空字符串。
BSTR没有引用计数,因此相同字符串内容的两个引用必须指向不同的BSTR。
换句话说,复制BSTR意味着制作字符串的一个拷贝,而不是简单地复制指针。
调用SysStringLen时,必须BSTR非NULL,也就是说,如果要获得某个bstr的长度,需要这样:
UINT uLen = bstr? SysStringLen(bstr) : 0;
VBScript和jscript内部都使用BSTR来表示字符串。
总之BSTR是包含长度前缀的OLECHAR数组。
虽然wtypes.h里面,这样定义BSTR:typedef /* [wire_marshal] */ OLECHAR __RPC_FAR *BSTR
但是,BSTR并不是OLECHAR *,能使用OLECHAR*的地方,并不一定都能够使用BSTR。
这可是很麻烦的事情,编译器认为他们一样,但他们又不一样,很容易出错,
问题就在于BSTR第一个字符是长度前缀,而OLECHAR*不是。
比如:STDMETHODIMP CMyClass::put_Name(LPCOLESTR pName);
BSTR bstrIn = ...
pObj->put_Name(bstrIn);
由于put_Name是一个LPCOLESTR,它有可能截断bstrIn(如果bstrIn可以有内嵌的NULL字符)。
再比如,需要[out]OLECHAR*参数的地方,也不能使用BSTR,否则可能会造成内存泄漏。
STDMETHODIMP CMyClass::get_Name(/* [out] */ OLECHAR ** ppName)
{
BSTR bstrOut = ...
*ppName = bstrOut;
return S_OK;
}
上面的代码中,调用者无法释放BSTR,所以就造成了内存泄漏。
同样,需要BSTR时,用OLECHAR *也会存在错误。
STDMETHODIMP CMyClasss::put_Name(BSTR bstrName);
pObj->put_Name(OLECHAR("This is a Ole Char!"));
put_Name会调用SysStringLen来获得BSTR的长度,
当试图从字符串前面的整数获取BSTR的长度时,就会出现问题。
COM的封装类CComBSTR的构造函数有CComBSTR(const OLECHAR*),
要特别注意不要在这个构造函数中用到BSTR,
如果确实需要,最好指定长度,或者先Empty(),然后再AppendBSTR()

CComBSTR
CComBSTR是一个ATL工具类,它是对BSTR的一个有效封装,头文件在atlbase.h中。
它有一个数据成员BSTR m_str;还有n多的成员函数,
这些函数的实现,也都可以查看,具体实现,用了不少技巧,但并不复杂。
在看这些过程中,我对SysAllocStringLen,SysAllocString,SysFreeString有了更深的认识。
Length函数要注意,由于SysStringLen(NULL)会出错(我的xp + vc60没有出错),
所以要这样写:
return m_str == NULL ? 0 : SysStringLen(m_str)
CComVariant
CComVariant从tagVARIANT(也就是VARIANT)继承而来。
使用COM的时候,希望在对方法所要求的数据类型一无所知的情况下,向它传递参数。
为了使方法能够解释接收到的参数,调用者必须指定数据的格式和相应的值。
格式存放在vt中,值存放在联合体变量中,具体的对应关系如下:
VT_EMPTY  没有指定值,如果可缺省的参数要放空,不能指定为VT_EMPTY,而是VT_ERROR,值为DISP_E_PARAMNOTFOUND
VT_EMPTY | VT_BYREF  不合法
VT_UI1   一个无符号一个字节长的字符,存储在bVal
VT_UI1 | VT_BYREF 一个无符号一个字节长的字符引用,存储在pbVal
其他的也都大同小异,不一一列举,单说值得注意的。
VT_NULL   传递空值,用于Sql中。
VT_NULL | VT_BYREF 不合法
VT_VARIANT  不合法,如果是VARIANT只能是引用。
VT_VARIANT | VT_BYREF 不能引用已经是VT_VARIANT | VT_BYREF的数据。
VT_ARRAY | <type> 传递type类型的数组,存储在pparray,type不能是VT_EMPTY和VT_NULL
CComBSTR和ComVariant是BSTR和VARIANT的封装,封装的还是很巧妙,
我深刻感觉的了看别人代码的重要性,很多技巧,都是模拟、剽窃来的。
Atach和Detach方法避免了额外的内存分配和Addref/Release调用。
但要注意,不要随意用Detach一个[out]参数,因为Detach时,要先清除参数,
而out参数在进入方法时,没有初始化,清除没有初始化的随机信息是非常危险的。
要彻底理解他们,就要非常熟悉BSTR和VARIANT。

你可能感兴趣的:(Solaris,null,存储,VBScript,语言,编译器)