| // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:ffi'; |
| import 'dart:isolate'; |
| |
| import 'package:ffi/ffi.dart'; |
| |
| import 'c_bindings_generated.dart' as c; |
| |
| /// Only for use by ffigen bindings. |
| Pointer<c.ObjCSelector> registerName(String name) { |
| final cstr = name.toNativeUtf8(); |
| final sel = c.registerName(cstr.cast()); |
| calloc.free(cstr); |
| return sel; |
| } |
| |
| /// Only for use by ffigen bindings. |
| Pointer<c.ObjCObject> getClass(String name) { |
| final cstr = name.toNativeUtf8(); |
| final clazz = c.getClass(cstr.cast()); |
| calloc.free(cstr); |
| if (clazz == nullptr) { |
| throw Exception('Failed to load Objective-C class: $name'); |
| } |
| return clazz; |
| } |
| |
| /// Only for use by ffigen bindings. |
| final msgSendPointer = |
| Native.addressOf<NativeFunction<Void Function()>>(c.msgSend); |
| |
| /// Only for use by ffigen bindings. |
| final msgSendFpretPointer = |
| Native.addressOf<NativeFunction<Void Function()>>(c.msgSendFpret); |
| |
| /// Only for use by ffigen bindings. |
| final msgSendStretPointer = |
| Native.addressOf<NativeFunction<Void Function()>>(c.msgSendStret); |
| |
| /// Only for use by ffigen bindings. |
| final useMsgSendVariants = |
| Abi.current() == Abi.iosX64 || Abi.current() == Abi.macosX64; |
| |
| class _ObjCFinalizable<T extends NativeType> implements Finalizable { |
| final Pointer<T> _ptr; |
| bool _pendingRelease; |
| |
| _ObjCFinalizable(this._ptr, {required bool retain, required bool release}) |
| : _pendingRelease = release { |
| if (retain) { |
| _retain(_ptr.cast()); |
| } |
| if (release) { |
| _finalizer.attach(this, _ptr.cast(), detach: this); |
| } |
| } |
| |
| /// Releases the reference to the underlying ObjC object held by this wrapper. |
| /// Throws a StateError if this wrapper doesn't currently hold a reference. |
| void release() { |
| if (_pendingRelease) { |
| _pendingRelease = false; |
| _release(_ptr.cast()); |
| _finalizer.detach(this); |
| } else { |
| throw StateError( |
| 'Released an ObjC object that was unowned or already released.'); |
| } |
| } |
| |
| @override |
| bool operator ==(Object other) { |
| return other is _ObjCFinalizable && _ptr == other._ptr; |
| } |
| |
| @override |
| int get hashCode => _ptr.hashCode; |
| |
| /// Return a pointer to this object. |
| Pointer<T> get pointer => _ptr; |
| |
| /// Retain a reference to this object and then return the pointer. This |
| /// reference must be released when you are done with it. If you wrap this |
| /// reference in another object, make sure to release it but not retain it: |
| /// `castFromPointer(lib, pointer, retain: false, release: true)` |
| Pointer<T> retainAndReturnPointer() { |
| _retain(_ptr.cast()); |
| return _ptr; |
| } |
| |
| NativeFinalizer get _finalizer => throw UnimplementedError(); |
| void _retain(Pointer<T> ptr) => throw UnimplementedError(); |
| void _release(Pointer<T> ptr) => throw UnimplementedError(); |
| } |
| |
| /// Only for use by ffigen bindings. |
| class ObjCObjectBase extends _ObjCFinalizable<c.ObjCObject> { |
| ObjCObjectBase(super.ptr, {required super.retain, required super.release}); |
| |
| static final _objectFinalizer = NativeFinalizer( |
| Native.addressOf<NativeFunction<Void Function(Pointer<c.ObjCObject>)>>( |
| c.objectRelease) |
| .cast()); |
| |
| @override |
| NativeFinalizer get _finalizer => _objectFinalizer; |
| |
| @override |
| void _retain(Pointer<c.ObjCObject> ptr) { |
| assert(_isValidObject(ptr)); |
| c.objectRetain(ptr); |
| } |
| |
| @override |
| void _release(Pointer<c.ObjCObject> ptr) { |
| assert(_isValidObject(ptr)); |
| c.objectRelease(ptr); |
| } |
| } |
| |
| // Returns whether the object is valid and live. The pointer must point to |
| // readable memory, or be null. May (rarely) return false positives. |
| bool _isValidObject(Pointer<c.ObjCObject> ptr) { |
| if (ptr == nullptr) return false; |
| return _isValidClass(c.getObjectClass(ptr)); |
| } |
| |
| final _allClasses = <Pointer<c.ObjCObject>>{}; |
| |
| bool _isValidClass(Pointer<c.ObjCObject> clazz) { |
| if (_allClasses.contains(clazz)) return true; |
| |
| // If the class is missing from the list, it either means we haven't created |
| // the set yet, or more classes have been loaded since we created the set, or |
| // the class is actually invalid. To rule out the first two cases, rebulid the |
| // set then try again. This is expensive, but only happens if asserts are |
| // enabled, and only happens more than O(1) times if there are actually |
| // invalid objects in use, which shouldn't happen in correct code. |
| final countPtr = calloc<UnsignedInt>(); |
| final classList = c.copyClassList(countPtr); |
| final count = countPtr.value; |
| calloc.free(countPtr); |
| _allClasses.clear(); |
| for (int i = 0; i < count; ++i) { |
| _allClasses.add(classList[i]); |
| } |
| calloc.free(classList); |
| |
| return _allClasses.contains(clazz); |
| } |
| |
| /// Only for use by ffigen bindings. |
| class ObjCBlockBase extends _ObjCFinalizable<c.ObjCBlock> { |
| ObjCBlockBase(super.ptr, {required super.retain, required super.release}); |
| |
| static final _blockFinalizer = NativeFinalizer( |
| Native.addressOf<NativeFunction<Void Function(Pointer<Void>)>>( |
| c.blockRelease)); |
| |
| @override |
| NativeFinalizer get _finalizer => _blockFinalizer; |
| |
| @override |
| void _retain(Pointer<c.ObjCBlock> ptr) { |
| assert(c.isValidBlock(ptr)); |
| c.blockCopy(ptr.cast()); |
| } |
| |
| @override |
| void _release(Pointer<c.ObjCBlock> ptr) { |
| assert(c.isValidBlock(ptr)); |
| c.blockRelease(ptr.cast()); |
| } |
| } |
| |
| Pointer<c.ObjCBlockDesc> _newBlockDesc( |
| Pointer<NativeFunction<Void Function(Pointer<c.ObjCBlock>)>> |
| disposeHelper) { |
| final desc = calloc.allocate<c.ObjCBlockDesc>(sizeOf<c.ObjCBlockDesc>()); |
| desc.ref.reserved = 0; |
| desc.ref.size = sizeOf<c.ObjCBlock>(); |
| desc.ref.copy_helper = nullptr; |
| desc.ref.dispose_helper = disposeHelper.cast(); |
| desc.ref.signature = nullptr; |
| return desc; |
| } |
| |
| final _pointerBlockDesc = _newBlockDesc(nullptr); |
| final _closureBlockDesc = _newBlockDesc( |
| Native.addressOf<NativeFunction<Void Function(Pointer<c.ObjCBlock>)>>( |
| c.disposeObjCBlockWithClosure)); |
| |
| Pointer<c.ObjCBlock> _newBlock(Pointer<Void> invoke, Pointer<Void> target, |
| Pointer<c.ObjCBlockDesc> descriptor, int disposePort, int flags) { |
| final b = calloc.allocate<c.ObjCBlock>(sizeOf<c.ObjCBlock>()); |
| b.ref.isa = |
| Native.addressOf<Array<Pointer<Void>>>(c.NSConcreteGlobalBlock).cast(); |
| b.ref.flags = flags; |
| b.ref.reserved = 0; |
| b.ref.invoke = invoke; |
| b.ref.target = target; |
| b.ref.dispose_port = disposePort; |
| b.ref.descriptor = descriptor; |
| assert(c.isValidBlock(b)); |
| final copy = c.blockCopy(b.cast()).cast<c.ObjCBlock>(); |
| calloc.free(b); |
| assert(copy.ref.isa == |
| Native.addressOf<Array<Pointer<Void>>>(c.NSConcreteMallocBlock).cast()); |
| assert(c.isValidBlock(copy)); |
| return copy; |
| } |
| |
| const int _blockHasCopyDispose = 1 << 25; |
| |
| /// Only for use by ffigen bindings. |
| Pointer<c.ObjCBlock> newClosureBlock(Pointer<Void> invoke, Function fn) => |
| _newBlock(invoke, _registerBlockClosure(fn), _closureBlockDesc, |
| _blockClosureDisposer.sendPort.nativePort, _blockHasCopyDispose); |
| |
| /// Only for use by ffigen bindings. |
| Pointer<c.ObjCBlock> newPointerBlock( |
| Pointer<Void> invoke, Pointer<Void> target) => |
| _newBlock(invoke, target, _pointerBlockDesc, 0, 0); |
| |
| final _blockClosureRegistry = <int, Function>{}; |
| |
| int _blockClosureRegistryLastId = 0; |
| |
| final _blockClosureDisposer = () { |
| c.Dart_InitializeApiDL(NativeApi.initializeApiDLData); |
| return RawReceivePort((dynamic msg) { |
| final id = msg as int; |
| assert(_blockClosureRegistry.containsKey(id)); |
| _blockClosureRegistry.remove(id); |
| }, "ObjCBlockClosureDisposer") |
| ..keepIsolateAlive = false; |
| }(); |
| |
| Pointer<Void> _registerBlockClosure(Function closure) { |
| ++_blockClosureRegistryLastId; |
| assert(!_blockClosureRegistry.containsKey(_blockClosureRegistryLastId)); |
| _blockClosureRegistry[_blockClosureRegistryLastId] = closure; |
| return Pointer<Void>.fromAddress(_blockClosureRegistryLastId); |
| } |
| |
| /// Only for use by ffigen bindings. |
| Function getBlockClosure(Pointer<c.ObjCBlock> block) { |
| int id = block.ref.target.address; |
| assert(_blockClosureRegistry.containsKey(id)); |
| return _blockClosureRegistry[id]!; |
| } |
| |
| // Not exported by ../objective_c.dart, because they're only for testing. |
| bool blockHasRegisteredClosure(Pointer<c.ObjCBlock> block) => |
| _blockClosureRegistry.containsKey(block.ref.target.address); |
| bool isValidBlock(Pointer<c.ObjCBlock> block) => c.isValidBlock(block); |
| bool isValidClass(Pointer<c.ObjCObject> clazz) => _isValidClass(clazz); |
| bool isValidObject(Pointer<c.ObjCObject> object) => _isValidObject(object); |