Свистоперделка #2 | FFI-handshake

Идея написания этой краткой заметки посетила меня, когда я экспериментировал с FFI-вызовами между Go и Rust. Задача состояла в том, чтобы реализовать двунаправленное взаимодействие между приложением на Go и динамической библиотекой, написанной на Rust. После некоторого числа экспериментов мне удалось-таки добиться необходимого результата - корректно передать указатель на go-функцию в rust и вызвать ее оттуда с нужными аргументами.

И тут мне стало интересно, как подобное реализовать на любименьком Dart`е. В нем классная ffi-библиотека и вдобавок имеется пакет ffigen для генерации сигнатур методов на основе заголовочных файлов библиотек. Спойлер: мне не удалось сходу добиться от генератора корректного вывода (отсуствовал тайпдеф для колбека), потому-что он обрабатывает сингатуры external функций точно также, как и остальные. Поэтому пишем биндинги ручками.

Предварительно напишем простую либу на Rust, который будет принимать в качестве параметров указатель на колбек и просто его вызывать. Затем скомпилируем код в динамическую библиотеку командой cargo build --release.

`//Тип колбека определяем как внешнюю функцию, совместимус с ABI Cи pub type Callback = extern "C" fn(data: *const c_char);

#[no_mangle] pub extern "C" fn init_handshake(callback: Callback) { //Вызываем колбек, unsafe блок не обязателен (компилятор раста подсказал бы), а сам указатель можем присвоить переменной на строне Rust и использовать его многократно до завершения программы callback(CString::new("Event data from Rust").unwrap().as_ptr()); }

Опишем сигнатуры на стороне Dart. `//Сигнатура колбека Dart typedef DART_CALLBACK = Void Function(Pointer);

//Набор сигнатур для вызова Rust кода через FFI typedef RUST_HANDSHAKE = Void Function(Pointer>); typedef RUST_HANDSHAKE_DART = void Function( Pointer>);

Реализуем функцию обратного вызова, где преобразуем переданные аргументы и выполним некие полезные действия. `void callback(Pointer s) { //Приводим С-строку к Dart-строке и используем ее final x = s.toDartString(); print(x);

/* Освобождаем память при необходимости. В данном случае попытка освободить память вызовет ошибку повторного освобождения, поскольку Rust справился с этим за нас "pointers do not have a lifetime; when calling as_ptr the CString will be deallocated at the end of the statement because nothing is referencing it as far as the type system is concerned" */ // calloc.free(s); }

В официальной доке я не сразу нашел, как в Dart получить указатель на функцию. Пришлось спрашивать гигачат, но он жестко галлюцинировал и приходилось экспериментировать с его выдачей. Реализуем загрузку либы, поиск метода по символу и вызов нашей rust-фукнции init_handshake.

`void main() async { //Грузим динамическую библиотеку final DLIB = DynamicLibrary.open('../target/release/libdrust.dylib');

//Ищем функцию init_handshake по ее символу //P.S. Чтобы этот символ не был обфусцирован мы добавили #[no_mangle] в коде Rust final rustFn = DLIB.lookup>("init_handshake");

//Преобразуем указатель на внешнюю функцию в функцию Dart final initHandshake = rustFn.asFunction();

//Создаем указатель на Dart-функцию, которая соотвествует сигнатуре DART_CALLBACK final Pointer> callbackPtr = Pointer.fromFunction(callback);

//Вызываем функцию с указателем на колбек в качестве аргумента initHandshake(callbackPtr); }

Интересный момент, который я могу отметить после экспериментов со связкой Dart/Rust - можно не так внимательно следить за аллокациями и освобождением памяти, поскольку в некоторых случаях rust сам способен убить ненужные объекты, как было в случае с Pointer (см. фрагмент кода с коллебеком). Это способно немного облегчить нам управление памятью в приложении. Но помните про unsafe блоки и не расслабляйте булки)````