P/Invoke 机制

发表于2019-12-18
评论0 9k浏览

C#调用采用C/C++编写的dll库,托管代码中对非托管接口进行定义 + 数据封送

 

C#调用C++过程

76rN14pcCFmCavJoderY.png

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1.查询包含调用函数的DLL

2.将DLL加载到内存中(首次加载,并缓存)

3.查找函数在内存中的地址,并将其参数压入堆栈,封送所需数据。

4.调用C++对应函数。

注:在我们使用第三方DLL的时候,可以使用 dumpbin.exe工具,查看DLL里面的接口。

 

定义

8LqueKisVb6fffHTW4Vi.png

 

 

 

 

声明信息中包含指定的dll和dll内指定的函数,此方法是全局C函数。

extern:告诉编译器,我属于外部接口

static:非成员方法的好处是,不需要构造对象

DllImport:声明依赖的dll,它有很多参数,举几个常见的。

(1)EntryPoint:如果用它指定C++函数名,C#里面声明的函数就可以不与C++一致。

(2)CallingConvention:约定传递的参数的堆栈,由谁来清理,是C#端还是C++端。默认是由系统环境决定。

(3)CharSet:两个作用。1.确定字符串参数的封送格式。2.确定调用C++里面那一类接口,有些接口有不同字符编码格式的版本。

 

内存释放

C#调用C++时,有时候涉及指针和堆内存对象,如何释放比较合理?在C++里面写一个释放的方法,供C#调用。

TqWhLpW5E2YqUeFdzEGV.png

 

 

 

 

 

 

 

 

 

数据封送

由封送拆收器完成,将数据从非托管转换为托管类型,然后复制内存到托管内存,调用完后释放掉封送的内存。

封送字符串

ZQXAfADWc4YIvnIrQQbb.png

 

 

 

 

 

string:string类型,参数不可变,如果作为方法参数,只能in不能out

StringBuilder:默认带in/out属性,所以它既可以字符串到C++,也可以从C++获取字符串。最优实践:定义StringBuilder(size)的时候初始化一个确定的size,其次是采用Unicode编码,可以减少一次拷贝。

CharSet & MarshalAs:CharSet 可以定义整个函数所有参数的字符编码,MarshalAs可以指定单独某一个。

 

封送结构体

定义结构体:

1.数据结构要一致,定义顺序一致,类型要一致,占用内存要一致

2.ref:代表参数不是值类型,而是引用类型,具备out特性。修改后,C#端内容会改变。

3.StructLayout:CharSet指定字符串类型,Pack = 8指定在内存中的对齐方式,Size占用内存大小。

4.LayoutKind:Sequential字段按定义的顺序在内存中布局,Explicit精确控制位置,Auto由系统自动布局。默认是Sequential用这个就好,规范做事不容易出错。

BXORLLAdzzt24YYyDH6S.png

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

封送指针

Cc31uv6ofM8N0bH0n6H3.png

 

 

 

 

对于非托管内存数据(C++),如果要返回到托管内存(C#),最万能的做法就是IntPtr + Marshal组合,一力降十会。

 

最后:

1.P/Invoke机制虽然保证了C#对Native的访问,但是毕竟是夸语言沟通,参数传递时涉及拷贝,成本并不低。

2.关于参数的传递,应该力求简洁,如果你的结构体嵌套的很复杂,或许应该考虑换种方式组织代码。

  • 允许他人重新传播作品,但他人重新传播时必须在所使用作品的正文开头的显著位置,注明用户的姓名、来源及其采用的知识共享协议,并与该作品在磨坊上的原发地址建立链接
  • 可对作品重新编排、修改、节选或者以作品为基础进行创作和发布
  • 不可将作品进行商业性使用
  • 需在以作品基础上创作的演绎作品上适用相同类型的知识共享许可条款

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

标签: