The Go Blog
C? Go? Cgo!
Introduction
Cgo позволяет пакетам Go вызывать C-код. При наличии Go-исходного файла, написанного с использованием некоторых специальных возможностей, cgo генерирует Go- и C-файлы, которые могут быть объединены в один Go-пакет.
Для начала рассмотрим пример: вот пакет Go, который предоставляет две функции — Random и Seed, которые оборачивают функции random и srandom из C.
<code>package rand
/*
#include <stdlib.h>
*/
import "C"
func Random() int {
return int(C.random())
}
func Seed(i int) {
C.srandom(C.uint(i))
}
</code>
Рассмотрим, что здесь происходит, начиная с инструкции импорта.
Пакет rand импортирует "C", но вы не найдете такого пакета в стандартной библиотеке Go.
Это потому, что C является «псевдо-пакетом»,
специальным именем, интерпретируемым cgo как ссылка на пространство имён C.
Пакет rand содержит четыре ссылки на пакет C:
вызовы C.random и C.srandom, преобразование C.uint(i),
и инструкция import.
Функция Random вызывает стандартную функцию библиотеки C random и возвращает результат.
В C функция random возвращает значение типа long,
которое cgo представляет как тип C.long.
Оно должно быть преобразовано в тип Go перед использованием в коде Go вне этого пакета,
с использованием обычного преобразования типа Go:
<code>func Random() int {
return int(C.random())
}
</code>
Вот эквивалентная функция, использующая временную переменную, чтобы более явно показать преобразование типа:
<code>func Random() int {
var r C.long = C.random()
return int(r)
}
</code>
Функция Seed делает обратное.
Она принимает обычный Go int, преобразует его в тип C unsigned int,
и передаёт его в функцию C srandom.
<code>func Seed(i int) {
C.srandom(C.uint(i))
}
</code>
Обратите внимание, что cgo знает тип unsigned int как C.uint;
см. документацию cgo для полного списка этих имен числовых типов.
Одна деталь этого примера, которую мы ещё не рассматривали — это комментарий над инструкцией import.
<code>/* #include <stdlib.h> */ import "C" </code>
Cgo распознаёт этот комментарий. Все строки, начинающиеся с #cgo, за которыми следует пробел, удаляются;
они становятся директивами для cgo.
Оставшиеся строки используются как заголовок при компиляции C-частей пакета.
В данном случае эти строки представляют собой просто одну инструкцию #include,
но они могут быть практически любым C-кодом.
Директивы #cgo используются для передачи флагов компилятору и линковщику
при сборке C-частей пакета.
Существует ограничение: если ваша программа использует какие-либо директивы //export, то C-код в комментарии может включать только объявления (extern int f();), а не определения (int f() { return 1; }). С помощью директив //export можно сделать функции Go доступными для C-кода.
Директивы #cgo и //export описаны в документации cgo.
Строки и прочее
В отличие от Go, в C не существует явного типа строки. Строки в C представлены нулевым завершённым массивом символов.
Преобразование между строками Go и C выполняется с помощью функций C.CString, C.GoString и C.GoStringN. Эти преобразования создают копию данных строки.
Следующий пример реализует функцию Print, которая записывает строку в стандартный вывод, используя функцию fputs из библиотеки stdio:
<code>package print
// #include <stdio.h>
// #include <stdlib.h>
import "C"
import "unsafe"
func Print(s string) {
cs := C.CString(s)
C.fputs(cs, (*C.FILE)(C.stdout))
C.free(unsafe.Pointer(cs))
}
</code>
Выделение памяти C-кодом неизвестно менеджеру памяти Go. Когда вы создаёте C-строку с помощью C.CString (или любого другого выделения памяти в C), вы должны помнить о необходимости освободить память по завершении работы с ней, вызвав C.free.
Вызов C.CString возвращает указатель на начало массива символов, поэтому перед выходом из функции мы преобразуем его в unsafe.Pointer и освобождаем выделенную память с помощью C.free. Распространённой практикой в программах cgo является использование defer для освобождения памяти сразу после её выделения (особенно если последующий код сложнее, чем простой вызов функции), как показано в переписанной версии функции Print:
<code>func Print(s string) {
cs := C.CString(s)
defer C.free(unsafe.Pointer(cs))
C.fputs(cs, (*C.FILE)(C.stdout))
}
</code>
Сборка пакетов cgo
Для сборки пакетов cgo просто используйте go build или go install как обычно. Инструмент go распознаёт специальный импорт "C" и автоматически использует cgo для таких файлов.
Дополнительные ресурсы по cgo
Документация команды cgo содержит больше информации о псевдопакете C и процессе сборки. Примеры cgo в дереве Go демонстрируют более продвинутые концепции.
Наконец, если вы интересуетесь тем, как всё это работает внутренне, ознакомьтесь с вводным комментарием файла cgocall.go из пакета runtime.
Следующая статья: Gobs of data
Предыдущая статья: Go становится более стабильным
Индекс блога