C# строка с общей библиотекой - PullRequest
1 голос
/ 05 января 2020

У меня есть общая библиотека, созданная с помощью Go, и программа на C#, для которой я хочу вызвать функцию в общей библиотеке. Но печатает пустую строку.

Это код C#:

using System.Runtime.InteropServices;

class Program
{
    [DllImport("./main.so", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    static extern void Println(string s);

    static void Main(string[] args)
    {
        Println("hello world");
    }
}

Это код Go:

package main

import "C"
import "fmt"

//export Println
func Println(s string){
    fmt.Println(s)
}

func main(){}

Как мне пройти C# строка как Go строка?

1 Ответ

0 голосов
/ 05 января 2020

Проблема

Проблема в том, что хотя -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 взаимодействие.

...