Свистоперделка #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 блоки и не расслабляйте булки)````