托管代码与非托管代码之间的交互

基础知识:如下所示:
1、在运行时控制下执行的代码叫做托管代码;在运行时之外执行的代码叫做非托管代码。
2、可以使用vs提供的dumpbin工具来获取动态链接库中提供的所有函数。如:dumpbin /exports xxx.dll。
3、复制操作的是两个不同内存块;固定操作的是同一个内存块。
4、GCHandle、HandleRef可以手动控制托管对象的生命周期,进而避免被垃圾回收器自动回收。
5、当托管函数中使用ref修饰参数变量时,表明该参数变量需使用引用传递。

平台调用:它是一项服务,使托管代码能够定位并调用动态链接库中实现的非托管函数。具有以下特性:
1、平台调用的执行流程如下所示:
1.1、定位包含非托管函数的动态链接库。
1.2、将动态链接库加载到内存中。
1.3、在内存中定位非托管函数的地址并将参数推送到堆栈上。
1.4、将控制权转移到非托管函数。
2、在Unity中选择平台调用而不选择COM组件的原因是MONO不支持CLI。

:具有以下特性:
1、C中的malloc和free函数操作的是私有非托管堆。
2、C#中的Marshal.AllocHGlobal和Marshal.FreeHGlobal函数操作的是进程非托管堆。
3、C#中的Marshal.AllocCoTaskMem和Marshal.FreeCoTaskMem函数操作的是COM非托管堆。
4、C++中的CoTaskMemAlloc和CoTaskMemFree函数操作的是COM非托管堆。
5、应该始终避免分配非托管内存的代码与释放非托管内存代码不同的情况。也就是说必须遵循以下原则:
5.1、malloc函数分配的内存只能用free函数释放。
5.2、Marshal.AllocHGlobal函数分配的内存只能用Marshal.FreeHGlobal函数释放。
5.3、CoTaskMemAlloc和Marshal.AllocCoTaskMem函数分配的内存可以被CoTaskMemFree和Marshal.FreeCoTaskMem函数释放。
6、由于sizeof函数只会返回封送数据在托管代码中的大小,所以在互操作场景中应该使用Marshal.SizeOf函数来代替sizeof函数来获取封送数据的真实大小。

