Dart недавно представил финализаторы и представил их нативным библиотекам.

Мне было сложно его настроить, так как документации пока не так много, и я не уверен во всех деталях. Но эта статья — мое понимание этой функции, и она может помочь вам.

Прорабатывается также запуск финализаторов напрямую из Dart.

Проблема

Если у вас есть собственная библиотека, которую вы хотите использовать с приложением Dart/Flutter, вы можете использовать интерфейс сторонних функций Dart. Вы можете передавать данные туда и обратно, вызывать собственные функции и т. д.

Но если вы хотите реализовать класс Dart, который действует как оболочка для некоторых нативных функций, вы столкнетесь с проблемой. Dart предоставляет классы с конструктором для инициализации каждого экземпляра, но автоматически обрабатывает уничтожение каждого экземпляра с помощью сборщика мусора. Итак, если вы хотите, чтобы собственные ресурсы были привязаны к времени жизни объекта Dart, что вы можете сделать?

Решение

Чтобы решить эту проблему, команда Dart открыла финализаторам нативный код. Насколько я понимаю, некоторые его части все еще находятся в разработке, поэтому API может измениться, а документации не хватает.

Вот мое понимание процесса:

  1. Свяжите свою нативную библиотеку с API Dart VM
  2. Передавайте специальные данные из вашего приложения Dart в виртуальную машину Dart через собственную библиотеку.
  3. Зарегистрируйте собственную функцию для использования в качестве финализатора для вашего объекта 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 очищается сборщиком мусора. Поздравляем!