65.9K
CodeProject 正在变化。 阅读更多。
Home

C++ 字符串完整指南,第二部分 - 字符串包装类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (154投票s)

2002年10月7日

19分钟阅读

viewsIcon

1588561

关于 Visual C++ 和类库提供的字符串包装器类的指南

引言

由于 C 风格字符串容易出错且难以管理,更不用说成为寻找缓冲区溢出漏洞的黑客的目标,因此有许多字符串包装类。不幸的是,在某些情况下,并非总是清楚应该使用哪个类,也不知道如何将 C 风格字符串转换为包装类。

本文涵盖了 Win32 API、MFC、STL、WTL 和 Visual C++ 运行时库中的所有字符串类型。我将描述每个类的用法、如何构造对象以及如何转换为其他类。Nish 还贡献了 Visual C++ 7 中托管字符串和类的部分。

为了充分利用本文,你**必须**理解我在第一部分中介绍的不同字符类型和编码。

字符串类的规则 #1

**强制转换是糟糕的**,除非它们有明确的文档说明。

促使我写这两篇文章的原因是关于如何将字符串类型 X 转换为类型 Z 的常见问题,发帖人使用了强制转换却不明白为什么代码不起作用。各种字符串类型,尤其是`BSTR`,在任何一个地方都没有简洁的文档,所以我想有些人会随意使用强制转换,并希望它能起作用。

强制转换**不会**对字符串进行任何转换,**除非**源字符串是一个具有明确文档说明的转换运算符的包装类。对字符串字面量进行强制转换对字符串没有任何作用,所以写出类似这样的代码

void SomeFunc ( LPCWSTR widestr );
 
main()
{
  SomeFunc ( (LPCWSTR) "C:\\foo.txt" );  // WRONG!
}

将百分之百失败。它会编译,因为强制转换会覆盖编译器的类型检查。但仅仅因为它编译了,并不意味着代码是正确的。

在下面的示例中,我将指出何时强制转换是合法的。

C 风格字符串和 typedef

正如我在第一部分中介绍的,Windows API 是根据`TCHAR`定义的,它可以是 MBCS 或 Unicode 字符,具体取决于你在编译时是否定义了`_MBCS`或`_UNICODE`符号。你应该参考第一部分以获取`TCHAR`的完整描述,但为了方便,我将在此处列出字符 typedef。

类型

含义

WCHAR

Unicode 字符(`wchar_t`)

TCHAR

MBCS 或 Unicode 字符,取决于预处理器设置

LPSTR

`char`字符串(`char*`)

LPCSTR

`char`常量字符串(`const char*`)

LPWSTR

`WCHAR`字符串(`WCHAR*`)

LPCWSTR

`WCHAR`常量字符串(`const WCHAR*`)

LPTSTR

`TCHAR`字符串(`TCHAR*`)

LPCTSTR

`TCHAR`常量字符串(`const TCHAR*`)

另外一种字符类型是`OLECHAR`。它代表了自动化接口中使用的字符类型(例如 Word 公开的接口,以便你可以操作文档)。这种类型通常定义为`wchar_t`,但是如果你定义了`OLE2ANSI`预处理器符号,`OLECHAR`将被定义为`char`类型。我知道现在没有理由定义`OLE2ANSI`(自 MFC 3 时代以来,微软就没有使用过它),所以从现在开始我将把`OLECHAR`视为 Unicode 字符。

以下是你将看到的与`OLECHAR`相关的 typedef

类型

含义

OLECHAR

Unicode 字符(`wchar_t`)

`OLECHAR`字符串(`OLECHAR*`)

LPOLESTR

`OLECHAR`常量字符串(`const OLECHAR*`)

LPCOLESTR

还有两个宏用于字符串和字符字面量,以便同一代码可以用于 MBCS 和 Unicode 构建

类型

含义

_T(x)

在 Unicode 构建中,在字面量前加上`L`。

OLESTR(x)

在字面量前加上`L`,使其成为`LPCOLESTR`。

