平台调用中的数据封送
文章目录
内容整理自精通.NET互操作
字符串的封送
C风格字符及字符串重定义对照
类型 | 对何种类型的重定义(typedef) | 说明 |
---|---|---|
WCHAR | wchar_ t | Unicode字符 |
TCHAR | 与平台相关的字符,可以是char或wchar, t | ANSI, MBCS或Unicode字符 |
LPSTR | char* | 以null终止的ANSI或MBCS字符串 |
LPCSTR | const char* | 以null终止的ANSI或MBCS字符串常量 |
LPWSTR | WCHAR- | 以null终止的Unicode字符串 |
LPCWSTR | const WCHAR* | 以null终止的Unicode字符串常量 |
LPTSTR | TCHAR* | 以null终止的TCHAR字符串 |
LPCTSTR | const TCHAR | 以null终止的TCHAR字符串常量 |
OLECHAR | wchar t | Unicode字符 |
LPOLESTR | OLECHAR* | OLECHAR字符串 |
LPCOLESTR | const OLECHART | OLECHAR字符串常量 |
C++字符串及类型对照
字符串类 | 字符串类型 | 可以转化为何种字符串 |
---|---|---|
bstr t | BSTR | const wchar _t* |
_variant t | BSTR | ANSI、MBCS或Unicode字符 |
string | MBCS | const char* (调用c_str()方法) |
wstring | Unicode | const wchar_t*中(调用e_ str()方法) |
CComBSTR | BSTR | const wchar_t*中或BSTR |
CComVariant | BSTR | const wchar_t*或BSTR (使用ChangeType().然后获取VARIANT的成员bstrVal) |
CString | TCHAR | 在ANSI或MBCS下,可以转化为const char*;在Unicode下,可以转化为const wchar_ t* |
COleVariant | BSTR | const wchar_t*或BSTR (使用ChangeType().然后获取VARIANT的成员bstrVal) |
封送作为参数的字符串
需要注意两点:
- 非托管函数采用的字符串类型
- 参数传递方向(传入参数还是传出参数)
通过CharSet字段及方向属性控制字符串封送处理行为
输入使用String
不要通过引用传递StringBuilder
非托管函数使用Unicode时使用StringBuilder,因为如果非托管函数使用Ansi编码,CLR会进行复制和编码转换,会降低性能,最佳做法是将StringBuilder作为Unicode字符的LPARRAY或做为LPWSTR封送
使用MarshalAS属性控制字符串的封送行为
marshalAS属性只影响其作用的字符串参数的封送处理方式
枚举类型 | 非托管格式说明 |
---|---|
UnmanagedType.AnsiBStr | 具有预设长度并包含ANSI字符的COM样式的BSTR |
UnmanagedType.BStr | 具有预设长度并包含Unicode字符的COM样式的BSTR |
UnmanagedType.LPStr | 指向以null终止的ANSI字符数组的指针 |
UnmanagedType.LPTStr | 指向以null终止的、平台相关的字符数组的指针 |
UnmanagedType.LPWStr | 指向以null终止的Unicode字符数组的指针 |
UnmanagedType.TBStr | 具有预设长度并包含平台相关字符的COM样式的BSTR |
VBByRefStr | 该值使VisualBasic.NET能够更改非托管代码中的字符串,并使修改结果能在托管代码中反映出来。该值仅支持平台调用的情况 |
对于StringBuilder 只能选LPStr ,LPTStr,LPWStr
释放由非托管函数分配的内存
采用CoTaskMemAlloc方式分配内存,封送拆收器在释放非托管内存时,就能自动调用对应的内存释放方CoTaskMemFree释放掉非托管内存
如果用IntPtr,需要自已控制封送处理行为和释放内存。
由于IntPtr是一种特殊的数据类型,封送拆收器在将非托管数据封送成IntPtr时,是直接将非托管指针复制到IntPtr的值中,由于IntPtr中保存的是非托管内存的指针,因此可以使用marshal类提供的方法Marshal.PtrToStringUni从IntPtr中获取需要的字符串,然后再调用Marshal类中提供的释放内存的方法Marshal.FreeCoTaskMem将非托管内存释放掉。
封送做为返回值的字符串
如果非托管代码中是用CoTaskMemAlloc 分配的内存,在托管代码中可以用string接收返回值,CLR帮助释放内存,也可以用IntPtr接收,需要手动释放内存
封送BSTR类型的字符串
bstr Com中用的字符串类型
对BSTR类型的字符串进行封送处理,需要采用IntPtr类型手动控制封送处理和释放内存
因为BSTR是由SysStringAlloc分配的内存
Clr不能自动释放掉
使用Marshal.PtrToStringBSTR将BSTR类型的指针转为字符串
使用Marshal.FreeBSTR释放掉内存
封送作为参数的结构体
对作为参数的结构体进行封送处理是调用非托管函数最常见的方式。
非托管函数需要定义一个自已用到的非托管结构体。
同样在托管环境中也要定义一个等价的结构体,供调用方作为函数参数使用。
封送结构体最大的难题在于如何确保用于函数的结构体在托管代码和非托管代码中的定义是等价的。
结构体的名称和字段的名称可以不同
- 字段声明顺序必须相同
- 字段的类型必须相同
- 字段在内存中的大小必须相同
StructLayout 属于System.Runtime.InteropServices命名空间,作用是允许开发人员显式指定类和结构体数据字段的内存布局。为了保持结构体数据字段 在内存中顺序与定义时一致 ,指定LayoutKind.Sequential模式。
StructLayout属性有3个非常重要的,可以设置的字段
- CharSet 指定结构体或类中的字符串字段是按LPWSTR还是按LPSTR封送
- Pack:控制结构体或类的数据字段在内存中的对齐方式 内存对齐是指结构体,类或联合体的字段总是要与特定的内存边界对齐。而边界值来源于Pack值的倍数和字段大小的倍数二者中较小的那个数值。在托管代码中,如果没有显式指定Pack值,则默认为8.
- size:指定结构体或类在非托管内存中的绝对大小
封送从函数体内部返回的结构体
封送作为函数返回值 返回的结构体
- 使用new分配的结构体:需要在非托管代码中定义一个提供释放非托管内存的函数,该函数接收一个指向结构体实例的指针,通过C++运算符delete来释放掉由new分配的内存。
- 使用CoTaskMemAlloc分配的结构体:不用再额外写调用释放结构体的非托管函数 使用Marshal.FreeCoTaskMem方法释放内存
作为函数参数返回结构体
非托管函数使用结构体的二级指针
托管代码中按照引用传递 IntPtr即可实现对二级指针的封送处理
最后手动释放非托管内存
封送结构体中的字符串
结构体中的字符指针字段
- 非托管代码使用CoTaskMemAlloc创建的字符串,在托管代码中使用String接收时,封送器可以自动释放托管内存
- 非托管代码使用CoTaskMemAlloc创建的字符串,在托管代码中使用IntPtr接收时,需要用Marshal.PtrToStringAnsi 转换为字符串,并用Marshal.FreeCoTaskMem手动释放内存
- 对于使用new/malloc方法分配的内存 需要在非托管代码中编写对应的释放函数
结构体中的字符数组字段
托管代码中的结构体 仍使用String类型定义字符串字段,但还要用MarshaAs
特性进行修饰,以指明该字段的封送行为
[MarshalAs(UnmanagedType.ByValTStr,SizeConst =255)]
结构体用特性修饰
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
封送类
托管代码 类上添加StructLayout 特性
为了得到非托管函数对实例的修改,必须对参数同时指定In 和Out属性
封送blittable引用类型
如果引用类型的所有字段都 是blittable类型(What is blittable),那么在封送参数时,即使不指定Out属性,也能得到非托管代码更新后的结果。
除了blittable类型本身,下面两种也会当成blittable类型
- 只包含blittable类型的一维数组
- 只包含blittable类型的字段的类
引用类型作为参数封送数据结构的特点:
- 出于性能考虑,引用类型被默认按照In属性的方式封送。
- 如果需要将非托管代码对数据的修改返回给调用(托管代码),则要为参数指定Out属性。
- 对于即需要传入数据又要传出数据的非托管函数,需要为参数同时指定[In,Out]属性。
- 如果引用类型的所有字段都是blittable类型,则该引用类型也是blittalbe类型,在平台调用的过程中,封送拆收器将它锁定在托管内存中,并将指向它的指针传递给非托管函数,因此调用方能够获得非托管函数对数据的修改。
- 如果引用类型作为参数按照引用传递,则它将被按照[In,Out]属性封送。
封送数组
在托管代码中,所有数组都是继承自System.Array
类,因此所有数组都是引用类型,在默认情况下,封器会把托管代码的数组按照In属性封送到非托管函数中,任何对数组中元素的修改都不能返回给调用方。为了让托管代码获得更新后数组元素,需要在封送数组时显式指定[In,Out]
特性。
如果数组内容是blittable类型,则不用加[In,Out]
特性