You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++与C#基于Protobuf双向通信:输出字节数组实现方案问询

C#与C++ Protobuf双向通信(动态输出缓冲区)解决方案

针对你提到的"无法预先知晓输出Protobuf数组大小"的问题,推荐两种成熟的实现方案,以下是具体代码和说明:

方案一:两次调用(内存安全,优先推荐)

核心逻辑是分两步完成通信:

  1. 第一次调用传入空缓冲区,让C++计算并返回输出Protobuf所需的字节数
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 22:41:17