你可能在文档或示例代码中遇到`_T`的变体。有*四个*等效宏——`TEXT`、`_TEXT`、`__TEXT`和`__T`——它们都做同样的事情。

COM 中的字符串 - BSTR 和 VARIANT

许多自动化和其他 COM 接口使用 `BSTR` 来处理字符串,并且 `BSTR` 有一些陷阱,因此我将在这里专门介绍 `BSTR`。

`BSTR` 是 Pascal 风格字符串(长度与数据一起明确存储)和 C 风格字符串(字符串长度必须通过查找终止零字符来计算)的混合体。`BSTR` 是一个 Unicode 字符串,其长度前置,并且也以零字符终止。以下是“Bob”作为 `BSTR` 的示例

06 00 00 00

 42 00

 6F 00

 62 00

 00 00

--长度--

B

o

b

EOS

请注意字符串的长度是如何前置于字符串数据的。它是一个`DWORD`,保存字符串中的*字节数*,*不包括*终止零。在这种情况下,“Bob”包含 3 个 Unicode 字符(不包括终止零),总共 6 个字节。长度字段的存在是为了当`BSTR`在进程或计算机之间封送时,COM 库知道要传输多少数据。(顺便说一句,`BSTR`可以包含任意数据块,而不仅仅是字符,并且可以包含嵌入的零字符。但是,出于本文的目的,我将不考虑这种情况。)

C++ 中的 `BSTR` 变量实际上是指向字符串第一个字符的指针。事实上,类型 `BSTR` 是这样定义的

  typedef OLECHAR* BSTR;

这是非常不幸的,因为实际上`BSTR`与 Unicode 字符串**不同**。该 typedef 禁用了类型检查,允许你随意混合`LPOLESTR`和`BSTR`。将`BSTR`传递给期望`LPCOLESTR`(或`LPCWSTR`)的函数是安全的,但是反之则不然。因此,重要的是要了解函数期望的字符串的确切类型,并传递正确类型的字符串。

要理解为什么将`LPCWSTR`传递给期望`BSTR`的函数是不安全的,请记住字符串前面的四个字节必须存储其长度。`LPCWSTR`没有这样的长度。如果`BSTR`需要封送到另一个进程(例如,你正在控制的 Word 实例),COM 库将查找该长度并找到垃圾,或者堆栈上的其他变量,或者其他随机数据。这可能会导致方法失败,甚至在感知到的长度太长时导致崩溃。

有几个 API 可以操作`BSTR`,但其中最重要的两个是创建和销毁`BSTR`的函数。它们是`SysAllocString()`和`SysFreeString()`。`SysAllocString()`将 Unicode 字符串复制到`BSTR`中,而`SysFreeString()`释放`BSTR`使用的内存。

BSTR bstr = NULL;
 
  bstr = SysAllocString ( L"Hi Bob!" );
 
  if ( NULL == bstr )
    // out of memory error
 
  // Use bstr here...
 
  SysFreeString ( bstr );

当然,各种`BSTR`包装类会为你处理内存管理。

自动化接口中使用的另一种类型是 `VARIANT`。它用于在 JScript 和 VBScript 等无类型语言以及某些情况下的 Visual Basic 之间发送数据。`VARIANT` 可以包含多种不同类型的数据,例如 `long` 和 `IDispatch*`。当 `VARIANT` 包含字符串时,它以 `BSTR` 形式存储。稍后在介绍 `VARIANT` 包装类时,我将详细讨论 `VARIANT`。

字符串包装类

既然我已经介绍了各种字符串类型,我将演示包装类。对于每个类,我将展示如何构造一个对象以及如何将其转换为 C 风格的字符串指针。C 风格指针通常是 API 调用或构造不同字符串类的对象所必需的。我将不涉及类提供的其他运算符,例如排序或比较。

再次强调,**不要**盲目地转换对象,除非你完全理解所生成的代码将做什么。

CRT 提供的类

_bstr_t

