
在C#中调用C++动态库导出的函数时,有时候需要封送一些复杂的数据结构,这时候不确定自己封送的数据的内存布局对不对,就想要弄个简单的C++项目模拟一下被调用的接口,检查一下C#的数据封送是否正确。
因为对C++项目基本上没任何了解,捣鼓了半天才搞定,所以这里简单记录一下过程,以加深记忆。
1. 首先用C++的动态链接库模板创建一个项目。然后在dllmain.cpp中定义一个名为PostMsg的C导出函数,该函数接收MsgData结构体并打印,代码如下:
typedef struct MsgData { int index; char msg[32]; BYTE data[8]; } MsgData; static void dumpData(const MsgData* msg) { for (size_t i = 0; i < sizeof(msg->data); i++) { printf("%02x ", *(msg->data + i)); if ((i + 1) % 8 == 0) { printf("\n"); } } } extern "C" __declspec(dllexport) void PostMsg(MsgData* msg) { printf("index=%d\nmsg=\"%s\"\n", msg->index, msg->msg); dumpData(msg); }
2. 然后创建一个C#的控制台程序来调用它。这里通过DllImport特性来导入C++项目的PostMsg函数,同样也定义了对应的MsgData结构体,代码如下:
var msg = new Msg.MsgData() { Index = 2025, Msg = "custom msg", Data = [1,2,3,4,5,6,7,8] }; var ptr = Marshal.AllocHGlobal(Marshal.SizeOf<Msg.MsgData>()); Marshal.StructureToPtr(msg, ptr, false); Msg.PostMsg(ptr); internal class Msg { [DllImport("TestDll.dll", EntryPoint = "PostMsg")] public static extern void PostMsg(IntPtr msg); [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)] public struct MsgData { public int Index; [MarshalAs(UnmanagedType.ByValTStr,SizeConst = 32)] public string Msg; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] Data; } }
3. 接着将C++项目添加为C#项目的项目引用。
这里存在一个问题,C#项目不支持添加C++的项目引用,所以这里标了黄色感叹号,虽然构建C#项目的时候会先构建C++项目,但不会自动把C++项目的输出拷贝到C#项目的输出目录。
网上有很多方法能够解决把C++输出拷贝到C#输出目录的问题,我这里采用添加项目文件的方式,在C#的项目文件里添加以下内容:
<ItemGroup> <None Include="../x64/$(Configuration)/*.dll;../x64/$(Configuration)/*.pdb"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> </ItemGroup>
4. 最后看一下结果:
index=2025
msg="custom msg"
01 02 03 04 05 06 07 08
结果正常,这样就可以简单验证一下自己往C函数里封送的数据是不是正确了