在混合模式编程中,字符串的处理尤其复杂,因为需要在MFC字符串、COM字符串、标准C++字符串和CLR字符串之间进行转换。为了解决这一问题,编写了一个名为StringConvertor的类,用于管理非托管字符串的转换。尽管意识到将"converter"拼写为"convertor"是一个错误,但决定保留这个错误,以便获得一个独特的类名,从而避免在Google搜索中的混淆(尤其是因为有许多其他名为StringConverter的类,尤其是基于Java的)。
在Visual C++的Orcas版本中,VC++团队增加了一个混合模式的封送库,主要包括marshal_as模板函数。虽然技术上marshal_as不限于字符串转换,但目前它最重要的角色就是字符串转换。使用它与使用C++的类型转换运算符(如static_cast)非常相似,但请注意,这里实际上不是在进行类型转换,而是在进行转换,这可能与纯粹的类型转换有所不同。
以下代码片段展示了如何将各种原生字符串转换为System::String^。
String^ clrString;
const char* pcszHello = "hello world";
clrString = marshal_as(pcszHello);
wchar_t* pwszHello = L"hello wide world";
clrString = marshal_as(pwszHello);
bstr_t bstrtHello("hello bstr_t world");
clrString = marshal_as(bstrtHello);
std::string stdHello = "hello from std::string";
clrString = marshal_as(stdHello);
CString mfcString("hello from CString");
clrString = marshal_as(mfcString);
CComBSTR atrBSTR(L"hello from CComBSTR");
clrString = marshal_as(atrBSTR);
反向转换也类似。
String^ clrString = "Original System::String";
std::string stdHello = marshal_as(clrString);
CString mfcString = marshal_as(clrString);
CComBSTR atrBSTR = marshal_as(clrString);
不能直接使用marshal_as将String^转换为const char*、const wchar_t*或BSTR,因为这些转换需要在使用后释放非托管资源。对于这些情况,需要使用上下文对象,如下所示。
marshal_context context;
const char* pcszHello = context.marshal_as(clrString);
const wchar_t* pcwszHello = context.marshal_as(clrString);
BSTR bstrString = context.marshal_as(clrString);
Console::WriteLine(context._clean_up_list.Count);
上下文对象会跟踪分配的对象,并在其析构函数中释放它们。它内部维护一个分配对象的链表,上述代码中的Console::WriteLine输出将是3(因为使用上下文对象分配了三个对象)。最初,觉得这样做有点烦人,但想不到任何更优雅的替代方案,可以避免使用上下文对象。实际上,在MC++ StringConvertor类中,内部使用了一个std::vector来存储所有分配的对象,以便在析构函数中释放它们。
虽然内置功能只允许字符串转换,但也可以扩展marshal_as的功能来支持其他类型的转换。例如,编写了一个示例扩展,支持在Windows Forms的Rectangle结构和Win32的RECT结构之间进行转换。为了完整性,还添加了MFC的CRect包装器(它是RECT结构的薄包装器)的特化。这是扩展(放入一个单独的头文件):
namespace msclr {
namespace interop {
template<>
System::Drawing::Rectangle marshal_as(
const RECT& from) {
return System::Drawing::Rectangle(from.left, from.top,
from.right - from.left, from.bottom - from.top);
}
template<>
System::Drawing::Rectangle marshal_as(
const CRect& from) {
return System::Drawing::Rectangle(from.left, from.top,
from.Width(), from.Height());
}
template<>
RECT marshal_as(
const System::Drawing::Rectangle& from) {
System::Drawing::Rectangle rectangle = from;
RECT rect = {rectangle.Left, rectangle.Top,
rectangle.Right, rectangle.Bottom};
return rect;
}
template<>
CRect marshal_as(
const System::Drawing::Rectangle& from) {
System::Drawing::Rectangle rectangle = from;
return CRect(rectangle.Left, rectangle.Top,
rectangle.Right, rectangle.Bottom);
}
}
}
为所需的四种转换添加了四个特化。注意是如何将转换放入msclr::interop命名空间的。这样做是为了让和其他人像使用常规转换一样使用marshal_as。
现在使用这些转换非常简单,如下所示。
RECT rect = {10, 10, 110, 110};
System::Drawing::Rectangle rectangle = marshal_as(rect);
CRect mfcRect(20, 20, 220, 220);
rectangle = marshal_as(mfcRect);
RECT rectBack = marshal_as(rectangle);
CRect mfcRectBack = marshal_as(rectangle);
需要注意的一个重要事项是,在添加的所有四种转换中,都不需要处理上下文,因为没有显式需要内存释放。这可能并不总是这样,正如在下一节中将看到的。
当有需要显式资源释放的类型转换时,需要稍微不同地处理它。例如,编写了一些代码,将marshal_as扩展到支持.NET的Font对象和原生Windows的HFONT结构之间的转换。
namespace msclr {
namespace interop {
template<>
ref class context_node : public context_node_base {
private:
System::Drawing::Font^ _font;
public:
context_node(System::Drawing::Font^% to, HFONT from) {
to = _font = System::Drawing::Font::FromHfont((IntPtr)from);
}
~context_node() {
this->!context_node();
}
protected:
!context_node() {
delete _font;
}
};
template<>
ref class context_node : public context_node_base {
private:
HFONT _hFont;
public:
context_node(HFONT& to, System::Drawing::Font^ from) {
to = _hFont = (HFONT)from->ToHfont().ToPointer();
}
~context_node() {
this->!context_node();
}
protected:
!context_node() {
DeleteObject(_hFont);
}
};
}
}
添加了两个context_node模板类的特化,这些类由marshal_context类用来处理需要上下文的转换。在构造函数中,创建了请求的对象,并在析构函数/终结器中释放了资源。对于HFONT,调用了DeleteObject,而对于Font,调用了delete(它调用Dispose)。对于MC++ Font,不必这样做,因为Font终结器最终会起作用,但对于GDI对象,最好在不需要时立即释放它们,因为它们在内存方面相当重。
使用这些转换与使用marshal_as转换const char*或BSTR(也需要上下文对象)的方式非常相似。
HFONT hFont = CreateSampleFont();
//...
marshal_context context;
System::Drawing::Font^ font = context.marshal_as(hFont);
HFONT hFontCopy = context.marshal_as(font);
//...
DeleteObject(hFont);