一:背景

1. 讲故事

如果你常翻看FCL的源码,你会发现这里面有不少方法借助了C/C++的力量让C#更快更强悍,如下所示:


[DllImport("QCall", CharSet = CharSet.Unicode)]
[SecurityCritical]
[SuppressUnmanagedCodeSecurity]
private static extern bool InternalUseRandomizedHashing(); [DllImport("mscoree.dll", EntryPoint = "ND_RU1")]
[SuppressUnmanagedCodeSecurity]
[SecurityCritical]
public static extern byte ReadByte([In] [MarshalAs(UnmanagedType.AsAny)] object ptr, int ofs);

联想到上一篇阿里短信netsdk也是全用C++实现,然后用C#做一层壳,两者相互打辅助彰显更强大的威力,还有很多做物联网的朋友对这种.Net互操作技术太熟悉不过了,很多硬件,视频设备驱动都是用C/C++实现,然后用winform/WPF去做管理界面,C++还是在大学里学过,好多年没接触了,为了练手这一篇用P/Invoke来将两者相互打通。

二:PInvoke互操作技术

1. 一些前置基础

这里我用vs2019创建C++的Console App,修改两个配置: 将程序导出为dll,修改成compile方式为Compile as C++ Code (/TP)

2. 基本类型的互操作

简单类型是最好处理的,基本上int,long,double都是一一对应的,这里我用C++实现了简单的Sum操作,画一个简图就是下面这样:

新建一个cpp文件和一个h头文件,如下代码。


--- Person.cpp extern "C"
{
_declspec(dllexport) int Sum(int a, int b);
} --- Person.h #include "Person.h"
#include "iostream"
using namespace std; int Sum(int a, int b)
{
return a + b;
}

有一个注意的地方就是 extern "C",一定要用C方式导出,如果按照C++方式,Sum名称会被编译器自动修改,不信你把extern "C"去掉,我用ida打开给你看一下,被修改成了 ?Sum@@YAHHH@Z, 尴尬。

接下来把C++项目生成好的 ConsoleApplication1.dll copy到C#的bin目录下,代码如下:


class Program
{
[DllImport("ConsoleApplication1.dll", CallingConvention = CallingConvention.Cdecl)]
extern static int Sum(int a, int b); static void Main(string[] args)
{
var result = Sum(10, 20); Console.WriteLine($"10+20={result}"); Console.ReadLine();
}
} ---- output ----- 10+20=30

2. 字符串的互操作

我们知道托管代码和非托管代码是两个世界,这中间涉及到了两个世界的的类型映射,那映射关系去哪找呢? 微软的msdn还真有一篇介绍 封送通用类型对照表: https://docs.microsoft.com/zh-cn/dotnet/standard/native-interop/type-marshaling ,大家有兴趣可以看一下。

从图中可以看到,C#中的string对应C++中的char*,所以这里就好处理了。


--- Person.cpp extern "C"
{
//字符串
_declspec(dllexport) int GetLength(char* chs);
} --- Person.h #include "Person.h"
#include "iostream"
using namespace std; int GetLength(char* chs)
{
return strlen(chs);
}

然后我们看一下C#这边怎么写,通常string在C++中使用asc码,而C#中是Unicode,所以在DllImport中加一个CharSet指定即可。


