Calling Go functions from C#
Tags: go, csharp
Published: 2023-06-04 15:58:19

## Intro
This time, I will call some Go functions from C#.
To do this, I should generate a Go dll file and call it from C#.

## Calling with int values
First, I will try sending an int value as an argument of a Go funtion and receiving int value from it.

#### [C#] CallSample.cs
```cs
using System.Runtime.InteropServices;

namespace CallDllSample;

public class CallSample
{
    [DllImport("dllsample")]
    private static extern int CallInt(int num);

    public int CallGoInt(int num)
    {
        return CallInt(num);
    }
}
```

#### [Go] main.go
```go
package main

import "C"

func main() { }

// publish functions by "//export ~"
//export CallInt
func CallInt(num int) int {
	return num + 3
}
```

### Build a dll file
```
go build -buildmode=c-shared -o dllsample.dll .
```

* [Go command - cmd/go - Go Packages](https://pkg.go.dev/cmd/go#hdr-Build_modes)

## Calling with string values
Because C#'s string type and Go's string type are not compatible, these codes cause an exception.

#### [C#] CallSample.cs
```cs
...
    [DllImport("dllsample")]
    private static extern string CallString(string text);
...
    public string CallGoString(string text)
    {
        return CallString(text);
    }
}
```

#### [Go] main.go
```go
...
// DON'T DO THIS
//export CallString
func CallString(text string) string {
	return fmt.Sprintf("%s World!", text)
}
```

#### Result
```
Fatal error. System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Repeat 2 times:
--------------------------------
   at CallDllSample.CallSample.CallString(System.String)
--------------------------------
   at CallDllSample.CallSample.CallGoString(System.String)
   at Program.<Main>$(System.String[])
```

To resolve that, I have to use "C.char".


#### [C#] CallSample.cs
```cs
...
    [DllImport("dllsample")]
    private static extern IntPtr CallString(string text);
...
    public string CallGoString(string text)
    {
        var result = CallString(text);
        Console.WriteLine(result);
        return Marshal.PtrToStringAnsi(result) ?? "";
    }
}
```

#### [Go] main.go
```go
...
//export CallString
func CallString(text *C.char) *C.char {
	gs := C.GoString(text)
	return C.CString(fmt.Sprintf("%s World!", gs))
}
```

* [cgo common - cmd/cgo - Go Packages](https://pkg.go.dev/cmd/cgo)
* [How to call go from c with string (char *) as the parameter without making a copy  - GitHub](https://gist.github.com/helinwang/2c7bd2867ea5110f70e6431a7c80cd9b)
* [GoからCのライブラリを呼ぶ - Qiita](https://qiita.com/toshikitsubouchi/items/251f9e69390e1a4260b9)

## Calling with arrays
To send int array to the Go function, I should convert it to IntPtr.
And to receive int array from the Go function, I should convert from IntPtr.

#### [C#] CallSample.cs
```cs
...
    public void CallGoArray()
    {
        // Convert from C# int array to IntPtr
        var nums = new int[]{ 4, 2, 5, 8 };
        IntPtr intPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * nums.Length);
        Marshal.Copy(nums, 0, intPtr, nums.Length);
        var pointerResult = CallArray(intPtr, nums.Length);

        // Convert from IntPtr to C# int array
        var results = new int[nums.Length];
        Marshal.Copy(pointerResult, results, 0, results.Length);

        for(var i = 0; i < results.Length; i++)
        {
            Console.WriteLine($"From Go Index: {i} Value: {results[i]}");
        }
    }
...
```

#### [Go] main.go
```go
...
//export CallArray
func CallArray(values *C.int, length C.int) *C.int {
	// Convert from C int array to Go int array
	cInts := (*[1 << 30]C.int)(unsafe.Pointer(values))[:length:length]

	goResults := make([]int, int(length))
	for i, v := range cInts {
		goResults[i] = int(v)
		log.Printf("From C# Index: %d Value: %d", int(i), int(v))
	}
	// Convert from Go int array to C int array
	results := C.malloc(C.size_t(length) * C.size_t(unsafe.Sizeof(uintptr(0))))
	pointerResult := (*[1 << 30]C.int)(results)
	for i := 0; i < int(length); i++ {
		pointerResult[i] = C.int(goResults[i] + 2)	
	}
	return (*C.int)(results)
}
```

* [Accessing C array in golang - StackOverflow](https://stackoverflow.com/questions/53238602/accessing-c-array-in-golang)
* [How to return go (array/slice/ist) to a C function - StackOverflow](https://stackoverflow.com/questions/65572429/how-to-return-go-array-slice-ist-to-a-c-function)
* [What does (*[1 << 30]C.YourType) do exactly in CGo? - StackOverflow](https://stackoverflow.com/questions/48756732/what-does-1-30c-yourtype-do-exactly-in-cgo)