键字: 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终止符。 字符数组内部可以包含嵌入的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。 详细出处参考:http://www./content-detail/188530_2.html |
|