class Program
{
[DllImport("ConsoleApplication1.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
extern static int GetLength([MarshalAs(UnmanagedType.LPStr)] string str); static void Main(string[] args)
{
var str = "hello world";
Console.WriteLine($"length={GetLength(str)}"); Console.ReadLine();
}
} ---- output ----- length=11

3. 复杂类型的处理

复杂类型配置对应关系就难搞了,还容易搞错,错了弄不好还内存泄漏,怕了吧,幸好微软提供了一个小工具P/Invoke Interop Assistant ,它可以帮助我们自动匹配对应关系,我就演示一个封送Person类的例子。

从图中可以看到,左边写好 C++,右边自动给你配好C#的映射类型,非常方便。


--- Person.cpp extern "C"
{
class Person
{
public:
char* username;
char* password;
}; _declspec(dllexport) char* AddPerson(Person person);
} --- Person.h #include "Person.h"
#include "iostream"
using namespace std; char* AddPerson(Person person)
{
return person.username;
}

可以看到C++中AddPerson返回了char*,在C#中我们用IntPtr来接,然后用Marshal将指针转换string,接下来用工具生成好的C#代码拷到项目中来,如下:


[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct Person
{
/// char*
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)]
public string username; /// char*
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)]
public string password;
} class Program
{
[DllImport("ConsoleApplication1.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
extern static IntPtr AddPerson(Person person); static void Main(string[] args)
{
var person = new Person() { username = "dotnetfly", password = "123456" }; var ptr = AddPerson(person);
var str = Marshal.PtrToStringAnsi(ptr); Console.WriteLine($"username={str}"); Console.ReadLine();
}
} ---------- output ------------ username=dotnetfly

4. 回调函数(异步)的处理

前面介绍的3种情况都是单向的,即C#向C++传递数据,有的时候也需要C++主动调用C#的函数,我们知道C#是用回调函数,也就是委托包装,具体我就不说了,很开心的是C++可以直接接你的委托,看下怎么实现。


--- Person.cpp extern "C"
{
//函数指针
typedef void(_stdcall* PCALLBACK) (int result);
_declspec(dllexport) void AsyncProcess(PCALLBACK ptr);
} --- Person.h #include "Person.h"
#include "iostream"
using namespace std; void AsyncProcess(PCALLBACK ptr)
{
ptr(10); //回调C#的委托
}

从代码中看到,PCALLBACK就是我定义了函数指针,接受int参数。


class Program
{
delegate void Callback(int a); [DllImport("ConsoleApplication1.dll", CallingConvention = CallingConvention.Cdecl)]
extern static void AsyncProcess(Callback callback); static void Main(string[] args)
{
AsyncProcess((i) =>
{
//这里回调函数哦... Console.WriteLine($"这是回调函数哦: {i}");
}); Console.ReadLine();
}
} ------- output ------- 这是回调函数哦: 10

这里我做了一个自定义的delegate,因为我使用Action<T>不接受泛型抛异常(┬_┬)。

四:总结

这让我想起来前段时间用python实现的线性回归,为了简便我使用了http和C#交互,这次准备用C++改写然后PInvoke直接交互就利索了,好了,借助C++的生态,让 C# 如虎添翼吧~~~

使用PInvoke互操作,让C#和C++愉快的交互优势互补的更多相关文章

  1. CUDA与OpenGL互操作

    当处理较大数据量的时候,往往会用GPU进行运算,比如OpenGL或者CUDA.在实际的操作中,往往CUDA实现并行计算会比OpenGL更加方便,而OpenGL在进行后期渲染更具有优势.由于CUDA中的 ...

  2. Atitit s2018.2 s2 doc list on home ntpc.docx  \Atiitt uke制度体系 法律 法规 规章 条例 国王诏书.docx \Atiitt 手写文字识别 讯飞科大 语音云.docx \Atitit 代码托管与虚拟主机.docx \Atitit 企业文化 每日心灵 鸡汤 值班 发布.docx \Atitit 几大研发体系对比 Stage-Gat

    Atitit s2018.2 s2 doc list on home ntpc.docx \Atiitt uke制度体系  法律 法规 规章 条例 国王诏书.docx \Atiitt 手写文字识别   ...

  3. [Java Web] 4、JavaScript 简单例子(高手略过)

    内容概览: JavaScript简介 JavaScript的基本语法 JavaScript的基本应用 JavaScript的事件处理 window对象的使用 JavaScript简介: JavaScr ...

  4. 【其他】IT公司的企业文化与竞争力

    一直觉得三流企业靠成本竞争,二流企业靠体制竞争,一流企业靠文化竞争. 企业在竞争时候,总会提到一个词:核心竞争力.对于IT企业来说,核心竞争是什么?无论是技术也好,销售也罢,归根到底还是人才的竞争,优 ...

  5. Java Script 简介

    Java Script 简介 JavaScript 是世界上最流行的编程语言. 这门语言可用于 HTML 和 web,更可广泛用于服务器.PC.笔记本电脑.平板电脑和智能手机等设备.JavaScrip ...

  6. Java内存管理-初始JVM和JVM启动流程(二)

    勿在流沙住高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇分享了什么是程序,以及Java程序运行的三个阶段.也顺便提到了Java中比较重要 ...

  7. 20165203&20165206结对创意感想

    一.结对学习过程 我和我的搭档性格志趣相投,而且各有所长,我们两个均属于一丝不苟的人,做一件事就要把它做好.因此,我们学习理念相同,志趣相投,这可能会占很大的优势.首先,我们会利用一周的前几天看课本, ...

  8. 移动端混合开发----ionic

    目前移动端分为三大主流:纯原生.混合开发.web App,随着手机硬件的升级,公司们似乎偏好于web页面开发,而混合开发相对纯web App似乎更受大公司青睐,所谓混合开发俾人理解为,原生代码(iOS ...

  9. WK 与 JS 的那些事

    苹果在iOS 8中推出了 WKWebView,这是一个高性能的 web 框架,相较于 UIWebView来说,有巨大提升.本文将针对 WKWebView 进行简单介绍,然后介绍下如何和 JS 进行愉快 ...

随机推荐

  1. 网络流--最大流--POJ 1273 Drainage Ditches

    链接 Description Every time it rains on Farmer John's fields, a pond forms over Bessie's favorite clov ...

  2. Unity 游戏框架搭建 2019 (四十二、四十三) MonoBehaviour 简化 & 定时功能

    MonoBehaviour 简化 在前两篇,我们完成了第九个示例.为了完善第九个示例,我们复习了类的继承,又学习了泛型和 params 关键字. 我们已经接触了类的继承了.接触继承之前,把类仅仅当做是 ...

  3. IOS抓取与反抓取

    目录 IOS抓取基础知识 IOS抓取方式 iOS破解 模拟器 黑雷苹果模拟器 介绍 局限 改机软件 常用改机软件 检测 可更改属性 注入与Hook(越狱下实现作弊) 注入方式 Hook方式 重打包(非 ...

  4. 【网络基础】ARP地址解析协议

    ARP(Address Rssolution Protocol) 地址解析协议 用于将IP地址解析为MAC地址. MAC地址是设备的物理地址,是被分配给每一个网络接口卡的全球唯一序号. 全球唯一:理论 ...

  5. 「从零单排HBase 09」Hbase的那些数据结构和算法

    在之前学习MySQL的时候,我们知道存储引擎常用的索引结构有B+树索引和哈希索引. 而对HBase的学习,也离不开索引结构的学习,它使用了一种LSM树((Log-Structured Merge-Tr ...

  6. C#并发编程之初识并行编程

    写在前面 之前微信公众号里有一位叫sara的朋友建议我写一下Parallel的相关内容,因为手中商城的重构工作量较大,一时之间无法抽出时间.近日,这套系统已有阶段性成果,所以准备写一下Parallel ...

  7. 王颖奇 201771010129《面向对象程序设计(java)》第七周学习总结

    实验七 继承附加实验 实验时间 2018-10-11 1.实验目的与要求 (1)进一步理解4个成员访问权限修饰符的用途: A.仅对本类可见-private B.对所有类可见-public C.对本包和 ...

  8. 【Kafka】Stream API

    Stream API Kafka官方文档给了基本格式 http://kafka.apachecn.org/10/javadoc/index.html?org/apache/kafka/streams/ ...

  9. Arrays.binarySearch的返回值

    如果查找的值包含在数组中,返回搜索的第一个值的下标: 如果查找的值不在数组中,返回(-插入点-1):插入点即为第一个大于此查找值的元素下标 插入点 为将该值插入数组的那一点:即第一个大于此键的元素下标 ...

  10. [hdu5218]DP-约瑟夫环变形

    题意:n个人围成一圈,另外一个人最开始站在第一个人前面,每次从集合s里面随机选一个数x,这个人顺时针经过x个人后停下来,当前位置的前一个人出队,然后继续进行,求最后剩下的那个人的可能编号. 思路:由于 ...