`_bstr_t`是`BSTR`的完整包装,实际上它隐藏了底层`BSTR`。它提供了各种构造函数,以及访问底层 C 风格字符串的运算符。但是,没有运算符可以直接访问`BSTR`本身,因此`_bstr_t`不能作为`[out]`参数传递给 COM 方法。如果你需要一个`BSTR*`作为参数,使用 ATL 类`CComBSTR`更容易。

一个`_bstr_t`*可以*传递给一个接受`BSTR`的函数,但这只是由于三个巧合。首先,`_bstr_t`有一个到`wchar_t*`的转换函数;其次,`wchar_t*`和`BSTR`在编译器看来是相同的,因为`BSTR`的定义;第三,`_bstr_t`内部保留的`wchar_t*`指向的内存块遵循`BSTR`格式。所以即使没有文档化的`BSTR`转换,它也恰好有效。

// Constructing
_bstr_t bs1 = "char string";       // construct from a LPCSTR
_bstr_t bs2 = L"wide char string"; // construct from a LPCWSTR
_bstr_t bs3 = bs1;                 // copy from another _bstr_t
_variant_t v = "Bob";
_bstr_t bs4 = v;                   // construct from a _variant_t that has a string
 
// Extracting data
LPCSTR psz1 = bs1;              // automatically converts to MBCS string
LPCSTR psz2 = (LPCSTR) bs1;     // cast OK, same as previous line
LPCWSTR pwsz1 = bs1;            // returns the internal Unicode string
LPCWSTR pwsz2 = (LPCWSTR) bs1;  // cast OK, same as previous line
BSTR    bstr = bs1.copy();      // copies bs1, returns it as a BSTR
 
  // ...
  SysFreeString ( bstr );

请注意,`_bstr_t`也有用于`char*`和`wchar_t*`的转换运算符。这是一种有问题的设计,因为即使这些是非常量字符串指针,你也不得使用这些指针修改缓冲区,因为这可能会破坏内部`BSTR`结构。

_variant_t

`_variant_t`是`VARIANT`的完整包装,它提供了许多构造函数和转换函数,以操作`VARIANT`可以包含的众多类型。我将仅在此处介绍与字符串相关的操作。

// Constructing
_variant_t v1 = "char string";       // construct from a LPCSTR
_variant_t v2 = L"wide char string"; // construct from a LPCWSTR
_bstr_t bs1 = "Bob";
_variant_t v3 = bs1;                 // copy from a _bstr_t object
 
// Extracting data
_bstr_t bs2 = v1;           // extract BSTR from the VARIANT
_bstr_t bs3 = (_bstr_t) v1; // cast OK, same as previous line

请注意,如果无法进行类型转换,`_variant_t`方法可能会抛出异常,因此请准备好捕获`_com_error`异常。

另请注意,没有从`_variant_t`到 MBCS 字符串的直接转换。你需要创建一个临时的`_bstr_t`变量,使用另一个提供 Unicode 到 MBCS 转换的字符串类,或者使用 ATL 转换宏。

与`_bstr_t`不同,`_variant_t`可以直接作为参数传递给 COM 方法。`_variant_t`派生自`VARIANT`类型,因此根据 C++ 语言规则,允许将`_variant_t`替换`VARIANT`进行传递。

STL 类

STL 只有一个字符串类,`basic_string`。`basic_string`管理一个以零结尾的字符数组。字符类型在`basic_string`模板参数中给出。通常,`basic_string`应被视为不透明对象。你可以获取内部缓冲区的只读指针,但任何写入操作都必须使用`basic_string`的运算符和方法。

有`basic_string`的两种预定义特化:`string`,它包含`char`,和`wstring`,它包含`wchar_t`。没有内置的`TCHAR`特化,但你可以使用下面列出的那个。

// Specializations
typedef basic_string<TCHAR> tstring; // string of TCHARs
 
