Visual C++ 2008
的封送处理库
C++的发展总是伴随着批评的,其中最常见的就是完成同一项任务,有太多可用的方法了,想一下在Visual C++中写一个文本文件有多少种方法吧:C运行时库、标准C++库、Windows SDK I/O API、.NET API、MFC,甚至还有通过COM接口实现的FileSystemObjects,其中任意一个都能写文本文件,而每个都有各自的优势与不便之处。
同样,C++的开发者要在 .NET与COM之间进行类型转换时,也有让人无所适从的多个选择,尽管大多数开发者借助于手工转换,但手工转换效率不高,且还可能把数据降低为blittable类型(blittable数据类型是指那些在托管与本机代码中有同样内存格式的数据类型,如四字节有符号整型或Unicode字符)。当然了,还有 .NET类型Marshal,其有多个静态方法用于数据类型转换;另外,对那些用 /clr:pure 或 /clr:safe 编译的C++代码,还可使用DllImport属性来指定一个托管类型是如何转换为一个本机类型的。但在Visual C++ 2008中,引入了一种新的转换方法:C++封送处理库(Marshaling Library)。
简介
封送处理库提供了基于模板的方法,以在本机与托管数据类型之间进行转换,对于简单的转换,则不存在复杂的内存管理,数据类型之间的转换语法也非常简单:
#include <msclr/marshal.h>
TCHAR* c_style_string = _T("My C style string");
System::String^ dotNetString =
msclr::interop::marshal_as<System::String^>(c_style_string);
当返回的对象需要显式内存清理时,就需要基于上下文的封送了。在基于上下文的封送中,需要创建一个托管的marshal_context对象,并把它传递给封送方法,且封送调用的结果只在marshal_context对象的生命期中有效。以下代码是把托管 .NET字符串转换回C风格的字符串:
//声明新的marshal_context
marshal_context^ mc = gcnew marshal_context();
//把字符串从 .NET转换为 C 风格
const TCHAR* new_c_style_string = mc->marshal_as<const TCHAR*>(dotNetString);
//取转换字符串的长度
int strLen = (int)_tcslen(new_c_style_string) + 1;
//分配一个新的字符数组保存字符串
TCHAR* copy_of_new_c_style_string = new TCHAR(strLen);
//复制到新的数组中
_tcscpy_s(copy_of_new_c_style_string, strLen, new_c_style_string);
//删除封送上下文
delete mc;
当marshal_context删除后,任何在封送调用期间分配的内存都将被释放,这通常意味着,如果在marshal_context对象删除之后,还需要访问被转换的对象,就先要制作一份封送数据的副本。
Visual C++ 2008中支持多种类型的转换,主要的头文件(marshal.h)定义了System::String与C风格字符串(char* 及 wchar_t*)间最基本的封送类型及字符串转换方法;Marshal_atl.h提供从COM字符串(CComBSTR)及MFC字符串(CStringA 及 CStringW)到托管字符串的转换;marshal_cppstd.h用于标准C++字符串(std::wstring and std::string)与 .NET之间的转换;而marshal_windows.h提供托管IntPtr类型与本机HANDLE类型的转换,其还支持COM字符串类型(_bstr_t 及 BSTR)与System::String之间的转换。
扩展封送处理库
封送处理库是高度可自定义的,我们可通过扩展封送处理库,来实现未提供的功能,扩展封送处理库有两种方法——既可以使用也可以不使用 marshal_context Class。需要使用上下文的三个转换是:
System::String^ 到 const char *
System::String^到 const wchar_t*
System::String^到 BSTR
一、使用不需要上下文的转换扩展封送处理库
1、创建一个文件来存储新的封送处理函数,例如,MyMarshal.h。
2、包括以下一个或多个封送库文件:
用于基类型的 marshal.h。
用于 Windows 数据类型的 marshal_windows.h。
用于 STL 数据类型的 marshal_cppstd.h。
用于 ATL 数据类型的 marshal_atl.h。
3、编写转换函数。在代码中,TO 是要转换到的类型,FROM 是要从中转换的类型,from 是要转换的参数。
4、编写代码将 from 参数转换为 TO 类型的一个对象,并返回被转换的对象。
namespace msclr {
namespace interop {
template<>
inline TO marshal_as<TO, FROM> (const FROM& from) {
//实际的转换代码,并返回一个 TO 参数。
}
}
}
二、使用需要上下文的转换扩展封送处理库
1、创建一个文件来存储新的封送处理函数,例如,MyMarshal.h
2、包括以下一个或多个封送库文件:
用于基类型的 marshal.h。
用于 Windows 数据类型的 marshal_windows.h。
用于 STL 数据类型的 marshal_cppstd.h。
用于 ATL 数据类型的 marshal_atl.h。
3、编写转换函数。在代码中,TO 是要转换到的类型,FROM 是要从中转换的类型,toObject 是存储结果的指针,fromObject 是要转换的参数。
4、编写代码,将 toPtr 初始化为适当的空值。例如,如果是指针,则将其设置为 NULL。
5、编写代码,将 from 参数转换为 TO 类型的一个对象。此被转换的对象将存储在 toPtr 中。
6、编写代码设置 toObject,将 toObject 设置为被转换的对象。
7、编写代码清理本机资源,以释放由 toPtr 分配的任何内存。如果 toPtr 使用 new 分配了内存,请使用 delete 释放内存。
namespace msclr {
namespace interop {
template<>
ref class context_node<TO, FROM> : public context_node_base
{
private:
TO toPtr;
public:
context_node(TO& toObject, FROM fromObject)
{
//第4步:将 toPtr 初始化化为适当的空值。
//第5步:实际的转换代码。
//第6步:设置 toObject 为被转换的对象。
}
~context_node()
{
this->!context_node();
}
protected:
!context_node()
{
//第7步:清理本机资源。
}
};
}
}
扩展封送处理库相对比较简单,只需在头文件中添加一个新的封送函数,就可以像使用内置marshal_as函数一样使用新的函数了。下面以添加 .NET中的SecureString类与普通本机类型的转换为例,来进行讲解,而其他本机字符串类型的转换也与示例中的 wchar_t* 非常类似。
要转换为SecureString,封送处理的方法非常简单:
template <>
inline System::Security::SecureString^ msclr::interop::marshal_as
(wchar_t* const & _from_object)
{
if (_from_object == NULL)
{
return nullptr;
}
return gcnew System::Security::SecureString(_from_object,
(int)wcslen(_from_object));
}
代码中通过封送处理库使用了模板特化,将检查输入的参数是否为NULL,如果参数有效,会用一个重载的构造函数创建一个新的SecureString。
要转换为wchar_t* 字符串,可能稍要复杂一点了,因为涉及到了C风格字符串的内存管理。SecureString到wchar_t* 的转换需要使用上下文,这意味着必须使用继承自context_node_base的模板特化。实际的类也是比较简单的,由构造函数进行实际的转换工作,一旦marshal_context类中保存的context_node_base派生对象被删除后,析构函数就负责清理数据,另外,还声明了一个Finalize方法(在本例中析构函数会转发到此),以防止错过析构后,还能通过 .NET的Finalize队列进行内存清理。
template<>
ref class msclr::interop::context_node<wchar_t*,
System::Security::SecureString^> : public context_node_base
{
private:
System::IntPtr _ip;
public:
context_node(wchar_t*& _to_object,
System::Security::SecureString^ _from_object)
{
_ip = System::Runtime::InteropServices::Marshal::
SecureStringToGlobalAllocUnicode (_from_object);
_to_object = static_cast<wchar_t*>(_ip.ToPointer());
}
~context_node()
{
this->!context_node();
}
protected:
!context_node()
{
if(_ip != System::IntPtr::Zero)
System::Runtime::InteropServices::Marshal::FreeHGlobal(_ip);
}
};
编译器会自动探测到需要封送的转换试图使用无需上下文的封送处理的这种情况,如果下列代码被编译:
System::Security::SecureString^ mySecureString = GetSecureString(...);
wchar_t* c_style_string1 = marshal_as<wchar_t*>(mySecureString);
编译器会产生以下错误:
C:/Program Files/Microsoft Visual Studio 9.0/VC/include/msclr/
marshal.h(200) : error C4996: 'msclr::interop::
error_reporting_helper<_To_Type,_From_Type>::marshal_as':
This conversion requires a marshal_context. Please use a
marshal_context for this conversion.
with
[
_To_Type=wchar_t *,
_From_Type=System::Security::SecureString ^
]
C:/Program Files/Microsoft Visual Studio 9.0/VC/include/msclr/
marshal.h(191) : see declaration of 'msclr::interop::
error_reporting_helper<_To_Type,_From_Type>::marshal_as'
with
[
_To_Type=wchar_t *,
_From_Type=System::Security::SecureString ^
]
./myFile.cpp(19) : see reference to function template
instantiation '_To_Type msclr::interop::marshal_as
<wchar_t*,System::Security::SecureString^>
(const _From_Type &)' being compiled
with
[
_To_Type=wchar_t *,
_From_Type=System::Security::SecureString ^
]
此时编译器会强制你使用基于上下文的封送,由此防止粗心造成的内存泄漏。
结论
C++封送处理库为本机与托管类型之间的转换,提供了用户友好、可扩展、一站式的解决方案,通过使用内联模板,也使转换有了尽可能高的速度表现,把基于运行时的封送处理所进行的查找及类型安全检查的开销降至最低。C++封送处理库易于使用及扩展,而自定义的转换过程,也方便在开发者之间共享,提升了代码的复用性。