在C#中嵌入Golang函数的实现方法

在C#中嵌入Golang函数的实现方法 不确定这是否是正确的地方 🤔,因为我不确定这涉及 Go 还是 C#。

无论如何,我需要将我的 Go 应用程序嵌入到一个大型 C# 项目中。 我能够嵌入大部分函数,例如:

//PrintHello :
//export PrintHello
func PrintHello(s string) {
	fmt.Println(s)

}

然后在 C# 中我会这样使用:

[DllImport("main", EntryPoint = "PrintHello")]
extern static void PrintHello(string s);

PrintHello("Go rules!!!!");

这工作得很好,但是当我的 Go 函数接收一个切片作为输入,而从 C# 传入一个数组时,我遇到了问题。 例如:

//Sum :
//export Sum
func Sum(a []int) int {
	return a[0] + a[1]
}

[DllImport("main", EntryPoint = "Sum")]
extern static int Sum(int[] array);


var numlis = new List<int>();
numlis.Add(1);
numlis.Add(5);
int[] array = numlis.ToArray();

int c = Sum(array);
Console.WriteLine("Call Go func Sum, result is " + c);

我得到:

程序 ‘[15300] InitialConnect.exe’ 已退出,代码为 2 (0x2)。

我做错了什么?


更多关于在C#中嵌入Golang函数的实现方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

这可能没有帮助,但或许可以尝试在你的 DllImportAttribute 中指定调用约定:

[DllImport("main", EntryPoint = "Sum", CallingConvention=CallingConvention.Cdecl)]
extern static int Sum(int[] array);

更多关于在C#中嵌入Golang函数的实现方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


与你的导入问题无关(看起来你已经得到了帮助),但你可以简化这段代码:

var numlis = new List<int>();
numlis.Add(1);
numlis.Add(5);
int[] array = numlis.ToArray();

…为单行代码:

int[] array = { 1, 5 };

除非你确实需要,否则没有必要创建一个列表。

我刚刚做了一个小测试,.h 文件中包含了以下代码:

typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
extern __declspec(dllexport) void DoubleSlice(GoSlice s);

另一种方法可能是,与其在 Go 代码中创建切片,不如在你的 C# 代码中创建 GoSlice 结构体。无论哪种方式,这都不会是透明/自动的。

你生成了 .h 文件吗?如果生成了,请检查其中 Sum 函数的签名。C# 不太可能自动生成正确的结构体来表示 Go 的切片。我曾经制作过一个供 Python 调用的共享库,那时我不得不将那些操作切片的函数进行包装,包装后的函数接受指针和长度作为参数,就像在 C 语言中通常做的那样。这些包装函数在调用原本接受切片参数的原始函数之前,会使用指针和长度来创建切片头,就像这样:

	header := (*reflect.SliceHeader)(unsafe.Pointer(&out))
	header.Data = uintptr(unsafe.Pointer(ptr))
	header.Len = n
	header.Cap = n

这里的 out 是切片变量。

感谢您的提示,这帮助我解决了问题。 确实,我必须创建一个 C# 结构体来容纳 Go 切片。

extern static int Sum(GoSlice vals);

		struct GoSlice
		{
			public IntPtr data;
			public Int64 len;
			public Int64 cap;
		}

经过一番研究,并借助这里的帮助,我得以完成任务。这允许将数组作为 Go 切片传递给 Go 函数。

			GCHandle h = GCHandle.Alloc(array, GCHandleType.Pinned);
			GoSlice gs = new GoSlice
			{
				data = h.AddrOfPinnedObject(),
				cap = n,
				len = n
			};

在C#中调用接收切片的Go函数时,需要理解Go的切片在C接口中的内存布局。Go的切片实际上是一个包含三个字段的结构体:

type slice struct {
    data uintptr
    len  int
    cap  int
}

你需要传递这个结构体,而不是直接传递C#数组。以下是正确的实现方法:

Go代码:

package main

import "C"
import "fmt"

//export PrintHello
func PrintHello(s string) {
    fmt.Println(s)
}

// SumSlice 接收Go切片结构体
//export SumSlice
func SumSlice(ptr *C.int, length C.int) C.int {
    // 将C指针转换为Go切片
    slice := (*[1 << 30]C.int)(unsafe.Pointer(ptr))[:length:length]
    
    var sum C.int = 0
    for i := 0; i < int(length); i++ {
        sum += slice[i]
    }
    return sum
}

// Sum 接收完整的切片描述符(用于演示)
//export Sum
func Sum(ptr unsafe.Pointer, len int) int {
    // 创建Go切片
    slice := (*[1 << 30]int)(ptr)[:len:len]
    
    sum := 0
    for _, v := range slice {
        sum += v
    }
    return sum
}

func main() {}

C#代码:

using System;
using System.Runtime.InteropServices;

class Program
{
    // 简单字符串函数
    [DllImport("main", EntryPoint = "PrintHello", CharSet = CharSet.Ansi)]
    extern static void PrintHello(string s);
    
    // 方法1:传递指针和长度
    [DllImport("main", EntryPoint = "SumSlice")]
    extern static int SumSlice(IntPtr arrayPtr, int length);
    
    // 方法2:使用结构体传递完整的切片描述符
    [StructLayout(LayoutKind.Sequential)]
    struct GoSlice
    {
        public IntPtr data;     // 指向数据的指针
        public long len;        // 长度
        public long cap;        // 容量
    }
    
    [DllImport("main", EntryPoint = "Sum")]
    extern static int Sum(GoSlice slice);
    
    static void Main()
    {
        PrintHello("Go rules!!!!");
        
        // 方法1:传递指针和长度
        int[] array1 = { 1, 5, 3, 7 };
        IntPtr ptr = Marshal.AllocHGlobal(array1.Length * sizeof(int));
        Marshal.Copy(array1, 0, ptr, array1.Length);
        
        int result1 = SumSlice(ptr, array1.Length);
        Console.WriteLine($"SumSlice result: {result1}");
        
        Marshal.FreeHGlobal(ptr);
        
        // 方法2:使用GoSlice结构体
        int[] array2 = { 1, 5, 3, 7 };
        IntPtr ptr2 = Marshal.AllocHGlobal(array2.Length * sizeof(int));
        Marshal.Copy(array2, 0, ptr2, array2.Length);
        
        GoSlice slice = new GoSlice
        {
            data = ptr2,
            len = array2.Length,
            cap = array2.Length
        };
        
        int result2 = Sum(slice);
        Console.WriteLine($"Sum result: {result2}");
        
        Marshal.FreeHGlobal(ptr2);
    }
}

编译Go代码:

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

关键点:

  1. Go切片在C接口中不是简单的指针,而是包含指针、长度和容量的结构体
  2. 需要在C#中定义匹配的GoSlice结构体
  3. 或者更简单的方法是分别传递数据指针和长度
  4. 必须正确管理内存分配和释放

对于你的具体问题,错误代码2通常表示访问了无效的内存地址,这是因为C#数组没有被正确转换为Go切片结构体。使用上述方法可以解决这个问题。

回到顶部