// Constructing
string str = "char string";         // construct from a LPCSTR
wstring wstr = L"wide char string"; // construct from a LPCWSTR
tstring tstr = _T("TCHAR string");  // construct from a LPCTSTR
 
// Extracting data
LPCSTR psz = str.c_str();    // read-only pointer to str's buffer
LPCWSTR pwsz = wstr.c_str(); // read-only pointer to wstr's buffer
LPCTSTR ptsz = tstr.c_str(); // read-only pointer to tstr's buffer

与`_bstr_t`不同,`basic_string`无法直接在字符集之间进行转换。但是,如果构造函数接受字符类型,你可以将`c_str()`返回的指针传递给另一个类的构造函数,例如

// Example, construct _bstr_t from basic_string
_bstr_t bs1 = str.c_str();  // construct a _bstr_t from a LPCSTR
_bstr_t bs2 = wstr.c_str(); // construct a _bstr_t from a LPCWSTR

ATL 类

CComBSTR

`CComBSTR`是 ATL 的`BSTR`包装器,在某些情况下比`_bstr_t`更有用。最值得注意的是,`CComBSTR`允许访问底层`BSTR`,这意味着你可以将`CComBSTR`对象传递给 COM 方法,并且`CComBSTR`对象会自动为你管理`BSTR`内存。例如,假设你想调用此接口的方法

// Sample interface:
struct IStuff : public IUnknown
{
  // Boilerplate COM stuff omitted...
  STDMETHOD(SetText)(BSTR bsText);
  STDMETHOD(GetText)(BSTR* pbsText);
};

`CComBSTR`有一个`operator BSTR`方法,因此可以直接传递给`SetText()`。还有一个`operator &`,它返回一个`BSTR*`,因此你可以在`CComBSTR`对象上使用`&`运算符将其传递给接受`BSTR*`的函数。

CComBSTR bs1;
CComBSTR bs2 = "new text";
 
  pStuff->GetText ( &bs1 );       // ok, takes address of internal BSTR
  pStuff->SetText ( bs2 );        // ok, calls BSTR converter
  pStuff->SetText ( (BSTR) bs2 ); // cast ok, same as previous line

`CComBSTR`具有与`_bstr_t`类似的构造函数,但是没有内置的 MBCS 字符串转换器。为此,你可以使用 ATL 转换宏。

// Constructing
CComBSTR bs1 = "char string";       // construct from a LPCSTR
CComBSTR bs2 = L"wide char string"; // construct from a LPCWSTR
CComBSTR bs3 = bs1;                 // copy from another CComBSTR
CComBSTR bs4;
 
  bs4.LoadString ( IDS_SOME_STR );  // load string from string table
 
// Extracting data
BSTR bstr1 = bs1;        // returns internal BSTR, but don't modify it!
BSTR bstr2 = (BSTR) bs1; // cast ok, same as previous line
BSTR bstr3 = bs1.Copy(); // copies bs1, returns it as a BSTR
BSTR bstr4;
 
  bstr4 = bs1.Detach();  // bs1 no longer manages its BSTR
 
  // ...
  SysFreeString ( bstr3 );
  SysFreeString ( bstr4 );

请注意,在最后一个例子中使用了`Detach()`方法。调用该方法后,`CComBSTR`对象不再管理其`BSTR`或相关内存。这就是为什么`bstr4`上需要调用`SysFreeString()`。

附注:`operator &`重载意味着你不能直接在某些 STL 集合(例如`list`)中使用`CComBSTR`。集合要求`&`运算符返回一个指向所包含类的指针,但对`CComBSTR`应用`&`会返回`BSTR*`,而不是`CComBSTR*`。但是,有一个 ATL 类可以解决这个问题,`CAdapt`。例如,要创建一个`CComBSTR`列表,可以这样声明

  std::list< CAdapt<CComBSTR> > bstr_list;

`CAdapt`提供了集合所需的运算符,但它对你的代码是不可见的;你可以像使用`CComBSTR`列表一样使用`bstr_list`。

CComVariant

