简单写一个C函数供C#代码调用


在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函数里封送的数据是不是正确了