Dart недавно представил финализаторы и представил их нативным библиотекам.
Мне было сложно его настроить, так как документации пока не так много, и я не уверен во всех деталях. Но эта статья — мое понимание этой функции, и она может помочь вам.
Прорабатывается также запуск финализаторов напрямую из Dart.
Проблема
Если у вас есть собственная библиотека, которую вы хотите использовать с приложением Dart/Flutter, вы можете использовать интерфейс сторонних функций Dart. Вы можете передавать данные туда и обратно, вызывать собственные функции и т. д.
Но если вы хотите реализовать класс Dart, который действует как оболочка для некоторых нативных функций, вы столкнетесь с проблемой. Dart предоставляет классы с конструктором для инициализации каждого экземпляра, но автоматически обрабатывает уничтожение каждого экземпляра с помощью сборщика мусора. Итак, если вы хотите, чтобы собственные ресурсы были привязаны к времени жизни объекта Dart, что вы можете сделать?
Решение
Чтобы решить эту проблему, команда Dart открыла финализаторам нативный код. Насколько я понимаю, некоторые его части все еще находятся в разработке, поэтому API может измениться, а документации не хватает.
Вот мое понимание процесса:
- Свяжите свою нативную библиотеку с API Dart VM
- Передавайте специальные данные из вашего приложения Dart в виртуальную машину Dart через собственную библиотеку.
- Зарегистрируйте собственную функцию для использования в качестве финализатора для вашего объекта Dart.
Затем виртуальная машина выполнит ваш финализатор, когда ваш объект Dart будет собран мусором.
Базовый пример
Вот простая библиотека, написанная на C++. У него есть три функции: одна для включения динамического связывания с виртуальной машиной Dart, одна для регистрации финализатора и одна, которая является самим финализатором.
#include <cstdio> #include <cstdlib> #include “include/dart_api_dl.h” extern “C” void init_dart_dynamic_linking(void* data) { if (Dart_InitializeApiDL(data) != 0) { printf(“Failed to initialise Dart VM API\n”); } } static void my_finaliser(void* isolate_callback_data, void* peer) { printf(“Finalising: %p\n”, peer); free(peer); } extern “C” uint32_t* register_finaliser(Dart_Handle handle, intptr_t length) { uint32_t* native_data = new uint32_t[length](); Dart_NewFinalizableHandle_DL(handle, reinterpret_cast<void*>(native_data), length, my_finaliser); printf(“Registered finaliser: %p\n”, native_data); return native_data; }
dart_api_dl.h
взят из Dart SDK и предоставляет символы для взаимодействия с виртуальной машиной Dart во время выполнения.
Чтобы инициализировать динамическую компоновку, наше приложение Dart вызовет функцию init_dart_dynamic_linking()
и предоставит некоторые данные инициализации. При вызове Dart_InitializeApiDL()
эти данные пересылаются в среду выполнения Dart.
Затем наше приложение Dart будет вызывать register_finaliser()
, что говорит среде выполнения Dart вызывать my_finaliser()
при сборке объекта мусором.
FFI автоматически преобразует объекты Dart в Dart_Handles
при отправке через FFI. Dart_NewFinalizableHandle_DL()
(документация) — функция, выполняющая регистрацию. Второй параметр будет передан нашему финализатору. Указатель называется peer
, что, как я предполагаю, означает, что он представляет данные на собственной стороне, которые имеют отношение к объекту на стороне Dart. Следующий параметр — это значение размера, которое, как мне кажется, используется сборщиком мусора в качестве подсказки о том, сколько собственной памяти используется. Последний параметр — это указатель на функцию финализатора.
Виртуальная машина Dart вызовет my_finaliser()
с двумя параметрами-указателями. Я не уверен, для чего используется первый указатель, но второй указатель — это наш указатель peer
, который мы можем использовать для освобождения собственной памяти. Если это указывает на некоторую структуру данных, вы можете привести ее к этому типу и освободить ее ресурсы, как вам нужно.
Компиляция
С помощью документации по Нативным расширениям Dart мы можем скомпилировать разделяемую библиотеку.
Согласно исходникам, мы должны скомпилировать dart_api_dl.c
и связать с ним нашу библиотеку, но я не смог найти этот файл в Dart SDK на своем компьютере, поэтому мне пришлось скачать SDK, который включает в себя каталог времени выполнения.
В моем случае я скомпилировал для Linux:
g++ -std=c++11 -fPIC -I/path/to/dart/sdk/include -I/path/to/dart/runtime/ -DDART_SHARED_LIB -c example.cc /path/to/dart/runtime/include/dart_api_dl.c`
Теперь мы можем связать эти объектные файлы в динамическую библиотеку:
gcc -shared -Wl,-soname,libmylib.so -o libmylib.so example.o dart_api_dl.o`
Используя команду Unix nm
, мы можем убедиться, что символы находятся в общей библиотеке.
Код приложения
Вот код нашего приложения Dart:
import ‘dart:ffi’ as ffi; typedef _c_init_dart_dynamic_linking = ffi.Void Function( ffi.Pointer<ffi.Void> data, ); typedef _dart_init_dart_dynamic_linking = void Function( ffi.Pointer<ffi.Void> data, ); typedef _c_register_finaliser = ffi.Pointer<ffi.Uint32> Function( ffi.Handle handle, ffi.IntPtr length, ); typedef _dart_register_finaliser = ffi.Pointer<ffi.Uint32> Function( Object object, int length, ); void main() { final lib = ffi.DynamicLibrary.open(‘./libmylib.so’); final init = lib.lookupFunction<_c_init_dart_dynamic_linking, _dart_init_dart_dynamic_linking>(‘init_dart_dynamic_linking’); init(ffi.NativeApi.initializeApiDLData); final objectWhichNeedsAFinaliser = “I need a finaliser”; final registerFinaliser = lib.lookupFunction<_c_register_finaliser, _dart_register_finaliser>(‘register_finaliser’); const length = 5; final nativeDataPointer = registerFinaliser(objectWhichNeedsAFinaliser, length); final list = nativeDataPointer.asTypedList(length); print(‘Native data is $list’); }
Интересно то, что данные инициализации из NativeApi
передаются в нашу родную библиотеку.
Когда вы запустите программу, вы должны увидеть такой вывод:
Registered finaliser: 0x7f2a84002be0 Native data is [0, 0, 0, 0, 0] Finalising: 0x7f2a84002be0
Последняя строка — это выполнение финализатора… наконец!
Вывод
Теперь у вас есть финализатор, написанный на C++, который выполняется, когда объект Dart очищается сборщиком мусора. Поздравляем!