`CComVariant`是`VARIANT`的包装。然而,与`_variant_t`不同,`VARIANT`并未隐藏,事实上你需要直接访问`VARIANT`的成员。`CComVariant`提供了许多构造函数来操作`VARIANT`可以包含的众多类型。我将仅在此处介绍与字符串相关的操作。

// Constructing
CComVariant v1 = "char string";       // construct from a LPCSTR
CComVariant v2 = L"wide char string"; // construct from a LPCWSTR
CComBSTR bs1 = "BSTR bob";
CComVariant v3 = (BSTR) bs1;          // copy from a BSTR
 
// Extracting data
CComBSTR bs2 = v1.bstrVal;            // extract BSTR from the VARIANT

与`_variant_t`不同,没有到各种`VARIANT`类型的转换运算符。如上所示,你必须直接访问`VARIANT`成员并确保`VARIANT`包含你期望类型的数据。如果需要将`CComVariant`的数据转换为`BSTR`,可以调用`ChangeType()`方法。

CComVariant v4 = ... // Init v4 from somewhere
CComBSTR bs3;
 
  if ( SUCCEEDED( v4.ChangeType ( VT_BSTR ) ))
    bs3 = v4.bstrVal;

与`_variant_t`一样,没有直接转换为 MBCS 字符串的方法。你需要创建一个临时的`_bstr_t`变量,使用另一个提供 Unicode 到 MBCS 转换的字符串类,或者使用 ATL 转换宏。

ATL 转换宏

ATL 的字符串转换宏是字符编码之间转换的非常便捷的方式,在函数调用中特别有用。它们根据`[源类型]2[新类型]`或`[源类型]2C[新类型]`的方案命名。使用第二种形式命名的宏转换为常量指针(因此名称中带有“C”)。类型缩写是

A:MBCS 字符串,`char*`(A 代表 ANSI)
W:Unicode 字符串,`wchar_t*`(W 代表宽)
T:`TCHAR`字符串,`TCHAR*`
OLE:`OLECHAR`字符串,`OLECHAR*`(实际上等同于 W)
BSTR:`BSTR`(仅用作目标类型)

因此,例如,`W2A()`将 Unicode 字符串转换为 MBCS 字符串,而`T2CW()`将`TCHAR`字符串转换为常量 Unicode 字符串。

要使用宏,首先包含 atlconv.h 头文件。即使在非 ATL 项目中也可以这样做,因为该头文件不依赖于 ATL 的其他部分,也不需要`_Module`全局变量。然后,当你在函数中使用转换宏时,在函数开头放置`USES_CONVERSION`宏。这定义了一些宏使用的局部变量。

当目标类型不是`BSTR`时,转换后的字符串存储在堆栈上,因此如果希望字符串比当前函数存在更长时间,则需要将字符串复制到另一个字符串类中。当目标类型*是*`BSTR`时,内存不会自动释放,因此必须将返回值分配给`BSTR`变量或`BSTR`包装类,以避免内存泄漏。

以下是一些显示各种转换宏的示例

// Functions taking various strings:
void Foo ( LPCWSTR wstr );
void Bar ( BSTR bstr );
// Functions returning strings:
void Baz ( BSTR* pbstr );
 
#include <atlconv.h>
 
main()
{
using std::string;
USES_CONVERSION;    // declare locals used by the ATL macros
 
// Example 1: Send an MBCS string to Foo()
LPCSTR psz1 = "Bob";
string str1 = "Bob";
 
  Foo ( A2CW(psz1) );
  Foo ( A2CW(str1.c_str()) );
 
// Example 2: Send a MBCS and Unicode string to Bar()
LPCSTR psz2 = "Bob";
LPCWSTR wsz = L"Bob";
BSTR bs1;
CComBSTR bs2;
 
  bs1 = A2BSTR(psz2);         // create a BSTR
  bs2.Attach ( W2BSTR(wsz) ); // ditto, assign to a CComBSTR
 
  Bar ( bs1 );
  Bar ( bs2 );
 
  SysFreeString ( bs1 );      // free bs1 memory
  // No need to free bs2 since CComBSTR will do it for us.
 
// Example 3: Convert the BSTR returned by Baz()
BSTR bs3 = NULL;
string str2;
 
  Baz ( &bs3 );          // Baz() fills in bs3
 
  str2 = W2CA(bs3);      // convert to an MBCS string
  SysFreeString ( bs3 ); // free bs3 memory
}

