P/Invoke简介

简单点说,P/Invoke就是一种可以使本地语言(C++)和高级语言(C#、Java、Python)等其他语言相互调用的技术。我们拿Windows下最熟悉的C#技术来说,通过P/Invoke技术,我们可以实现使用C#调用C++,也可以实现C++调用C#。 下面的介绍我们就都以C#为例来说明。

微软为什么要发明P/Invoke这个技术。

windows系统提供了大量的系统api供我们开发人员使用,这些api都是以C/C++类库的方式暴露出来的,如果我们是使用C/C++开发,可以直接调用这些api。但是其他的高级语言C#、Java、Python、GO等高级语言,其实也需要调用这些Windows Api。由于高级语言和本地语言(C/C++)本质上有很大的差距,无法直接相互调用。于是微软就发明了一个叫做P/Invoke的技术来允许高级语言(C#)调用本地语言(C/C++)。

c#调用c++是否只有P/Invoke一种方式

c#调用c++除了可以通过P/Invoke这种方式之外,也可以通过C++/CLI这种中间层项目来实现。

P/Invoke的核心是什么

P/Invoke的核心实际上是把C#中的各种数据类型传递给C++或者把C++中的数据类型传递给C#。尤其是一些复杂类型的传递,比如结构体、类、数组、指针等复杂类型的相互传递和转换。

用一个简单的例子来演示一下C#如何调用C++

假设我们C#要调用C++,我们需要有两个程序,一个是C#的控制台程序,一个是C++的动态链接库。这里我演示使用C#调用C++的一个Add方法。
首先需要在C++动态类库中定义一个方法,具体如下:

extern "C" {
	_declspec(dllexport)	int Add(int a, int b)
	{
		return a + b;
	}; 
}

以上C++代码有两个关键字需要注意:
1、extern "C" 关键字:这个必须要有,它指示编译器使用C语言的方式编译该函数,因为C++编译器在编译之后会使函数名发生变化,而且不同编译器对编译之后的函数名的命名规则可能还不一样,这可能会导致我们的C#程序找不到对应的函数。但是C语言编译出来的函数名和我们定义的函数名是一致的,所以 extern "C" 这个关键字必须要有。
2、_declspec(dllexport) 关键字:这个关键字也必须要有,dllexport的字面意思就是dll导出,这个关键字指示编译器将这个函数导出,并给我们生成一个.lib的链接库,如果没有 _declspec(dllexport) 这个关键字的话,是不会生成.lib库的。当然函数的导出有很多种方法,我在这里使用的是 _declspec(dllexport) 关键字导出,这是最常用的一种导出方式。你也可以使用.def文件导出、使用AFX_EXT_CLASS导出、使用命令行导出。
生成C++程序集之后,我们需要将C++的.dll库和.lib库复制到C#控制台的运行目录下,否则C#控制台是无法加载C++的程序集的。
接下在C#中定义一个方法来调用C++程序集中的方法。

[DllImport("TestDll.dll")]
public static extern int Add(int a, int b);

以上C#代码有几个关键点:
1、DllImport :这是C#专门给调用C++而设计的一个特性,该特性有很多参数,构造函数中传入的参数TestDll.dll是我们要调用的C++程序集的名称。
2、static :调用C++的C#方法必须定义成静态方法。
3、extern关键字 :这个关键字的字面意思是外部的,简单点说,就是这个C#方法是个外部方法(定义在C++程序集中),没有方法体,程序执行的时候,要到外部程序集中去寻找这个方法的方法体。
4、方法名Add、方法的两个参数、方法返回值 要和C++中函数的定义保持一致。 完成上面的两部分代码,就可以顺利的在C#中调用C++的代码了。

C++调用C#代码

C++如果想调用C#的话,C#需要将一个方法用委托包装起来,然后传递给C++,C++使用一个函数指针来接受C#的委托。这样C++通过调用这个函数就可以调用C#的方法了。

public delegate void CallBackDel(int a, int b);
static CallBackDel del = new CallBackDel(CPPCallBack);
static void CPPCallBack(int a,int b)
{
    Console.WriteLine(a + "" + b);
}

以上代码中,我们定义了一个方法 CPPCallBack ,这个方法将由C++调用。其次我们定义了一个委托类 型CallBackDel ,然后再定义了一个CallBackDel的对象 del 对方法进行封装。del就是最终传递给C++的对象。
接下来我们看下C++如何接受这个委托,C++主要使用一个函数指针来接受。

typedef void (*chasrpFun)(int a, int b);
_declspec(dllexport) void SetCallBack(chasrpFun fun) 
{
	fun(3, 5);//直接调用c#的委托
};	

以上代码中,我们通过typedef定义了一个函数指针类型,然后SetCallBack的第一个形参就是函数指针,在函数体中直接调用这个函数指针,其实就相当于调用了C#中的方法。
由于SetCallBack方法需要C#主动调用,所以我们在C#中需要定义一个方法,来调用C++函数。

[DllImport("TestDll.dll")]
public static extern int SetCallBack(CallBackDel del);

定义完成之后,我们就可以在C#中调用 SetCallBack 函数将委托传递给C++,然后C++就可以调用我们C#的委托了。但是这里要注意,我们传递给C++的委托对象,一般建议设置成静态变量或者全局变量,防止C#这边执行完代码之后,GC把委托对象回收,当C++回调过来的时候,发现C#的对象已经不见了,这时候就会报错。