C# calls a golang function that returns a struct

Viewed 448

I've been trying to make interop calls from C# to a function in a go lib, that returns a struct. If I understand correct you have to use CGO in go to be able to return more advanced data types.

I have successfully made calls on methods that returns a string, methods that return structs looks harder solve.

Added some sample code below. The output tells that there are some marshaling problems.

Any suggestions? Thanks!

Output:

[go] Inspect
[go]  main._Ctype_struct_foobar
[go]  0 : {A  main._Ctype_long  0 [0] false} , 0
[go]  1 : {B  *main._Ctype_char  8 [1] false} , 8
[C#] GetStruct
System.Runtime.InteropServices.MarshalDirectiveException: Cannot marshal 'return value': Invalid managed/unmanaged type combination (Int/UInt must be paired with SysInt or SysUInt).
   at pinpoint.Program.GetStruct()
   at pinpoint.Program.Main(String[] args) in /home/me/examples/pinpoint/Program.cs:line 14

Build/Run:

> env GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -o golib.so -buildmode=c-shared golib.go
> dotnet run

The GO code:

package main

/*
#include <stdint.h>

struct foobar {
    int64_t A;
    char*   B;
};

*/
import "C"

import (
    "fmt"
    "reflect"
)

//export Inspect
func Inspect() {
    fmt.Println("[go] Inspect")
    var foo = C.struct_foobar{}
    t := reflect.TypeOf(foo)
    fmt.Println("[go] ", t)
    for i := 0; i < t.NumField(); i++ {
        fmt.Println("[go] ", i, ":", t.Field(i), ",", t.Field(i).Offset)
    }
}

//export GetStruct
func GetStruct(id string) *C.struct_foobar {
    fmt.Println("[go] GetStruct")
    var A int64 = 34
    var B string = "AB"

    var foo = C.struct_foobar{}
    foo.A = C.int64_t(A)
    foo.B = C.CString(B)

    return &foo
}

func main() {}

The C# code:

using System;
using System.Runtime.InteropServices;

namespace pinpoint
{
    class Program
    {
        static void Main(string[] args)
        {
            try {
            Inspect();
            Console.WriteLine("[C#] GetStruct");
            var ptr = GetStruct();
            Console.WriteLine("[C#] Do marshal");
            var foo = (Foo) Marshal.PtrToStructure(ptr, typeof(Foo));
            Console.WriteLine($"[C#] foo = {foo}");
            // FreeMem()
            } catch (Exception e) {
                Console.WriteLine(e);
            }
        }

        // C type struct { int64_t A; char* B; }
        [StructLayout(LayoutKind.Sequential)]
        public struct Foo {
            [MarshalAs(UnmanagedType.I8)]
            public Int64 A;
            [MarshalAs(UnmanagedType.LPTStr)]
            public string B;
        } 

        [DllImport("./golib.so", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        public static extern void Inspect();

        [DllImport("./golib.so", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.LPStruct)]
        public static extern IntPtr GetStruct();
    }
}
1 Answers

finally, something that's running. (Answer myself if someone else might find it useful)

When I removed [return: MarshalAs(UnmanagedType.LPStruct)].
It crashes with this output: "panic: runtime error: cgo result has Go pointer"

Which pointed me to this post: https://groups.google.com/forum/#!topic/golang-nuts/uW9ehN4uXrM

Some re-arrangement:

C#

[DllImport("./golib.so", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern IntPtr GetStruct();

Go

//export GetStruct
func GetStruct(id string) *C.struct_foobar {
    fmt.Println("[go] GetStruct")
    var A int64 = 34
    var B string = "ABCD"

    foo := (*C.struct_foobar) (C.malloc(C.size_t(unsafe.Sizeof(C.struct_foobar{}))))
    foo.A = C.int64_t(A)
    foo.B = C.CString(B)
    return foo
}
Related