首先,我们需要在C++程序中导出DLL文件。我使用的是Visual Studio开发,把项目"属性"中的“配置类型”改为"动态库dll",然后添加如下导出代码:
extern "C" __declspec(dllexport) void AS3911FindTag(Tag tags[], int &tagNum, int slot);//find tags
extern "C" __declspec(dllexport) bool GetTagInformation(Tag& tag);//get tag information
extern "C" __declspec(dllexport) int usbDeviceAttached(int waitTime);//initialize usb connect
然后运行程序,可以找到生成的DLL文件。在C#项目中使用,把DLL文件和生成的exe文件放在一起即可,然后C#中添加头部引用:
using System.Runtime.InteropServices;
类中声明:
[DllImport("AS3911Test.dll", CallingConvention = CallingConvention.Cdecl)] public static extern void AS3911FindTag(IntPtr tags, ref int tagNum, int slot); [DllImport("AS3911Test.dll", CallingConvention = CallingConvention.Cdecl)] public static extern bool GetTagInformation(ref Tag tag);
[DllImport("AS3911Test.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int usbDeviceAttached(int waitTime);
声明后,可以直接使用这些方法了。不过要注意一点,C#和C++的交互,最麻烦的是数据类型的转换。我这里在参数中使用了Tag这个结构体,在C++中如下:
struct Tag { char id[20]; char dsfid[4]; char afi[4]; unsigned int blockNum; unsigned int bytesPerBlock; char info[1024]; };
在C#中对应的声明如下:
public struct Tag { /// char[]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 20)] public string id; /// char[]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 4)] public string dsfid; /// char[]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 4)] public string afi; /// unsigned int
public uint blockNum; /// unsigned int
public uint bytesPerBlock; /// char[]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 1024)] public string info; }
注意C#中参数部分的ref,相当于C++中指针。涉及到数据类型对应转换时,可以借助CLRInsideOut这个工具。下面以AS3911FindTag(IntPtr tags, ref int tagNum, int slot)这个函数为例,看看如何使用。
static void FindTag() { ComInit(); usbDeviceAttached(500); ISO15693Init(); int num = 0; int size = Marshal.SizeOf(typeof(Tag)) * Max_Tag_Num; IntPtr pBuff = Marshal.AllocHGlobal(size); Tag[] mytag = new Tag[Max_Tag_Num]; AS3911FindTag(pBuff, ref num, 16); for (int i = 0; i < num; i++) { IntPtr p = new IntPtr(pBuff.ToInt64() + Marshal.SizeOf(typeof(Tag)) * i); mytag[i] = (Tag)Marshal.PtrToStructure(p, typeof(Tag)); Console.WriteLine(mytag[i].id); } Marshal.FreeHGlobal(pBuff); ISO15693DeInit(); usbDeviceDeAttached(); ComDeInit(); }
直接看到AS3911FindTag(pBuff, ref num, 16)这段代码,这里的pBuff是C#中的引用,然后用Marshal.AllocHGlobal(size)分配了一块内存,传入函数的是一块内存的引用。第二个参数比较简单,表示通过该函数给num赋值。AS3911FindTag这个函数运行完后,pBuff所指向的内存块会被赋值,我们下面就试着取出数据。我们使用了结构体Tag并声明了大小,所以可以从这里着手解决问题。看到for循环中第二行代码,这里的p表示从何处读取数据,最后做类型转换可。Marshal.PtrToStructure这个函数可以将数据从非托管内存块封送到新分配的指定类型的托管对象,这里从DLL中的非托管内存封送到当前C#托管内存。最后,用Marshal.FreeHGlobal(pBuff)释放内存。