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

如何将Go函数设为.NET AOT回调?参数异常问题排查与解决

问题

我用Go做主程序,搭配C# .NET AOT编译的共享库,需要实现两者函数互调——把Go函数作为回调传给C#并在C#中调用,但测试时回调无法正常工作。

测试用的Add(int a, int b)函数,输入1和2预期返回3,但实际结果错误。调试发现回调参数是异常数值,试过Stdcall和Cdecl两种调用约定都没解决。


测试代码、编译命令及输出

C#代码

using System.Runtime.InteropServices;

namespace CSharp_Go
{
    public unsafe class Export
    {
        private static delegate* unmanaged[Stdcall]<int, int, int> _addDel;
        [UnmanagedCallersOnly(EntryPoint = "SetAddFunc")]
        public static void SetAddFunc(delegate* unmanaged[Stdcall]<int, int, int> addDel)
        {
            _addDel = addDel;
        }

        private static delegate* unmanaged<int> _testFun;
        [UnmanagedCallersOnly(EntryPoint = "SetTestFunc")]
        public static void SetTestFunc(delegate* unmanaged<int> testFun)
        {
            _testFun = testFun;
        }

        [UnmanagedCallersOnly(EntryPoint = "Test")]
        public static int Test()
        {
            int res = _testFun();
            Console.WriteLine($"in c# Test res:{res}");
            return res;
        }

        [UnmanagedCallersOnly(EntryPoint = "Add")]
        public static int Add(int a, int b)
        {
            Console.WriteLine($"in c# Add a:{a}, b:{b}");
            int res = 0;
            if (null != _addDel)
            {
                res = _addDel(a, b);
                Console.WriteLine($"in c# Add res:{res}, a:{a}, b:{b}");
            }

            return res;
        }
    }
}

编译命令

dotnet publish -p:NativeLib=Shared -r win-x64 -c Debug

Go代码

package main

import (
    "C"
    "fmt"
    "reflect"
    "syscall"
    "unsafe"
)

func Sum(a, b int32) int32 {
    //fmt.Printf("a:%d, b:%d\n", a, b)
    res := a + b
    return res
}

func main() {
    f := Sum
    ptrValue := reflect.ValueOf(f)
    ptr := unsafe.Pointer(ptrValue.Pointer())
    addr := uintptr(ptr)
    fmt.Printf("Func Addr: %v\n", addr)

    var input string
    fmt.Scanln(&input)
    fmt.Println(input)

    var aValue int32 = int32(1)
    var bValue int32 = int32(2)
    var a uintptr = uintptr(aValue)
    var b uintptr = uintptr(bValue)

    ptrVa := &aValue
    ptrA := &a
    fmt.Printf("va:%v, a: %v\n", ptrVa, ptrA)
    t := func() int32 {
        //fmt.Println(aValue, bValue)
        //pa := (*int32)(unsafe.Pointer(uintptr(aValue)))
        //a := *pa
        return aValue + bValue
    }
    ptrT := uintptr(unsafe.Pointer(reflect.ValueOf(t).Pointer()))
    fmt.Printf("Func Addr: %v\n", ptrT)

    fmt.Println("Hello go c#")
    maindll := syscall.NewLazyDLL("CSharp_Go.dll")

    setTestFunc := maindll.NewProc("SetTestFunc")
    test := maindll.NewProc("Test")

    //cb := syscall.NewCallback(t)
    r1, r2, err := setTestFunc.Call(ptrT)
    fmt.Println(r1, r2, err)

    r1, r2, err = test.Call()
    fmt.Println(r1, r2, err)

    setAddFunc := maindll.NewProc("SetAddFunc")
    add := maindll.NewProc("Add")

    r1, r2, err = setAddFunc.Call(addr)
    fmt.Println(r1, r2, err)

    r1, r2, err = add.Call(a, b)

    fmt.Println(r1, r2, err)
    fmt.Scanln(&input)
    fmt.Println(input)
}

输出日志

Func Addr: 15405888
6
6
va:0xc00000e128, a: 0xc00000e130
Func Addr: 15410016
Hello go c#
2259596893072 2260255909544 The operation completed successfully.
in c# Test res:12144
12144 0 The operation completed successfully.
2259596893072 15405888 The operation completed successfully.
in c# Add a:1, b:2
in c# Add res:31533024, a:1, b:2
31533024 0 The operation completed successfully.


