The Go Blog

C? Go? Cgo!

Andrew Gerrand
17 March 2011

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 становится более стабильным
Индекс блога

GoRu.dev Golang на русском

На сайте представлена адаптированная под русский язык документация языка программирования Golang