DllImportAttribute定制特性:它用来设置非托管函数的托管原型。包含以下成员:
1、Value:该属性表示动态链接库的名称。
2、BestFitMapping:该字段表示将Unicode字符转换为ANSI字符时,是否(true表示是、false表示否)启用最佳映射。
3、ThrowOnUnmappableChar:该字段表示启用最佳映射时,是否(true表示是、false表示否)将无法映射的Unicode字符转换成一个ANSI ‘?’字符并抛出异常。
4、CallingConvention:该字段表示传递参数到非托管函数的调用约定。可选值如下所示:
4.1、Winapi:默认平台调用约定。例如:在Windows x86上默认为StdCall,在Linux x86上默认为Cdecl。
4.2、Cdecl:该调用约定可以用于接收可变参数的非托管函数。其中,调用者清理堆栈。
4.3、StdCall(默认):被调用者清理堆栈。
4.4、ThisCall:该调用约定用于非托管类上的函数。其中,第一个参数是this指针,存储在寄存器ECX中;其他参数被推送到堆栈上。
4.5、FastCall:不支持该调用约定。
5、PreserveSig:该字段表示是否(true表示是、false表示否)不控制托管原型签名转换成返回HRESULT并且返回值有一个附加的[out, retval]参数的非托管签名。
6、SetLastError:该字段表示是否(true表示是、false表示否)允许调用方使用Marshal.GetLastWin32Error API函数来判断执行非托管函数时是否发生了错误。
7、CharSet:该字段用来控制名称重整以及将字符串参数封送到非托管函数中的方式。可选值如下所示:
7.1、None:该值已过时,具有和Ansi相同的行为。
7.2、Ansi(默认):按照1字节形式来封送字符串。
7.3、Unicode:按照2字节形式来封送字符串。
7.4、Auto:按照操作系统自动编组的形式来封送字符串。
8、EntryPoint:该字段用来指定要调用的动态链接库入口点,入口点用于标识函数在动态链接库中的位置。具有以下特性:
8.1、当不指定该字段时,入口点默认为托管原型的名称。
8.2、当指定该字段时,入口点不仅可以使用非托管函数真实名称;而且还可以使用"#序号"来表示,其中可以通过dumpbin工具来查看序号在动态链接库中关联的函数。
9、ExactSpelling:该字段用来控制名称重整。具有以下特性:
9.1、当CharSet字段值为Ansi时,具有以下特性:
9.1.1、当ExactSpelling字段值为false时,那么就会按照“EntryPoint字段值 > 托管原型名 > 托管原型名称A”的顺序来获取需要调用的非托管函数。
9.1.2、当ExactSpelling字段值为true时,那么就会按照“EntryPoint字段值 > 托管原型名”的顺序来获取需要调用的非托管函数。
9.2、当CharSet字段值为Unicode时,具有以下特性:
9.2.1、当ExactSpelling字段值为false时,那么就会按照“EntryPoint字段值 > 托管原型名”的顺序来获取需要调用的非托管函数(如果获取的非托管函数名称既不包含A也不包含W样式的话,那么就会优先按照“非托管函数名称W”样式从动态链接库中进行查找;然后再按照“非托管函数名称”样式从动态链接库中进行查找)。
9.2.2、当ExactSpelling字段值为true时,那么就会按照“EntryPoint字段值 > 托管原型名”的顺序来获取需要调用的非托管函数。
9.3、一般情况下由编译器设置此字段,可忽略设置。

RegistryPermissionAttribute定制特性:在非托管函数的托管原型上应用该定制特性进行安全检查时,SecurityAction枚举的Assert、Deny、PermitOnly成员会被忽略。

StructLayoutAttribute定制特性:它用来控制类类型或者结构体类型中元素的物理布局。包含以下成员:
1、Value:该属性表示结构体类型或者类类型的排列方式,可选值如下所示:
1.1、Sequential:按照元素定义的先后顺序来进行排列。对于结构体类型而言,默认就是该排列方式;对于类类型而言,默认不存在任何排列方式。
1.2、Explicit:按照FieldOffsetAttribute定制特性中指定的偏移量来进行排列。
1.3、Auto:按照最优方案来进行排列。如:调整元素的先后顺序,进而导致托管结构体类型或者托管类类型和非托管结构体类型的内存表现形式不一致。
2、Pack:该字段表示元素的对齐方式。具有以下特性:
2.1、该字段通常在往磁盘里面写入数据、往网络上传输数据、往非托管代码里面传递数据等情况下使用,但是由于硬件限制可能导致性能变差,所以尽量还是不要使用该字段。
2.2、当元素为结构体类型或者类类型时,元素的对齐大小就是该结构体类型或者类类型的对齐大小;否则,元素的对齐大小就是自身类型的大小。
2.3、该字段值只能为0(默认值)、1、2、4、8、16、32、64、128。
2.4、当该字段值为0时,就按照最大元素对齐大小进行对齐;否则就按照MIN(最大元素对齐大小,该字段值)获取的大小进行对齐。
2.5、元素之间会添加填充来满足对齐需求。具体流程如下所示:
2.5.1、在排列元素时会采用n倍对齐大小来存放元素大小。其中,n取值为可以存放元素大小的最小值。
2.5.2、如果n倍对齐大小减去元素大小得到的剩余大小不足以存放下一个元素大小时,那么就会先将剩余大小进行填充,然后接着排列下一个元素;否则就直接排列下一个元素。
2.5.3、如果最后一个元素不能占满n倍对齐大小,那么n倍对齐大小减去元素大小得到的剩余大小就会进行填充。
3、Size:该字段表示结构体类型或者类类型的绝对大小,取值为MAX(该字段值,结构体类型或者类类型的最小大小)。其中,结构体类型或者类类型的最小大小为n-1个元素参与对齐的大小加上最后一个元素不参与对齐的大小。
4、CharSet:该字段表示结构体类型或者类类型中字符串按照Ansi编码或者Unicode编码进行封送。