原因分析
  • ABI不兼容:Go的函数调用约定、栈结构和C/C#完全不同,直接传递Go原生函数指针给C#,会导致C#按自身约定解析参数时出现栈平衡错误,参数值被篡改。
  • 错误的指针获取方式:reflect.ValueOf(f).Pointer()拿到的是Go内部的函数入口,并非符合C/C#调用约定的标准指针,不能直接给非Go代码调用。
  • 调用约定不匹配:C#声明的delegate* unmanaged[Stdcall]<...>要求函数遵循Stdcall约定,但Go原生函数不支持该约定,参数传递逻辑完全错位。

解决方案

步骤1:用syscall.NewCallback包装Go函数

Go的syscall.NewCallback会将Go函数转换为符合Windows Stdcall约定的函数指针,让C#可以正确调用。修改Go代码:

package main

import (
    "C"
    "fmt"
    "syscall"
)

// 符合C#回调签名的Go函数
func Sum(a int, b int) int {
    fmt.Printf("Go Sum called with a: %d, b: %d\n", a, b)
    return a + b
}

func TestFunc() int {
    return 3
}

func main() {
    maindll := syscall.NewLazyDLL("CSharp_Go.dll")

    // 注册TestFunc回调
    setTestFunc := maindll.NewProc("SetTestFunc")
    testCb := syscall.NewCallback(TestFunc)
    _, _, err := setTestFunc.Call(testCb)
    fmt.Println("SetTestFunc result:", err)

    test := maindll.NewProc("Test")
    r1, _, err := test.Call()
    fmt.Println("Test result:", r1, err)

    // 注册Sum回调
    setAddFunc := maindll.NewProc("SetAddFunc")
    sumCb := syscall.NewCallback(Sum)
    _, _, err = setAddFunc.Call(sumCb)
    fmt.Println("SetAddFunc result:", err)

    add := maindll.NewProc("Add")
    r1, _, err = add.Call(uintptr(1), uintptr(2))
    fmt.Println("Add result:", r1, err)

    var input string
    fmt.Scanln(&input)
}

步骤2:统一C#的调用约定

修改C#代码,确保所有回调委托都明确使用Stdcall约定:

using System.Runtime.InteropServices;

namespace CSharp_Go
{
    public unsafe class Export
    {
        private static delegate* unmanaged[Stdcall]<int, int, int> _addDel;
        [UnmanagedCallersOnly(EntryPoint = "SetAddFunc")]
        public static void SetAddFunc(delegate* unmanaged[Stdcall]<int, int, int> addDel)
        {
            _addDel = addDel;
        }

        private static delegate* unmanaged[Stdcall]<int> _testFun;
        [UnmanagedCallersOnly(EntryPoint = "SetTestFunc")]
        public static void SetTestFunc(delegate* unmanaged[Stdcall]<int> testFun)
        {
            _testFun = testFun;
        }

        [UnmanagedCallersOnly(EntryPoint = "Test")]
        public static int Test()
        {
            int res = _testFun();
            Console.WriteLine($"in c# Test res:{res}");
            return res;
        }

        [UnmanagedCallersOnly(EntryPoint = "Add")]
        public static int Add(int a, int b)
        {
            Console.WriteLine($"in c# Add a:{a}, b:{b}");
            int res = 0;
            if (null != _addDel)
            {
                res = _addDel(a, b);
                Console.WriteLine($"in c# Add res:{res}, a:{a}, b:{b}");
            }

            return res;
        }
    }
}

步骤3:重新编译测试

  1. 编译C#库:保持原命令
dotnet publish -p:NativeLib=Shared -r win-x64 -c Debug
  1. 编译Go程序(Windows环境需启用cgo):
set CGO_ENABLED=1
set GOOS=windows
set GOARCH=amd64
go build -o go_csharp.exe main.go
  1. 运行go_csharp.exe,会得到正确输出:

SetTestFunc result: The operation completed successfully.
in c# Test res:3
Test result: 3 The operation completed successfully.
SetAddFunc result: The operation completed successfully.
in c# Add a:1, b:2
Go Sum called with a: 1, b: 2
in c# Add res:3, a:1, b:2
Add result: 3 The operation completed successfully.


关键说明
  • syscall.NewCallback是Windows平台下Go与C#互调回调的核心,它负责将Go函数适配为符合Stdcall约定的可调用指针。
  • 必须保证C#的委托声明和Go函数的签名、调用约定完全一致,否则参数传递必然出错。
  • 复杂场景下可结合cgo导出符合C ABI的函数,进一步提升兼容性和稳定性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 17:24:53