如你所见,当你在一种格式中拥有字符串而函数需要不同的格式时,这些宏在向函数传递参数时非常方便。

MFC 类

CString

MFC `CString` 保存 `TCHAR`,因此确切的字符类型取决于你定义的预处理器符号。通常,`CString` 类似于 STL `string`,你应将其视为不透明对象,并仅使用 `CString` 方法修改它。`CString` 相对于 STL `string` 的一个优点是它具有接受 MBCS 和 Unicode 字符串的构造函数,并且它具有到 `LPCTSTR` 的转换器,因此你可以直接将 `CString` 对象传递给接受 `LPCTSTR` 的函数;无需调用 `c_str()` 方法。

// Constructing
CString s1 = "char string";  // construct from a LPCSTR
CString s2 = L"wide char string";  // construct from a LPCWSTR
CString s3 ( ' ', 100 );  // pre-allocate a 100-byte buffer, fill with spaces
CString s4 = "New window text";
 
  // You can pass a CString in place of an LPCTSTR:
  SetWindowText ( hwndSomeWindow, s4 );
 
  // Or, equivalently, explicitly cast the CString:
  SetWindowText ( hwndSomeWindow, (LPCTSTR) s4 );

你还可以从字符串表中加载字符串。有一个`CString`构造函数可以做到这一点,以及`LoadString()`。`Format()`方法也可以选择从字符串表中读取格式字符串。

// Constructing/loading from string table
CString s5 ( (LPCTSTR) IDS_SOME_STR );  // load from string table
CString s6, s7;
 
  // Load from string table.
  s6.LoadString ( IDS_SOME_STR );
 
  // Load printf-style format string from the string table:
  s7.Format ( IDS_SOME_FORMAT, "bob", nSomeStuff, ... );

那个第一个构造函数看起来很奇怪,但它实际上就是这样记录的加载字符串的方式。

请注意,你可以应用于`CString`的**唯一**合法转换是转换为`LPCTSTR`。转换为`LPTSTR`(即非`const`指针)是错误的。养成将`CString`转换为`LPTSTR`的习惯只会伤害自己,因为当代码稍后确实崩溃时,你可能不明白为什么,因为你在其他地方使用了相同的代码并且它恰好起作用了。获取缓冲区的非`const`指针的正确方法是`GetBuffer()`方法。

以正确用法为例,考虑在列表控件中设置项目文本的情况

CString str = _T("new text");
LVITEM item = {0};
 
  item.mask = LVIF_TEXT;
  item.iItem = 1;
  item.pszText = (LPTSTR)(LPCTSTR) str; // WRONG!
  item.pszText = str.GetBuffer(0);      // correct
 
  ListView_SetItem ( &item );
  str.ReleaseBuffer();  // return control of the buffer to str

`pszText`成员是一个`LPTSTR`,一个非`const`指针,因此你需要在`str`上调用`GetBuffer()`。`GetBuffer()`的参数是你希望`CString`为缓冲区分配的最小长度。如果出于某种原因你想要一个足够大以容纳 1K `TCHAR`的可修改缓冲区,你将调用`GetBuffer(1024)`。将 0 作为长度传递只是返回一个指向字符串当前内容的指针。

上面划掉的那一行会编译,甚至会在*这种情况下*工作。但这并不意味着代码是正确的。通过使用非`const`强制转换,你打破了面向对象的封装,并对`CString`的内部实现做出了假设。如果你养成这种强制转换的习惯,你最终会遇到代码中断的情况,你会想知道为什么它不起作用,因为你在其他地方都使用了相同的代码并且它(似乎)有效。

