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

如何在C#中向CGO编译的Go函数传递[]string类型的GoSlice

解决CGO编译Go库供C#调用时传递[]string的问题

问题根源

Go的[]string在CGO导出时,对应的GoSlice内部存储的是Go字符串结构体(包含指针和长度),而非C风格的char*数组。你之前成功调用IntArray是因为[]int的元素是基本类型,可直接与C#的数值数组映射,但[]string需要特殊处理。


方案一:修改Go导出函数,适配C风格字符串数组(推荐)

这种方式更简单,避免直接处理Go内部结构体,降低出错概率。

1. Go端代码调整

修改导出函数,接收C风格的字符串指针数组和长度,再转换为Go的[]string:

package main

import "C"
import "fmt"
import "unsafe"

//export StringArray
func StringArray(strs **C.char, len C.int) {
    goStrs := make([]string, len)
    for i := 0; i < int(len); i++ {
        // 遍历指针数组,转换为Go字符串
        ptr := (*C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(strs)) + uintptr(i)*unsafe.Sizeof(*strs)))
        goStrs[i] = C.GoString(ptr)
    }
    fmt.Println("Received strings:", goStrs)
}

//export IntArray
func IntArray(slice []int) {
    fmt.Println("Received ints:", slice)
}

func main() {}

2. 编译Go库

执行命令生成C共享库(以Windows为例):

go build -buildmode=c-shared -o goarray.dll goarray.go

3. C#端调用代码

通过Marshal类将C#的string[]转换为C风格的char*数组:

using System;
using System.Runtime.InteropServices;

class Program
{
    [DllImport("goarray.dll")]
    private static extern void StringArray(IntPtr strs, int len);

    [DllImport("goarray.dll")]
    private static extern void IntArray(GoSlice slice);

    // 定义GoSlice结构用于IntArray调用
    [StructLayout(LayoutKind.Sequential)]
    public struct GoSlice
    {
        public IntPtr data;
        public long len;
        public long cap;
    }

    static void Main()
    {
        // 调用IntArray(原成功逻辑)
        int[] ints = { 1, 2, 3 };
        var intSlice = new GoSlice
        {
            data = Marshal.UnsafeAddrOfPinnedArrayElement(ints, 0),
            len = ints.Length,
            cap = ints.Length
        };
        IntArray(intSlice);

        // 调用StringArray
        string[] strs = { "hello", "world", "CGO&C#" };
        IntPtr[] charPtrs = new IntPtr[strs.Length];

        // 将每个C#字符串转换为C风格char*
        for (int i = 0; i < strs.Length; i++)
        {
            charPtrs[i] = Marshal.StringToHGlobalAnsi(strs[i]);
        }

        // 传递char*数组的首地址和长度
        StringArray(Marshal.UnsafeAddrOfPinnedArrayElement(charPtrs, 0), strs.Length);

        // 释放内存,避免泄漏
        foreach (var ptr in charPtrs)
        {
            Marshal.FreeHGlobal(ptr);
        }
    }
}

方案二:直接构造Go字符串结构体数组(不修改Go端)

如果必须保留原Go函数的[]string参数,需在C#端构造符合Go字符串结构的数组,再封装为GoSlice。

1. Go端原代码(无需修改)

package main

import "C"
import "fmt"

//export StringArray
func StringArray(slice []string) {
    fmt.Println("Received strings:", slice)
}

//export IntArray
func IntArray(slice []int) {
    fmt.Println("Received ints:", slice)
}

func main() {}

2. C#端调用代码

定义Go字符串结构体,手动转换并构造GoSlice:

using System;
using System.Runtime.InteropServices;

class Program
{
    [DllImport("goarray.dll")]
    private static extern void StringArray(GoSlice slice);

    [DllImport("goarray.dll")]
    private static extern void IntArray(GoSlice slice);

    [StructLayout(LayoutKind.Sequential)]
    public struct GoSlice
    {
        public IntPtr data;
        public long len;
        public long cap;
    }

    // 定义Go字符串结构体(与Go内部string结构一致)
    [StructLayout(LayoutKind.Sequential)]
    public struct GoString
    {
        public IntPtr p; // 字符串指针
        public long n;   // 字符串长度(UTF-8字节数)
    }

    static void Main()
    {
        // 调用StringArray
        string[] strs = { "hello", "world", "中文测试" };
        GoString[] goStrings = new GoString[strs.Length];

        for (int i = 0; i < strs.Length; i++)
        {
            // 将C#字符串转为UTF-8字节数组
            byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(strs[i]);
            // 分配非托管内存存储字节
            IntPtr strPtr = Marshal.AllocHGlobal(utf8Bytes.Length);
            Marshal.Copy(utf8Bytes, 0, strPtr, utf8Bytes.Length);
            
            goStrings[i] = new GoString
            {
                p = strPtr,
                n = utf8Bytes.Length
            };
        }

        // 构造GoSlice
        var strSlice = new GoSlice
        {
            data = Marshal.UnsafeAddrOfPinnedArrayElement(goStrings, 0),
            len = goStrings.Length,
            cap = goStrings.Length
        };

        StringArray(strSlice);

        // 释放内存
        foreach (var gs in goStrings)
        {
            Marshal.FreeHGlobal(gs.p);
        }
    }
}

关键注意事项

  1. 平台一致性:Go库的编译平台(32位/64位)必须与C#项目的目标平台一致,否则会出现内存访问错误。例如编译64位Go库:GOARCH=amd64 go build ...,C#项目设为x64。
  2. 内存管理:所有通过Marshal.AllocHGlobal分配的非托管内存,必须手动释放,避免内存泄漏。
  3. 编码问题:Go字符串默认是UTF-8编码,C#转换时需使用Encoding.UTF8,避免中文乱码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 18:24:29