MarshalAsAttribute定制特性:它用来设置如何在托管代码与非托管代码之间封送数据。包含以下字段:
1、Value:表示非托管类型,可选值如下所示:
1.1、I1:按照1字节有符号整数类型进行封送数据。
1.2、U1:按照1字节无符号整数类型进行封送数据。
1.3、I2:按照2字节有符号整数类型进行封送数据。
1.4、U2:按照2字节无符号整数类型进行封送数据。
1.5、I4:按照4字节有符号整数类型进行封送数据。
1.6、U4:按照4字节无符号整数类型进行封送数据。
1.7、I8:按照8字节有符号整数类型进行封送数据。
1.8、U8:按照8字节无符号整数类型进行封送数据。
1.9、R4:按照4字节浮点类型进行封送数据。
1.10、R8:按照8字节浮点类型进行封送数据。
1.11、SysInt:根据目标平台按照4或者8字节有符号整数类型进行封送数据。
1.12、SysUInt:根据目标平台按照4或者8字节无符号整数类型进行封送数据。
1.13、FunctionPtr:按照函数指针类型来封送委托类型数据。
1.14、VariantBool:按照2字节有符号整数类型来封送布尔类型数据。
1.15、Bool:按照4字节有符号整数类型来封送布尔类型数据。
1.16、LPStr(默认):按照Ansi编码来封送String或者StringBuilder类型数据。
1.17、LPWStr或者LPTStr:按照Unicode编码来封送String或者StringBuilder类型数据。
1.18、AnsiBStr:按照Ansi编码和固定长度来封送String类型数据。
1.19、BStr、TBStr:按照Unicode编码和固定长度来封送String类型数据。
1.20、LPUTF8Str:按照UTF8编码来封送String类型数据。
1.21、ByValTStr:按照SizeConst字段代表的固定长度和StructLayoutAttribute定制特性指定的CharSet字段值代表的编码来封送String类型数据。

方向定制特性:它用来控制互操作封送器在托管和非托管内存之间如何封送函数参数。具有以下特性:
1、方向定制特性是可选的。当不显示指定方向定制特性时,互操作封送器会根据参数的类型、参数的传递方式(值传递、引用传递)来假定默认规则。
2、InAttribute定制特性用来表示调用者将函数参数封送到被调用者。
3、OutAttribute定制特性用来表示被调用者将数据封送回调用者。
4、InAttribute定制特性和OutAttribute定制特性对应C#中的ref关键字。
5、OutAttribute定制特性对应C#中的out关键字。
6、按照值传递的引用类型(类、数组、字符串、接口)默认使用InAttribute定制特性。但是StringBuilder类型默认使用InAttribute定制特性和OutAttribute定制特性。

blittable类型:指的是数据类型在托管和非托管内存中具有相同的表现形式。具有以下特性:
1、blittable类型在托管和非托管代码之间相互传递时不需要进行转换。
2、blittable类型包含以下几种:
2.1、System命名空间下的Byte、SByte、Int16、UInt16、Int32、UInt32、Int64、UInt64、IntPtr、UIntPtr、Single、Double类型为blittable基元类型。
2.2、由blittable基元类型组成的数组类型为blittable数组类型。
2.3、只由blittable基元类型组成并且使用StructLayoutAttribute定制特性进行格式化的结构体类型为blittable结构体类型。
2.4、只由blittable基元类型组成并且使用StructLayoutAttribute定制特性进行格式化的类类型为blittable类类型。
3、在Unity中可以使用UnsafeUtility.IsBlittable函数来判定指定的类型是否为blittable类型。
4、平台调用执行的函数不支持返回非blittable类型。