你知道人们总是抱怨现在的软件多么多 bug 吗?Bug 是由程序员编写不正确的代码引起的。你真的想编写你*知道*是错误的代码,从而助长“所有软件都有 bug”这种观念吗?花时间学习使用`CString`的正确方法,让你的代码百分之百有效。

`CString`还有两个函数可以从`CString`内容创建`BSTR`,必要时转换为 Unicode。它们是`AllocSysString()`和`SetSysString()`。除了`SetSysString()`接受的`BSTR*`参数外,它们的工作方式相同。

// Converting to BSTR
CString s5 = "Bob!";
BSTR bs1 = NULL, bs2 = NULL;
 
  bs1 = s5.AllocSysString();
  s5.SetSysString ( &bs2 );
 
  // ...
  SysFreeString ( bs1 );
  SysFreeString ( bs2 );

COleVariant

`COleVariant`与`CComVariant`非常相似。`COleVariant`派生自`VARIANT`,因此可以传递给接受`VARIANT`的函数。然而,与`CComVariant`不同,`COleVariant`只有一个`LPCTSTR`构造函数。没有单独的`LPCSTR`和`LPCWSTR`构造函数。在大多数情况下这不是问题,因为你的字符串很可能都是`LPCTSTR`,但这是一个需要注意的地方。`COleVariant`还有一个接受`CString`的构造函数。

// Constructing
CString s1 = _T("tchar string");
COleVariant v1 = _T("Bob"); // construct from an LPCTSTR
COleVariant v2 = s1; // copy from a CString

与`CComVariant`一样,你必须直接访问`VARIANT`成员,如果需要将`VARIANT`转换为字符串,可以使用`ChangeType()`方法。但是,`COleVariant::ChangeType()`如果失败,会抛出异常,而不是返回失败的`HRESULT`代码。

// Extracting data
COleVariant v3 = ...; // fill in v3 from somewhere
BSTR bs = NULL;
 
  try
    {
    v3.ChangeType ( VT_BSTR );
    bs = v3.bstrVal;
    }
  catch ( COleException* e )
    {
    // error, couldn't convert
    }
 
  SysFreeString ( bs );

WTL 类

CString

WTL 的`CString`行为与 MFC 的`CString`完全相同,因此请参阅上面 MFC`CString`的描述。

CLR 和 VC 7 类

`System::String`是 .NET 中用于处理字符串的类。在内部,`String`对象包含一个不可变的字符序列。任何声称操作`String`对象的`String`方法实际上都会返回一个新的`String`对象,因为原始`String`是不可变的。`String`的一个特殊之处在于,如果你有多个包含相同字符序列的`String`,它们实际上都指向同一个对象。C++ 的托管扩展有一个新的字符串字面量前缀`S`,用于表示托管字符串字面量。

// Constructing
String* ms = S"This is a nice managed string";

你可以通过传递非托管字符串来构造`String`对象,但这比通过传递托管字符串来构造`String`对象的效率稍低。这是因为所有相同的`S`前缀字符串实例都代表同一个对象,但对于非托管字符串则不是这样。以下代码将阐明这一点

String* ms1 = S"this is nice";
String* ms2 = S"this is nice";
String* ms3 = L"this is nice";
 
  Console::WriteLine ( ms1 == ms2 ); // prints true
  Console::WriteLine ( ms1 == ms3);  // prints false

比较可能未使用`S`前缀字符串创建的字符串的正确方法是使用`String::CompareTo()`方法,如下所示

  Console::WriteLine ( ms1->CompareTo(ms2) );
  Console::WriteLine ( ms1->CompareTo(ms3) );

以上两行都将打印 0,这意味着字符串相等。

在`String`和 MFC 7 `CString`之间转换很容易。`CString`有一个到`LPCTSTR`的转换器,`String`有两个接受`char*`和`wchar_t*`的构造函数,因此你可以直接将`CString`传递给`String`构造函数。

