Проблема
Проблема в том, что хотя -buildmode=c-shared
(и -buildmode=c-whatever
в целом) действительно делают символы Go вызываемыми из C, это происходит с поворотом, когда дело доходит до strings.
В C действительно нет такой вещи, как строка, но существует соглашение, что строка является указателем на ее первый байт, а длина строки неявно определяется байтом с кодом 0 (ASCII NUL
) в этой строке.
В Go строки представляют собой struct
s из двух полей: указатель на блок памяти, содержащий содержимое строки, и количество байтов в этом блоке.
Как вы можете видеть, когда набор инструментов Go компилирует функцию Go, помеченную как "экспортированная в C", он в основном имеет два варианта:
- Сделать функцию вызываемой «как есть», требуя от вызывающих абонентов передать значение Go в стиле
struct
, описывающее строку. - Искусственно «обернуть» экспортированную функцию в код блок, который будет принимать "C стиль" NUL-концевой строки ng, посчитайте количество байтов в нем, приготовьте значение Go -тиля
struct
и, наконец, вызовите исходную функцию.
Второй подход может быть проще для не Go с программистами приходится иметь дело, но существуют два аргумента против его использования:
- Этот подход требует скрытых затрат для каждого вызова: сканирование байтов строки - даже если вызывающий знает это.
- Возможно - если необходимо - создать такую обертку прямо в коде Go - как @Selvin предложил в своем комментарии к вопросу.
Итак, еще раз, когда Go Инструментарий компилирует функцию Go, помеченную как "экспортированная в C" в режиме c-whatever
, она соответствует соглашению Go и результат ее работы:
- Скомпилированная библиотека ожидает получить
struct
-типовое значение, состоящее из указателя и размера, как описано выше, для каждого аргумента Go type string
каждой экспортируемой функции. - Поддерживающий заголовок C файл будет ge nerated, содержащий определение этого типа
struct
- он будет называться GoString
, - и объявление для всех ваших экспортируемых функций, используя этот тип GoString
для аргументов, которые string
на стороне Go .
Проще говоря, если бы вы создали файл foo.go
, содержащий
package main
import "C"
import "fmt"
//export PrintIt
func PrintIt(string s) {
fmt.Println(s)
}
, а затем скомпилировали его, используя go build -buildmode=c-shared -o foo.so foo.go
, * Набор инструментов 1093 * создаст как foo.so
, так и foo.h
, содержащий, помимо прочего, что-то вроде:
typedef struct { const char *p; ptrdiff_t n; } GoString;
extern void PrintIt(GoString p0);
Как видите, для вызова PrintIt
вы должны его передать экземпляр GoString
, а не значение типа const char *
.
Решение
Надлежащее решение - иметь что-то вроде
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack=0)]
struct GoString {
byte *p;
int n;
}
А затем
[DllImport("./main.so", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
static extern void Println(GoString gs);
Обратите внимание, что я не совсем уверен, что обычный int
можно использовать для ptrdiff_t
в каждом конкретном случае - пожалуйста, сделайте свое собственное исследование того, что следует правильно использовать в . NET взаимодействие.