Search code examples
c#gocgo

CGO C# string array to GO slice


I'm compiling a C library from GO code, using CGO. The libraries functions are then called from C#.

In this GO code I have a function that expects a []string input, such as: func StringArray(strings []string)

I also have another function that expects an []int input, such as: func IntArray(vals []int)

If I look at the generated header file, I can see the following for the above functions:

extern __declspec(dllexport) void IntArray(GoSlice vals);
extern __declspec(dllexport) void StringArray(GoSlice strings);

I can successfully call the IntArray function from C#, by creating the following struct:

internal struct GoSlice
{
    public IntPtr data;
    public long len, cap;
    public GoSlice(IntPtr data, long len, long cap)
    {
        this.data = data;
        this.len = len;
        this.cap = cap;
    }
}

And then call the function like so:

long[] data = { 1, 2, 3, 4, 5, 6 };
IntPtr data_ptr = Marshal.AllocHGlobal(Buffer.ByteLength(data));
Marshal.Copy(data, 0, data_ptr, data.Length);
var nums = new GoSlice(data_ptr, data.Length, data.Length);
IntArray(nums);
Marshal.Copy(nums.data, data, 0, data.Length);

I can also successfully call functions expecting a string input, by creating the following struct:

internal struct GoString
{
    public string msg;
    public long len;
    public GoString(string msg, long len)
    {
        this.msg = msg;
        this.len = len;
    }
}

And then just call the function like so:

string inputString = "Test";
GoString goString = new GoString(inputString, inputString.Length);

StringInput(goString);

What I struggle to achieve, is to pass the expected []string GoSlice to the StringArray function. Any suggestions? I need the GoSlice to include strings and not integers.

I've tried, in various ways, to pass strings to the GoSlice instead of integers which didn't work with mixed results. I expected to end up with a []string GoSlice which could be used when calling the "CGO compiled" GO function from C#.


Solution

  • Thanks to Liams input, I managed to come up with the following solution.

    Exported GO function:

    //export Parse22Strings
    func Parse22Strings(argv **C.char, argc C.int) {
        length := int(argc)
        tmpslice := unsafe.Slice(argv, length)
        gostrings := make([]string, length)
        for i, s := range tmpslice {
            gostrings[i] = C.GoString(s)
        }
    }
    

    Since I'm using version 1.20.1 of GO, I actually changed:

    tmpslice := (*[1 << 30]*C.char)(unsafe.Pointer(argv))[:length:length]
    

    To:

    tmpslice := unsafe.Slice(argv, length)
    

    As per the documentation from the CGO wiki here: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices

    C# Platform Invoke (P/Invoke):

    [DllImport("shared.dll", CallingConvention = CallingConvention.Cdecl)]
    internal static extern void Parse22Strings(string[] argv, int argc);
    

    C# library call:

    string[] strArray = {"one", "two", "three", "four", "five", "six", "seven", "..."};
    Parse22Strings(strArray, strArray.Length);