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

如何将含string[]字段的结构体从C#封送到带C绑定的C++库?

C#向C/C++结构体封送string[]的问题与解决方案

问题场景

需要从C#向带有C接口的C++库传递Config结构体,移除stringArray字段时其余字段可正常传递,添加后程序崩溃。尝试使用[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)]封送stringArray仍无效,需解决以下问题:

  1. 封送string[]的代码存在什么错误?
  2. 是否可以将结构体中的string[]从C#封送到C/C++?
  3. 若不可行,有哪些替代方案?

原库代码

MyLib.h

#ifdef __cplusplus
extern "C"
{
#endif

#pragma pack(push, 1)

typedef struct _Config
{
    unsigned long arraySize;
    double* doubleArray;        // array of arraySize doubles
    const char* stringVar;
    const char** stringArray;   // array of arraySize strings as const char*
} Config;

#pragma pack(pop)

__declspec(dllexport) int SetConfig(Config config);

#ifdef __cplusplus
}
#endif

MyLib.cpp

#include "MyLib.h"
#include <vector>
#include <string>

int SetConfig(Config config)
{
    if (config.arraySize == 0)
        return -1;

    unsigned long N = config.arraySize;
    
    std::vector<double> doubles(N);
    for (unsigned long i = 0; i < N; i++)
        doubles[i] = config.doubleArray[i];
    
    std::string stringVar = config.stringVar;
    
    std::vector<std::string> strings(N);
    for (unsigned long i = 0; i < N; i++)
        strings[i] = config.stringArray[i];
    
    return 0;
}

原C#调用代码

namespace MySpace
{
    public class MyLib
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
        public struct Config
        {
            public uint arraySize;

            [MarshalAs(UnmanagedType.LPArray)] 
            public double[] doubleArray;    // array of arraySize doubles

            [MarshalAs(UnmanagedType.LPStr)]
            public string stringVar;

            [MarshalAs(UnmanagedType.LPArray)]
            public string[] stringArray;    // array of arraySize strings
        }

        public void Test_SetConfig()
        {
            Config config;
            config.arraySize = 3;
            config.doubleArray = new double[3] { 1.11, 2.22, 3.33 };
            config.stringVar = "str";
            config.stringArray = new string[3] { "str 1", "str 2", "str 3" };
            
            int result = MyLibNativeMethods.SetConfig(config);
            
            if (result != 0) 
                throw new Exception("Error in Test_SetConfig");
        }
    }

    public class MyLibNativeMethods
    {
        [DllImport("MyLib", CallingConvention = CallingConvention.Cdecl)]
        public static extern int SetConfig(MyLib.Config config);
    }
}

问题解答

1. 封送string[]的代码错误

原C#代码存在两个关键错误:

  • 未指定数组元素的封送类型:stringArray的MarshalAs仅用了UnmanagedType.LPArray,默认会将字符串封送为BStr(COM字符串类型),而C++期望的是ANSI编码的const char*,类型不匹配导致内存访问错误。
  • 未指定数组长度参数:封送器无法自动关联arraySize字段作为stringArray的长度,可能导致封送的数组长度不符合预期,触发越界访问。

2. 是否可以将结构体中的string[]从C#封送到C/C++?

完全可以,只要修正封送属性,确保类型和布局与C++结构体完全匹配。

修正后的C# Config结构体代码:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct Config
{
    public uint arraySize;

    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] 
    public double[] doubleArray;

    [MarshalAs(UnmanagedType.LPStr)]
    public string stringVar;

    [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 0)]
    public string[] stringArray;
}
  • SizeParamIndex = 0:告诉封送器用第0个字段(arraySize)的值作为数组的长度,确保数组元素数量正确。
  • ArraySubType = UnmanagedType.LPStr:明确指定每个字符串封送为ANSI编码的const char*,与C++类型匹配。

3. 替代方案

如果直接封送仍有问题(比如复杂场景下的内存管理),可以采用以下替代方案:

  • 手动管理非托管内存:用IntPtr代替string[],手动分配非托管内存存储const char**数组,每个字符串用Marshal.StringToHGlobalAnsi转换,调用完函数后释放内存。示例代码:
    public void Test_SetConfig_Manual()
    {
        uint arraySize = 3;
        double[] doubleArray = new double[] {1.11, 2.22, 3.33};
        string stringVar = "str";
        string[] stringArray = new string[] {"str1", "str2", "str3"};
    
        // 分配指针数组内存
        IntPtr stringPtrArray = Marshal.AllocHGlobal((int)arraySize * IntPtr.Size);
        try
        {
            // 逐个转换字符串并写入指针数组
            for (int i = 0; i < arraySize; i++)
            {
                IntPtr strPtr = Marshal.StringToHGlobalAnsi(stringArray[i]);
                Marshal.WriteIntPtr(stringPtrArray, i * IntPtr.Size, strPtr);
            }
    
            // 构建配置(用IntPtr代替string[])
            ConfigManual config;
            config.arraySize = arraySize;
            config.doubleArray = doubleArray;
            config.stringVar = stringVar;
            config.stringArray = stringPtrArray;
    
            int result = MyLibNativeMethods.SetConfigManual(config);
            if (result != 0) throw new Exception("Error");
        }
        finally
        {
            // 释放每个字符串的内存
            for (int i = 0; i < arraySize; i++)
            {
                IntPtr strPtr = Marshal.ReadIntPtr(stringPtrArray, i * IntPtr.Size);
                Marshal.FreeHGlobal(strPtr);
            }
            // 释放指针数组内存
            Marshal.FreeHGlobal(stringPtrArray);
        }
    }
    
    // 对应的结构体定义
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public struct ConfigManual
    {
        public uint arraySize;
        [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
        public double[] doubleArray;
        [MarshalAs(UnmanagedType.LPStr)]
        public string stringVar;
        public IntPtr stringArray; // 对应const char**
    }
    
  • 字符串拼接传递:将字符串数组用分隔符(如|)拼接成单个字符串,在C++中拆分,适合字符串不含分隔符的场景。
  • C++/CLI中间层:用C++/CLI编写包装类,直接处理托管string[]到非托管const char**的转换,简化C#端调用。
  • 传递结构体指针:将C#结构体封送到非托管内存,传递指针给C++函数,避免值传递时的复杂封送,调用后释放内存。

内容的提问来源于stack exchange,提问作者KoenT

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 18:35:53