Ключевой мотивацией для написания данной статьи является факт сильного недостатка информации (особенно в русскоязычном сообществе) по использованию cgo и Dart FFI для вызова Go кода из языка Dart.
Язык Dart, не смотря на свою возрастающую популярность, на данный момент до сих пор не имеет такого же большого сообщества, как у языка Go. Dart заточен под выполнение других задач, по этому он иногда не содержит тех реализаций и функционала, который уже есть на языке Go.
В случае если можно можно избежать экспорта go кода в Dart (например экспортировать готовую c библиотеку), то лучше воспользоваться такой возможностью и не использовать cgo. Однако, могут возникать случаи, когда перегонка go в dart кода является оптимальным решением (например вы уже знакомы с Go и Dart, и не хотите писать код на C, в таком случае есть смысл задуматься об использованием cgo и Dart FFI).
В данной статье на простом примере будет показано как можно вызвать код Go из языка Dart (например в приложениях на Flutter).
Что должно быть установлено:
Go
Dart
Текстовый редактор/IDE (я буду использовать VSCode, так как это самая популярная среда среди Dart и Go сообщества, так же будут установлены специальные плагины для поддержки языков Go и Flutter)
Шаг 1 - Создаем пустое консольное приложение на Dart
Вызываем Command Palette клавишей F1 и создаем новый проект на Dart, выбираем опцию Console Application (данный формат использован для примера, далее код на cgo можно будет использовать в том числе из Flutter проектов или других форматов приложений на Dart).
Назвать приложение можно в целом как угодно, я выбрал название cgo_dartffi_helloworld, исключительно для тестового примера. (Нам потребуется именно директория с проектом на Dart, так как мы будем добавлять ffi в pubspec.yaml файл).
Прожигаем кнопку создать и переходим в директорию со новоиспеченным проектом.
Шаг 2 - Добавляем ffi в yaml файл
Далее нам необходимо добавить ffi в yaml файл для возможности использования go кода из dart.
name: cgo_dartffi_helloworld
description: A sample command-line application.
version: 1.0.0
environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
path: ^1.8.0
ffi: ^0.1.3
dev_dependencies:
pedantic: ^1.10.0
test: ^1.16.0
Шаг 3 - Создаем .go файл содержащий экспортируемую функцию
Далее необходимо создать файл на go, (в например в руте директории с проектом, например lib.go) который будет содержать функцию для экспорта в Dart. В данном примере эта функция - HelloFromGo().
// filename: lib.go
package main
import "C"
//export HelloFromGo
func HelloFromGo() *C.char {
message := "Hello to dart lang from go"
return C.CString(message)
}
func main() {}
Стоит быть крайне аккуратными при написании кода cgo так как большая часть инструментов, включая сборщик мусора перестают работать. В cgo комментарии имеют значение (да, это странно), именно с помощью комментариев можно обозначить функцию которую необходимо экспортировать (используя слово export). Более подробно данные нюансы описаны на официальной странице cgo https://golang.org/cmd/cgo/, ну а мы вернемся к практической стороне вопроса.
Шаг 4 - Собираем динамическую библиотеку из go файла
Далее необходимо открыть терминал и запустить там следующую команду:
go build -buildmode=c-shared -o lib.a lib.go
Данная команда создаст файл lib.a (который и представляет из себя динамическую c библиотеку). Даже для такого небольшого файлика время сборки заставляет ужаснуться (аж целых несколько секунд, в отличии от моментальных сборок на go, еще один из плюсов go, который теряется при использовании cgo).
Шаг 5 - Проверяем наличие необходимых файлов
На данном этапе наша директория должна выглядеть примерно следующим образом:
Она должна содержать следующие файлы:
Измененный pubspec.yaml файл
lib.h, lib.a файлы созданные из файла lib.go
директорию bin с дефолтным файлом библиотеки dart (туда мы сейчас и отправимся)
Шаг 6 - Прописываем биндинги на cgo функцию в Dart коде
Далее идёт самая сложная (и потенциально вызывающая ошибки) часть процедуры. Наличие ошибок возможно при несоответствии имён (так как нет возможности их проверить), по этому на данном этапе необходимо быть особенно аккуратными.
6.1 - удаляем всё содержимое файла bin/cgo_dartffi_helloworld.dart и начинаем там писать с чистого листа
6.2 - импортируем необходимые библиотеки (для нас это ffi и utf8 для передачи текста)
import 'dart:ffi' as ffi;
import 'package:ffi/src/utf8.dart';
6.3 - открываем динамическую библиотеку
final dylib = ffi.DynamicLibrary.open('lib.a');
6.4 - привязываем нашу функции к функции в dart
typedef HelloFromGo = ffi.Pointer<Utf8> Function();
typedef HelloFromGoFunc = ffi.Pointer<Utf8> Function();
final HelloFromGo _finalFunction = dylib
.lookup<ffi.NativeFunction<HelloFromGoFunc>>('HelloFromGo')
.asFunction();
6.5 - создаем метод который проверит вызов нашей функции (обратите внимание, метод .toDartString переводит стринг из формата C в формат Dart):
void main() {
print(_finalFunction().toDartString());
}
Таким образом мы создали функцию на go, которая передает string в язык Dart.
Далее при написании своих функций следует учитывать, что форматы данных в языках Go, C и Dart могут отличаться (и зачастую так происходит), что приводит к необходимости использовать различные конвертации на стороне go/dart кода, более подробно можно ознакомиться по следующим ссылкам:
Полный код на Dart:
import 'dart:ffi' as ffi;
import 'package:ffi/src/utf8.dart';
final dylib = ffi.DynamicLibrary.open('lib.a');
typedef HelloFromGo = ffi.Pointer<Utf8> Function();
typedef HelloFromGoFunc = ffi.Pointer<Utf8> Function();
final HelloFromGo _finalFunction = dylib
.lookup<ffi.NativeFunction<HelloFromGoFunc>>('HelloFromGo')
.asFunction();
void main() {
print(_finalFunction().toDartString());
}
При необходимости передавать параметры в вызываемую функцию можно использовать поинтеры и объявить их в вызываемой функции, например:
typedef GetHash = Pointer<Utf8> Function(Pointer<Utf8> str);
typedef GetHashFunc = Pointer<Utf8> Function(Pointer<Utf8> str);
final GetHash _getHashGoFunction =
_lib.lookup<NativeFunction<GetHashFunc>>('GetHash').asFunction();
Главное помнить, что необходимо проверять форматы передаваемых данных.