Передача структуры, содержащей * байт, в Syscall и чтение ее содержимого после выполнения - PullRequest
1 голос
/ 29 октября 2019

Я использую syscall.Syscall(...) для вызова метода C в DLL.


Это подпись метода C:

SENSEI_API HSENSEI SENSEI_open(const char* sensigrafo, const char* options, SENSEI_ERR* se);

Это SENSEI_ERRstruct:

typedef struct
{
    int code;
    char* error_string;
} SENSEI_ERR;

В моей программе GO я объявил структуру:

type senseiErr struct {
    code         int
    error_string *byte
}

И попытался вызвать метод:

    var nargs uintptr = 3
    var err senseiErr

    ret, _, callErr := syscall.Syscall(uintptr(senseiOpen),
        nargs,
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("en"))),
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(""))),
        uintptr(unsafe.Pointer(&err)),
    )

Как вывозможно, догадались, что метод SENSEI_open заполнит аргумент SENSEI_ERR кодом и текстом ошибки.

Теперь мне нужно прочитать содержание этой ошибки.

err.code на самом деле имеет правильное значение.

О err.error_string Я не знаю. Я новичок в GO, и у меня есть несколько вопросов:

  • Поскольку структура C имеет поле char* error_string, верно ли error_string *byte в моей структуре GO?
    • Должен ли я использовать []byte или что-то еще?
  • Как прочитать содержимое поля error_string?
    • fmt.Println(err.error_string) печатает адрес памяти
    • fmt.Println(*err.error_string) печатает всегда "101"

1 Ответ

2 голосов
/ 29 октября 2019

1) Я сомневаюсь, что cost char* предназначено для кодировки UTF16. Так что все, что вам нужно, это просто получить необработанные данные:

sensigrafo := "en\000" // \000 = 0 = null termination, \0 does not valid
options := "\000"

...

uintptr(*(*unsafe.Pointer)(unsafe.Pointer(&sensigrafo))
uintptr(*(*unsafe.Pointer)(unsafe.Pointer(&options))

// *(*unsafe.Pointer) are accessing the first field of string header:
type string struct {
    data *byte
    len int
}

// same with slices
// but for them there's less ugly way:
sensigrafo := []byte("en\000")
options := []byte("\000")

uintptr(unsafe.Pointer(&sensigrafo[0]))
uintptr(unsafe.Pointer(&options[0]))

2) C's int и Golang's int могут иметь разные размеры, поэтому для этого требуется объявление cgo (C.int) или ручное сопоставление сслучайный выбор (попробуйте также int32, int64, если вы не хотите использовать cgo)

type senseiErr struct {
    code         C.int /* Golang's int32/int64 */
    error_string *byte // pointer types are same as C's void* or Golang's unsafe.Pointer
}

Неверное смещение может привести к тому, что error_string будет пустым или будет указывать на случайный адрес.

3) Читатьсодержимое, вы должны использовать те же методы, что и C (чтение данных, пока байт не завершится нулем, учитывая, что * байт указывает на первый элемент строки), но я предлагаю использовать уже реализованные функции времени выполнения:

//go:linkname gostringn runtime.gostringn
func gostringn(p *byte, l int) string

//go:linkname findnull runtime.findnull
//go:nosplit
func findnull(s *byte) int

...

error_string := gostringn(err.error_string, findnull(err.error_string))

// or cgo one:

type senseiErr struct {
    code         C.int
    error_string *C.char
}

...

error_string := C.GoString(err.error_string)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...