如何在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); } } }
关键注意事项
- 平台一致性:Go库的编译平台(32位/64位)必须与C#项目的目标平台一致,否则会出现内存访问错误。例如编译64位Go库:
GOARCH=amd64 go build ...,C#项目设为x64。 - 内存管理:所有通过
Marshal.AllocHGlobal分配的非托管内存,必须手动释放,避免内存泄漏。 - 编码问题:Go字符串默认是UTF-8编码,C#转换时需使用
Encoding.UTF8,避免中文乱码。
内容的提问来源于stack exchange,提问作者user8973449
相关产品推荐
相关产品推荐