托管代码向非托管函数传递参数:需要注意以下事项:
1、为了防止互操作封送器在非托管代码调用结束后自动回收返回值,此时可以在托管代码中使用IntPtr类型来接收返回值的地址。只有当托管代码中使用完毕后,互操作封送器才会择机回收返回值。
2、不支持传递泛型类型。
3、托管代码传递数值类型到非托管函数中时,需要注意以下事项:
3.1、不论由值传递还是引用传递数值类型,非托管函数中都是使用一个新的数值类型变量进行接收。
3.2、使用由值传递数值类型时,调用者看不见被调用者修改的内容。
3.3、使用由引用传递数值类型时,调用者可以看见被调用者修改的内容。
4、托管代码传递枚举类型到非托管函数中时,需要注意以下事项:
4.1、托管代码中只能使用值传递枚举类型。
5、托管代码传递联合体类型到非托管函数中时,需要注意以下事项:
5.1、托管代码中是没有联合体概念的,但是可以使用结构体来进行模拟实现。
5.2、托管代码的结构体中值类型和值类型允许重叠,也就是允许占用相同的内存块数。如:两个值类型都占用0位置([FieldOffset(0)])的内存块。
5.3、托管代码的结构体中值类型和引用类型不允许重叠,也就是不允许占用相同的内存块数。
5.4、只有当每一个引用类型单独用一个顺序布局([StructLayout(LayoutKind.Sequential)])的结构体来进行表示时,才能将该引用类型数据独自传值给非托管代码中的联合体。
5.5、应该根据目标平台位数(IntPtr.Size == 8 表示64位;IntPtr.Size == 4 表示32位)来设置变量重叠时的内存块数。如:64位的IntPtr变量应该使用[FieldOffset(8)];32位的IntPtr变量应用使用[FieldOffset(4)]。
6、托管代码传递结构体类型到非托管函数中时,需要注意以下事项:
6.1、当未托管函数不要求间接时,使用由值传递的结构体。
6.2、当未托管函数要求一级间接时,使用由引用传递的结构体。
6.3、托管结构体类型中包含子结构体类型时,如果该子结构体类型可以进行平展的话,那么就可以将该子结构体类型中的字段成员拆解出来并布局到父结构体类型中,从而组成一个大的托管结构体类型。
6.4、未托管函数中只能访问托管结构体类型中的字段成员。
6.5、使用由值传递结构体类型时,调用者看不见被调用者修改的内容。
6.6、使用由引用传递结构体类型时,调用者可以看见被调用者修改的内容。
7、托管代码传递类类型到非托管函数中时,需要注意以下事项:
7.1、当未托管函数要求一级间接时,使用由值传递的类。此时可以传递null值。
7.2、当未托管函数要求二级间接时,使用由引用传递的类。
7.3、托管类类型中包含子类类型时,如果该子类类型可以进行平展的话,那么就可以将该子类类型中的字段成员拆解出来并布局到父类类型中,从而组成一个大的托管类类型。
7.4、未托管函数中只能访问托管类类型中的字段成员。
7.5、使用由值传递类类型时,当类类型里面的字段全是blittable类型的话,那么调用者就可以看见被调用者修改的内容;否则,如果使用了OutAttribute定制特性来设置参数的话,那么调用者就仍然能看见被调用者修改的内容;否则,调用者就看不见被调用者修改的内容。
7.6、使用由引用传递类类型时,调用者可以看见被调用者修改的内容。
8、托管代码传递字符串类型到非托管函数中时,需要注意以下事项:
8.1、BSTR是通过SysAllocString函数进行空间分配,其他字符串类型都是通过CoTaskMemAlloc函数进行空间分配。
8.2、当String以LPStr、AnsiBStr、BStr、TBStr、LPUTF8Str非托管类型按照值或者引用传递时,互操作封送器首先会将String数据复制到一个辅助缓冲区中;然后将指向辅助缓冲区的指针传递给非托管函数。
8.3、当String以LPStr、AnsiBStr、BStr、TBStr、LPUTF8Str非托管类型按照值传递时,互操作封送器在非托管代码调用返回时不会将辅助缓冲区中的内容同步回托管代码中。
8.4、当String以LPStr、AnsiBStr、BStr、TBStr、LPUTF8Str非托管类型按照引用传递时,互操作封送器在非托管代码调用返回时会将辅助缓冲区中的内容复制回托管代码中的一个新String中。
8.5、当String以LPWStr、LPTStr非托管类型按照值传递时,互操作封送器会将指向String内部的Unicode缓冲区的指针传递给非托管函数。
8.6、当String以LPWStr、LPTStr非托管类型按照值传递时,互操作封送器在非托管代码调用返回时会将Unicode缓冲区中的内容固定回托管代码中的String。
8.7、当String以LPWStr、LPTStr非托管类型按照引用传递时,互操作封送器首先会将String数据复制到一个辅助缓冲区中;然后将指向辅助缓冲区的指针传递给非托管函数。
8.8、当String以LPWStr、LPTStr非托管类型按照引用传递时,互操作封送器在非托管代码调用返回时会将辅助缓冲区中的内容复制回托管代码中的一个新String中。
8.9、当StringBuilder以LPStr、LPWStr、LPTStr非托管类型按照值或者引用传递时,互操作封送器首先会将StringBuilder数据复制到一个辅助缓冲区中;然后将指向辅助缓冲区的指针传递给非托管函数。
8.10、当StringBuilder以LPStr、LPWStr、LPTStr非托管类型按照值或者引用传递时,互操作封送器在非托管代码调用返回时会将辅助缓冲区中的内容固定回托管代码中的StringBuilder。
8.11、在结构体类型和类类型中不能存在StringBuilder类型的字段成员。
8.12、在结构体类型和类类型中封送String类型的字段成员时,可以采用ByValTStr非托管类型形式进行封送。
8.13、互操作封送器在非托管代码调用返回时会将字符串返回值复制回托管代码中的一个新String中。
9、托管代码传递委托类型到非托管函数中时,需要注意以下事项:
9.1、非托管函数必须使用函数指针来接收托管代码中传递进来的委托对象。
9.2、托管代码在调用非托管函数并使用委托对象时,CLR确保委托对象不会被垃圾回收器进行回收。
9.3、托管代码在调用完非托管函数并后续还要使用委托对象时,可以使用GCHandle或者HandleRef来手动控制委托对象的生命周期。
10、托管代码传递数组类型到非托管函数中时,需要注意以下事项:
10.1、当数组类型按照值传递时,可以传递null值。
10.2、当数组类型按照引用传递时,需要使用IntPtr对象来表示该数组对象。
10.3、使用由值传递数组类型时,需要注意以下事项:
10.3.1、当元素类型是blittable基础类型的话,那么被调用者可以将修改的内容固定回调用者。
10.3.2、当元素类型是blittable结构体类型的话,如果使用了OutAttribute定制特性来设置参数的话,那么被调用者可以将修改的内容固定回调用者;否则,调用者就看不见被调用者修改的内容。
10.3.3、当元素类型是no-blittable类型的话,如果使用了OutAttribute定制特性来设置参数的话,那么被调用者可以将修改的内容复制回调用者;否则,调用者就看不见被调用者修改的内容。
10.4、使用引用传递IntPtr类型时,被调用者可以将修改的内容固定回调用者。
10.5、将数组从非托管函数封送到托管代码时,封送处理程序会检查与参数关联的MarshalAsAttribute定制特性以确定数组大小。 如果未指定数组大小,则只封送一个元素。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值