混合模式编程中的字符串转换

在混合模式编程中,字符串的处理尤其复杂,因为需要在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功能

虽然内置功能只允许字符串转换,但也可以扩展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以处理需要上下文的对象

当有需要显式资源释放的类型转换时,需要稍微不同地处理它。例如,编写了一些代码,将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);
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485