C++与C#基于Protobuf双向通信:输出字节数组实现方案问询
C#与C++ Protobuf双向通信(动态输出缓冲区)解决方案
针对你提到的"无法预先知晓输出Protobuf数组大小"的问题,推荐两种成熟的实现方案,以下是具体代码和说明:
方案一:两次调用(内存安全,优先推荐)
核心逻辑是分两步完成通信:
- 第一次调用传入空缓冲区,让C++计算并返回输出Protobuf所需的字节数
- C#根据返回值分配数组,第二次调用传入数组获取序列化后的数据
C++端修改
// sender.hpp extern "C" bool __declspec(dllexport) CppFunction(const void* bin, std::size_t size_bin, void* bout, std::size_t* size_bout);
// sender.cpp bool CppFunction(const void* bin, std::size_t size_bin, void* bout, std::size_t* size_bout) { // 解析输入Protobuf消息 std::unique_ptr<MyProtobufMessage> input_proto = std::make_unique<MyProtobufMessage>(); if (!input_proto->ParseFromArray(bin, size_bin)) { return false; } // 生成输出Protobuf消息(这里替换为你的业务逻辑) MyProtobufOutputMessage output_proto; // output_proto.set_xxx(...); 填充输出数据 // 第一步:获取所需缓冲区大小 if (bout == nullptr) { *size_bout = output_proto.ByteSizeLong(); return true; } // 第二步:序列化到指定缓冲区 if (*size_bout < output_proto.ByteSizeLong()) { *size_bout = output_proto.ByteSizeLong(); // 返回实际需要的大小 return false; } return output_proto.SerializeToArray(bout, *size_bout); }
C#端修改
public static class CppWrapper { private const string InterfaceLibrary = "MyCppDll.dll"; [DllImport(InterfaceLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CppFunction"), SuppressUnmanagedCodeSecurity] public static extern bool CppFunction([In] byte[] bytes, [In] int size, IntPtr bout, ref int sizeBout); } // 调用逻辑 MyProtobufMessage inputProto = new(); // inputProto.Xxx = ...; 填充输入数据 byte[] inputBytes = inputProto.ToByteArray(); // 第一步:获取输出数据所需大小 int outputSize = 0; bool getSizeSuccess = CppWrapper.CppFunction(inputBytes, inputBytes.Length, IntPtr.Zero, ref outputSize); if (!getSizeSuccess || outputSize <= 0) { // 处理大小获取失败的情况 return; } // 分配输出数组 byte[] outputBytes = new byte[outputSize]; // 第二步:获取输出数据 GCHandle outputHandle = GCHandle.Alloc(outputBytes, GCHandleType.Pinned); try { bool getDataSuccess = CppWrapper.CppFunction(inputBytes, inputBytes.Length, outputHandle.AddrOfPinnedObject(), ref outputSize); if (getDataSuccess) { MyProtobufOutputMessage outputProto = MyProtobufOutputMessage.Parser.ParseFrom(outputBytes); // 处理outputProto的业务逻辑 } else { // 处理序列化失败(比如缓冲区大小仍不足) } } finally { outputHandle.Free(); // 释放内存锁定 }
方案二:C++分配内存,C#使用后释放
如果不想做两次调用,可以让C++负责分配内存,C#使用完成后调用专门的释放函数回收内存(需严格注意内存泄漏问题)
C++端修改
// sender.hpp extern "C" bool __declspec(dllexport) CppFunction(const void* bin, std::size_t size_bin, void** bout, std::size_t* size_bout); extern "C" void __declspec(dllexport) FreeCppBuffer(void* ptr);
// sender.cpp bool CppFunction(const void* bin, std::size_t size_bin, void** bout, std::size_t* size_bout) { // 解析输入消息 std::unique_ptr<MyProtobufMessage> input_proto = std::make_unique<MyProtobufMessage>(); if (!input_proto->ParseFromArray(bin, size_bin)) { return false; } // 生成输出消息 MyProtobufOutputMessage output_proto; // output_proto.set_xxx(...); 填充数据 std::size_t requiredSize = output_proto.ByteSizeLong(); void* buffer = malloc(requiredSize); // 用malloc分配,方便C#端释放 if (!buffer) { return false; } if (!output_proto.SerializeToArray(buffer, requiredSize)) { free(buffer); return false; } *bout = buffer; *size_bout = requiredSize; return true; } void FreeCppBuffer(void* ptr) { if (ptr) { free(ptr); } }
C#端修改
public static class CppWrapper { private const string InterfaceLibrary = "MyCppDll.dll"; [DllImport(InterfaceLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CppFunction"), SuppressUnmanagedCodeSecurity] public static extern bool CppFunction([In] byte[] bytes, [In] int size, out IntPtr bout, out int sizeBout); [DllImport(InterfaceLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "FreeCppBuffer"), SuppressUnmanagedCodeSecurity] public static extern void FreeCppBuffer(IntPtr ptr); } // 调用逻辑 MyProtobufMessage inputProto = new(); // inputProto.Xxx = ...; 填充数据 byte[] inputBytes = inputProto.ToByteArray(); IntPtr outputPtr = IntPtr.Zero; int outputSize = 0; bool success = CppWrapper.CppFunction(inputBytes, inputBytes.Length, out outputPtr, out outputSize); if (success && outputPtr != IntPtr.Zero && outputSize > 0) { try { byte[] outputBytes = new byte[outputSize]; Marshal.Copy(outputPtr, outputBytes, 0, outputSize); MyProtobufOutputMessage outputProto = MyProtobufOutputMessage.Parser.ParseFrom(outputBytes); // 处理outputProto业务逻辑 } finally { CppWrapper.FreeCppBuffer(outputPtr); // 必须释放C++分配的内存,避免泄漏 } } else { // 处理调用失败情况 }
方案对比
- 方案一:内存完全由各自语言管理,无泄漏风险,兼容性强;仅需两次函数调用,性能损耗可忽略(远低于Protobuf序列化开销)。
- 方案二:单次调用即可完成通信,适合对调用次数敏感的场景;但必须严格保证内存释放,否则会造成泄漏,维护成本较高。
内容的提问来源于stack exchange,提问作者loyd.f
相关产品推荐
相关产品推荐

