Search code examples
c#.netgonative-aot

How set a Go function as a callback function to .NET AOT


I have a project at work. The main program is written in Go, and there is a shared library written in C# .NET AOT.

In the project, functions need to be called between the Go code and C# .NET AOT.

The specific content is to pass a Go function to C# as a callback function and call it in C#. But when I tested, I found that the function didn't work properly.

Here is my test code:

In 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;
        }
    }
}

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

The Go code:

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)
}

I implemented a simple Add(int a, int b) function for testing. The input parameters were 1 and 2; the result should be 3, but it was not. When I was debugging, I found that the parameter list of the callback function was not 1 and 2, but some strange numbers. And I tried two calling conventions, Stdcall and Cdecl, but they couldn't solve this problem.

What is the reason for this and how to solve it?

Debug

Here is the full output log:

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.


Solution

  • It needs to use cgo to export the Go function:

    package main
    
    /*
    extern int sum(int, int);
    //static inline void CallMyFunction(int a, int b) {
    //    sum(a, b);
    //}
    */
    import "C"
    import (
        "fmt"
        "reflect"
        "syscall"
        "unsafe"
    )
    
    // Export sum
    func sum(a, b C.int) C.int {
        res := a + b
        fmt.Println(a, "+", b , "=", res )
        return res
    }
    
    func main() {
        fmt.Println("Hello, Go C#")
        var input string
        fmt.Scanln(&input)
        fmt.Println(input)
    
        //C.CallMyFunction(3, 4)
    
        var aValue int32 = int32(3)
        var bValue int32 = int32(4)
        var a uintptr = uintptr(aValue)
        var b uintptr = uintptr(bValue)
    
        f := C.sum
        ptrValue := reflect.ValueOf(f)
        ptr := unsafe.Pointer(ptrValue.Pointer())
        addr := uintptr(ptr)
        fmt.Printf("Func Addr: %v\n", addr)
    
        maindll := syscall.NewLazyDLL("CSharp_Go.dll")
        //maindll := syscall.NewLazyDLL("Cpp_Go.dll")
        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)
    }
    

    The output is:

    Func Addr: 13049808

    1640739134368 1641397561016 The operation completed successfully.

    in C# Add a:3, b:4

    3 + 4 = 7

    in C# Add res:7, a:3, b:4

    7 0 The operation completed successfully.