CString s1 ( "hello world" );
String* s2 ( s1 );  // copy from a CString

反向转换类似

String* s1 = S"Three cats";
CString s2 ( s1 );

这可能会让你有点困惑,但它之所以有效,是因为从 VS.NET 开始,`CString`有一个接受`String`对象的构造函数

  CStringT ( System::String* pString );

为了进行一些快速操作,你可能有时想访问底层字符串

String* s1 = S"Three cats";
 
  Console::WriteLine ( s1 );

const __wchar_t __pin* pstr = PtrToStringChars(s1);
 
  for ( int i = 0; i < wcslen(pstr); i++ )
    (*const_cast<__wchar_t*>(pstr+i))++;
 
  Console::WriteLine ( s1 );

`PtrToStringChars()`返回一个`const __wchar_t*`到底层字符串,我们需要将其固定下来,否则垃圾回收器在操作其内容时可能会在内存中移动字符串。

将字符串类与 printf 风格的格式化函数一起使用

在使用字符串包装类与`printf()`或任何以`printf()`方式工作的函数时,必须仔细注意。这包括`sprintf()`及其变体,以及`TRACE`和`ATLTRACE`宏。由于没有对函数的附加参数进行类型检查,因此必须小心只传递 C 风格字符串指针,而不是完整的字符串对象。

因此,例如,要将`_bstr_t`中的字符串传递给`ATLTRACE()`,你**必须**明确地写出`(LPCSTR)`或`(LPCWSTR)`强制转换

_bstr_t bs = L"Bob!";
 
  ATLTRACE("The string is: %s in line %d\n", (LPCSTR) bs, nLine);

如果你忘记强制转换并传递整个`_bstr_t`对象,则跟踪消息将显示无意义的输出,因为推入堆栈的将是`_bstr_t`变量保留的任何内部数据。

所有类摘要

两种字符串类之间转换的通常方法是,获取源字符串,将其转换为 C 风格字符串指针,然后将该指针传递给目标类型中的构造函数。因此,这里有一个图表,显示了如何将字符串转换为 C 风格指针,以及哪些类可以从 C 风格指针构造。

字符串
type

转换
转为`char*`?

转换为
`const char*`?

转换为
`wchar_t*`?

转换为
`const wchar_t*`?

转换
转为`BSTR`?

构造
来自`char*`?

构造
来自`wchar_t*`?

_bstr_t

BSTR

是,强制转换1

是,强制转换

是,强制转换1

是,强制转换

2

_variant_t

BSTR

强制转换为
`_bstr_t`3

强制转换为
`_bstr_t`3

字符串

MBCS

是,`c_str()`
method

wstring

Unicode

是,`c_str()`
method

CComBSTR

BSTR

是,强制转换
转为`BSTR`

是,强制转换

CComVariant

BSTR

4

4

CString

TCHAR

6

在 MBCS
构建中,强制转换

6

在 Unicode
构建中,强制转换

5

COleVariant

BSTR

4

4

在 MBCS 构建中

在 Unicode 构建中

1尽管`_bstr_t`提供了到非`const`指针的转换运算符,但如果超出缓冲区,修改底层缓冲区可能会导致 GPF,或者在释放`BSTR`内存时导致内存泄漏。
2一个`_bstr_t`内部以`wchar_t*`变量形式保存一个`BSTR`,因此你可以使用`const wchar_t*`转换器来检索`BSTR`。这是一个实现细节,因此请谨慎使用,因为它将来可能会改变。
3如果数据无法转换为`BSTR`,这将抛出异常。
4使用`ChangeType()`然后访问`VARIANT`的`bstrVal`成员。在 MFC 中,如果数据无法转换,这将抛出异常。
5没有`BSTR`转换函数,但是`AllocSysString()`方法返回一个新的`BSTR`。
6你可以使用`GetBuffer()`方法临时获取一个非`const`的`TCHAR`指针。

© . All rights reserved.