Sunday, March 27, 2005

 

Some notes on COM String Manipulation

1.
COM下的字符串处理

By heinsect
From http://www.donews.net/heinsect/archive/2005/01/18/245116.aspx

网上有一篇丁有和的《Visual C++.NET中 字符串转换方法》,对于我这号笨人来说,不是很清晰,所以重新写了一下。

一、TCHAR,LPTSTR,LPCTSTR:定义于WinNT.h

//
// Neutral ANSI/UNICODE types and macros
//
#ifdef UNICODE // r_winnt
#ifndef _TCHAR_DEFINED
typedef WCHAR TCHAR, *PTCHAR;
typedef WCHAR TBYTE , *PTBYTE ;
#define _TCHAR_DEFINED
#endif /* !_TCHAR_DEFINED */
typedef LPWSTR LPTCH, PTCH;
typedef LPWSTR PTSTR, LPTSTR;
typedef LPCWSTR PCTSTR, LPCTSTR;
typedef LPUWSTR PUTSTR, LPUTSTR;
typedef LPCUWSTR PCUTSTR, LPCUTSTR;
typedef LPWSTR LP;
#define __TEXT(quote) L##quote // r_winnt
#else /* UNICODE */ // r_winnt
#ifndef _TCHAR_DEFINED
typedef char TCHAR, *PTCHAR;
typedef unsigned char TBYTE , *PTBYTE ;
#define _TCHAR_DEFINED
#endif /* !_TCHAR_DEFINED */
typedef LPSTR LPTCH, PTCH;
typedef LPSTR PTSTR, LPTSTR, PUTSTR, LPUTSTR;
typedef LPCSTR PCTSTR, LPCTSTR, PCUTSTR, LPCUTSTR;
#define __TEXT(quote) quote // r_winnt
#endif /* UNICODE */ // r_winnt

类似的定义出现于Wtypes.h:


typedef char CHAR;
typedef /* [string] */ CHAR *LPSTR;
typedef /* [string] */ const CHAR *LPCSTR;
#ifndef _WCHAR_DEFINED
#define _WCHAR_DEFINED
typedef wchar_t WCHAR;
typedef WCHAR TCHAR;
#endif // !_WCHAR_DEFINED
typedef /* [string] */ WCHAR *LPWSTR;
typedef /* [string] */ TCHAR *LPTSTR;
typedef /* [string] */ const WCHAR *LPCWSTR;
typedef /* [string] */ const TCHAR *LPCTSTR;


注意在TCHAR.h, WinNT.h中都定义了TCHAR,它们是靠#ifdef _TCHAR_DEFINED来判断的。在VC6.0中:

#include 只在WINDEF.H中出现过。
#include 出现在 Windows.h中。
#include 、#include 出现在ATL/INCLUDE/ATLBASE.H中

TCHAR.h中重新定义了标准C库的很多字符串处理函数的宏,只要在工程中定义或取消UNICODE和_UNICODE,就可以在多字节字符串和UNICODE间互相转换。按Win32编程要求的"Think In UNICODE",TCHAR.h中的函数应该是经常用到的。

二、BSTR(BASIC String):定义于WTypes.h

#if defined(_WIN32) && !defined(OLE2ANSI)
typedef WCHAR OLECHAR;
typedef /* [string] */ OLECHAR *LPOLESTR;
typedef /* [string] */ const OLECHAR *LPCOLESTR;
# define OLESTR(str) L##str
#else
typedef char OLECHAR;
typedef LPSTR LPOLESTR;
typedef LPCSTR LPCOLESTR;
# define OLESTR(str) str
#endif
typedef /* [wire_marshal] */ OLECHAR *BSTR;


可以看到,BSTR实际上还是一个C-style的宽字符串(如果定义了OLE2ANSI,就是C字符串了,但很少用到,因为在OLE接口中传递时都是宽字符串),只不过在使用BSTR时,得用到特殊的申请/释放函数组。因为BSTR指针位置前面有一个四字节的数据,存储的是BSTR的长度。就是这个原因,我们不能把OLECHAR*当作BSTR来用,而编译器在遇到这种方式时还不报错!

