新建一个项目,名为PInvoke:
建好项目后,添加一个cpp源文件,由于只是一个Demo,我们使用默认的名称Source.cpp:
Source.cpp代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <Windows.h> typedef struct MyType{ int i; char *s; double d; struct MyType *p; } MyType; int TestInt(int a, int b) { return a + b; } void TestIntPtr(int *i) { *i = *i * -1; } char* TestCharPtr(char *a, char *b) { return strcat(a, b); } void TestStructPtr(MyType *p) { p->i++; p->d++; } void TestPtrPtr(int **a, int length) { *a = (int*)malloc(sizeof(int) * length); memset(*a, 0, sizeof(int) * length); } void TestArray(int *a, int length) { for (int i = 0; i < length; i++) { a[i]++; } } void TestCallback(void (*pf) ( )) { pf(); } int TestCharWidth(char *s) { return strlen(s); }
添加一个新文件Source.def,用于导出Dll中的函数:
内容如下,其中LIBRARY "PInvoke"中的"PInvoke"是项目的名称:
LIBRARY "PInvoke"
EXPORTS
TestInt
TestIntPtr
TestCharPtr
TestStructPtr
TestPtrPtr
TestArray
TestCallback
TestCharWidth
C的工程已经竣工了,下面来在解决方案中新加一个C#项目:
建好后,将ConsoleApplication1项目设置为启动项目,修改Program.cs代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct MyType { /// int public int i; /// char* [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)] public string s; /// double public double d; /// myType* public IntPtr p; } public delegate void Callback(); [DllImport("PInvoke.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public static extern int TestInt(int a, int b); [DllImport("PInvoke.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public static extern void TestIntPtr(ref int i); [DllImport("PInvoke.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr TestCharPtr(System.IntPtr a, System.IntPtr b); [DllImport("PInvoke.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr TestStructPtr(ref MyType myType); [DllImport("PInvoke.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public static extern void TestArray(int[] a, int length); [DllImport("PInvoke.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public static extern void TestCallback(Callback callback); [DllImport("PInvoke.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public static extern int TestCharWidth(IntPtr s); static void CallbackFunction() { Console.WriteLine("callback invoked"); } static void Main(string[] args) { // TestInt Console.WriteLine(TestInt(1, 2)); // TestIntPtr int i = 1; TestIntPtr(ref i); Console.WriteLine(i); // TestCharPtr IntPtr helloPtr = Marshal.StringToHGlobalAnsi("hello"); IntPtr worldPtr = Marshal.StringToHGlobalAnsi("world"); IntPtr helloWorldPtr = TestCharPtr(helloPtr, worldPtr); string helloWorld = Marshal.PtrToStringAnsi(helloWorldPtr); Console.WriteLine(helloWorld); Marshal.FreeHGlobal(helloPtr); Marshal.FreeHGlobal(worldPtr); // Marshal.FreeHGlobal(helloWorldPtr); // 因为helloWorldPtr和helloPtr指向的是同一地址,所以再次释放会报错 // TestCharWidth string a = "a的"; IntPtr aPtr = Marshal.StringToHGlobalAnsi(a); // Ansi int len = TestCharWidth(aPtr); Console.WriteLine(len); a = Marshal.PtrToStringAnsi(aPtr); Marshal.FreeHGlobal(aPtr); aPtr = Marshal.StringToHGlobalUni(a); // Unicode len = TestCharWidth(aPtr); // 值是1,strlen没有正确处理unicode,所以不要使用strlen测量unicode字符串的长度 Console.WriteLine(len); a = Marshal.PtrToStringUni(aPtr); Marshal.FreeHGlobal(aPtr); // TestStructPtr MyType myType = new MyType { i = 0, d = 1.1, s = "a的", p = IntPtr.Zero }; TestStructPtr(ref myType); // TestArray int[] array = new int[] { 1, 2, 3 }; TestArray(array, array.Length); // TestCallback TestCallback(CallbackFunction); Console.Read(); } } }
为了调试方便,我们在编译好C的dll后,自动把dll拷贝到C#项目的可执行文件目录下。在PInvoke项目上右键,属性,然后如下图:
按F5调试一下你的代码吧。
PInvoke Interop Assistant
PInvoke Interop Assistant是一个免费的把C/C++代码转成C#/VB.net的工具,非常好用。以下是使用截图:
在左边写native code,右边会自动转换成C#。所以如果有Native的类型不知转成C#该怎么写,它可以直接告诉你。
sample代码下载地址:https://files.cnblogs.com/dc10101/PInvoke.zip
PInvoke Interop Assistant下载地址:https://files.cnblogs.com/dc10101/PInvokeInteropAssistant.zip