Site icon Заметки разработчика

Передача строки без выделения памяти из функции на Go в код на C (часть 1)

Документация по CGO весьма скудно освещает вопрос передачи строк. Сказано только одно – используйте функцию C.CString() для преобразования объекта string в понятный коду на C указатель на буфер со строкой, завершенной нулем — char*. Все хорошо, но при этом выделяется блок памяти (malloc), который необходимо удалять вызовом (free), закончив его использование. Несколько расточительно, если функцию на C вы собираетесь вызывать очень часто. Можно ли избежать операций выделения/освобождения памяти? Ведь передача строки без выделения памяти работала бы быстрее.

Если посмотреть, какой код генерируется для экспортируемых для доступа из С функций GO c параметром типа string,

//export MyGoCallback
func MyGoCallback(param1 string) {
…
}

то можно увидеть, что тип param1 в сгенерированной функции — это GoString. Это структура, которая объявляется в генерируемом при построении включаемом файле _cgo_export.h как

typedef struct { const char *p; GoInt n; } GoString;

Если string — это структура GoString, почему бы просто не передавать ее адрес в C, а не выделять память, копировать строку в выделенный буфер, освобождать память после использования?

Прилагаемый пример реализует схему с передачей строки без выделения памяти. При этом в функцию на передается адрес объекта string как unsafe.Pointer и далее, после приведения типа к указателю на GoString, получаем прямой доступ к данным строки. При этом нужно учесть, что строка в кодировке utf8 и не завершена нулем.

Реализация

Код модуля на GO (gostring.go):

package main
// extern void Test(void* p);
import "C"
import "unsafe"

func main() {  
  str := "test test"
  C.Test(unsafe.Pointer(&str))
}

Код функции Test() на C (test.c):

#include <_cgo_export.h>
#include <string.h>
#include <stdio.h>
void Test(void* p)
{
  GoString* gs = (GoString*)p;
  char buf[64];
  int n = gs->n < sizeof(buf) - 1 ? gs->n : sizeof(buf) - 1;
  strncpy(buf, gs->p, n);
  buf[n] = '\0';
  printf("received: %s\n", buf);
}

В результате построения примера командой

go build .

и запуска приложения видим на экране:

received: test test

Функция Test обрезает строку, если ее длина превышает 63 символа. В реальной ситуации нужно использовать буфер большего размера и использовать динамически выделенный буфер, если этого размера оказывается недостаточно. В прилагаемых файлах gostr2c.c и gostr2c.h можно найти полную реализацию функций для использования пришедшего из кода на GO указателя на объект string. С использованием макросов из gostr2c.h функцию Test можно переписать как:

void Test(void* p)
{
  GO2C(p);
  printf("received: %s\n", pp);
  GO2C_FREE(p);
}

Заключение

Почему же этот способ не документирован в GO? Всегда ли предложенная схема будет работать? Читайте об этом во второй части

gostring.zip

Exit mobile version