在MFC程序中不用BSTR时,可以在stdafx.h中所有#include之前加一行:
#define _AFX_NO_BSTR_SUPPORT

在ATL对象的接口定义中,无论是双接口还是自定义接口,我们一般看不到TCHAR等定义,我们看到的是BSTR(双接口时的VARIANT,如果是BSTR类型,可以直接用BSTR的),这要求我们作一些处理。对于传入的参数,一般的做法是将参数赋给一个串处理类。对于传出的参数,一般的方法是内部先处理,最后将字符串复制一份传出去。

三、CComBSTR

CComBSTR是ATL库中的一个BSTR封装类,提供了一些简单的函数,支持各类字符串到BSTR的转换,但没有格式化、搜索、子串处理等基本功能。功能太过简单使得这个类可以说是一个鸡胁,只能作一个转换工具用。奇怪的是在Visaul .NET 2003版本中还是没什么大的改进,我本以为M$会把CString的所有功能都搬进去的。

在M$的《用 CComBSTR 进行编程》提到了一些使用CComBSTR时要注意的一些问题。

四、_bstr_t

_bstr_t是一个与ATL无关的COM的支持类,这个类定义在comutil.h文件中(同时还定义有一个_variant_t类)。这个类也是一个功能与CComBSTR类似,主要用于转换是,也是一个功能不怎么强的类。

在comutil.h还定义了两个函数用于在标准C字符串与BSTR间的转换,可以说这是个底层函数。程序员对TCHAR作一次分解就可以写出TCHAR适用的程序了。

namespace _com_util {
// Convert char * to BSTR
//
BSTR __stdcall ConvertStringToBSTR(const char* pSrc) ;
// Convert BSTR to char *
//
char* __stdcall ConvertBSTRToString(BSTR pSrc) ;
}

五、CString/CStringA/CStringW

CString在VC6.0以前是MFC的一个类,要想在COM中使用,就得加上那个900K的MFC42.DLL。可能是MFC下的CString太好用了(:p,不要打我),所以M$干脆把这个类搬到ATL中。在Visual C++.NET中CStringT(在CStringT.h中实现)成为ATL的一个模板方式的类,只是将以前在MFC中的方法搬了过来;而CString定义在afxstr.h中:

// MFC-enabled compilation. Use MFC memory management and exceptions;
// also, use MFC module state.
// Don't import when MFC dll is being built
#if !defined(_WIN64) && defined(_AFXDLL)
#if defined(_MFC_DLL_BLD)
template class ATL::CSimpleStringT< char, true >;
template class ATL::CStringT< char, StrTraitMFC_DLL< char > >;
template class ATL::CSimpleStringT< wchar_t, true >;
template class ATL::CStringT< wchar_t, StrTraitMFC_DLL< wchar_t > >;
#else
template class __declspec(dllimport) ATL::CSimpleStringT< char, true >;
template class __declspec(dllimport) ATL::CStringT< char, StrTraitMFC_DLL< char > >;
template class __declspec(dllimport) ATL::CSimpleStringT< wchar_t, true >;
template class __declspec(dllimport) ATL::CStringT< wchar_t, StrTraitMFC_DLL< wchar_t > >;
#if defined(_NATIVE_WCHAR_T)
template class __declspec(dllimport) ATL::CSimpleStringT< unsigned short, true >;
template class __declspec(dllimport) ATL::CStringT< unsigned short, StrTraitMFC_DLL< unsigned short > >;
#endif // _NATIVE_WCHAR_T
#endif // _MFC_DLL_BLD
typedef ATL::CStringT< wchar_t, StrTraitMFC_DLL< wchar_t > > CStringW;
typedef ATL::CStringT< char, StrTraitMFC_DLL< char > > CStringA;
typedef ATL::CStringT< TCHAR, StrTraitMFC_DLL< TCHAR > > CString;
#else
typedef ATL::CStringT< wchar_t, StrTraitMFC< wchar_t > > CStringW;
typedef ATL::CStringT< char, StrTraitMFC< char > > CStringA;
typedef ATL::CStringT< TCHAR, StrTraitMFC< TCHAR > > CString;
#endif // !_WIN64 && _AFXDLL


关于ATL7.0中CString的用法,在M$的《自 Visual C++ 6.0 以来 ATL 7.0 和 MFC 7.0 中的重大更改》中有提到:从 BSTR 转换到 CString

在 Visual C++ 6.0 中,使用下面的代码是可以接受的:

BSTR bstr = SysAllocString(L"Hello");
CString str = bstr;
SysFreeString(bstr);对于 Visual C++ .NET 下的新项目,这将在 ANSI 版本下导致下面的错误error C2440: 'initializing' : cannot convert from 'BSTR' to
'ATL::CStringT'
现在有 CString 的 UNICODE 和 ANSI 版本(CStringW 和 CStringA)。若要标记任何由隐式转换导致的不必要的系统开销,采用反向类型(如带 UNICODE 参数的 CStringA,或者带 ANSI 参数的 CStringW)的构造函数现在在 stdafx.h 中被使用下面的项标记为显式的:

#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS

若要避免此错误,请执行下列操作之一:

使用 CStringW 以避免转换:
BSTR bstr = SysAllocString(L"Hello");
CStringW str = bstr;
SysFreeString(bstr);

显式调用该构造函数:
BSTR bstr = SysAllocString(L"Hello");
CString str = CString(bstr);
SysFreeString(bstr);

从 stdafx.h 中移除 #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS 行。

在VC6.0及以前,很多程序员都做过将MFC中的CString,这种在网上很多。我看到的一个很完整的是gosh的gd::CString,网址是http://www.codeproject.com/soap/serialize_xml.asp。据这位老兄自己说,他自己还没有VC 6.0编译器。

六、std::string和std::wstring

std::string在我看来是std::vector。但这个类可以说是一个有史以来计算机程序员对字符串处理类的一个总结,它的算法应该是没得说的。

七、字符串(类)间的转换

以上各类字符串间的转换从概念上有两种方式,第一种是已经有的转换接口,另一种是基于某一个中间格式作转换,其实第一种实现也是基于第二种的。

char 与 wchar_t 间的转换:
简单地说,是两个Win32函数:WideCharToMultiByte和MultiByteToWideChar。当然中间还涉及到代码页和线程代码页等参数。如果用标准C函数wctomb和mbtowc也可以。

在M$的《自Visual C++6.0以来ATL7.0和MFC7.0中的重大更改》中有提到:字符串转换

Visual C++ 6.0 中 ATL 3.0 和 ATL 3.0 以前的 ATL 版本中,使用 atlconv.h 中的宏的字符串转换始终是使用系统 (CP_ACP) 的 ANSI 代码页执行的。从 Visual C++ .NET 中的 ATL 7.0 开始,字符串转换将使用当前线程的默认 ANSI 代码页执行,除非定义了 _CONVERSION_DONT_USE_THREAD_LOCALE(此情况下,如以前一样使用系统的 ANSI 代码页)。

请注意,字符串转换类(如 CW2AEX)使您得以将用于转换的代码页传递给它们的构造函数。如果未指定代码页,这些类使用与宏相同的代码页。

有关更多信息,请参见 ATL 和 MFC 字符串转换宏。

COM程序中,接口一般都是BSTR,所以无论在编写COM组件时还是使用组件时,转为BSTR和从BSTR转出都可能遇到。如果全部采用UNICODE,麻烦要少一些,如果使用到了char或std::string,麻烦就不少了。但一般而言,我们有以下方法可以用:

在接口(组件)内部,全部用UNICODE处理。将传入的BSTR转换为std::wstring、gd:CString或是其它的什么类,在内部成员变量声明时用同样的类,内部处理时不用管外面,生成结果时用一个CComBST或是_bstr_t类作一次转换就可以了。

在接口外部,调用COM组件的接口函数前,将字符串用_bstr_t或者comutil.h的函数作一个转换,传入组件进行处理;传出时,作一个反向的转换。

非COM程序中,接口一般用TCHAR*,这种方式的代价最小。



<< Home

This page is powered by Blogger. Isn't yours?