| // Copyright (c) 2022, 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 'package:ffigen/src/code_generator.dart'; |
| |
| import 'binding_string.dart'; |
| import 'writer.dart'; |
| |
| /// Built in functions used by the Objective C bindings. |
| class ObjCBuiltInFunctions { |
| late final _registerNameFunc = Func( |
| name: '_sel_registerName', |
| originalName: 'sel_registerName', |
| returnType: PointerType(objCSelType), |
| parameters: [Parameter(name: 'str', type: PointerType(charType))], |
| isInternal: true, |
| ); |
| late final registerName = ObjCInternalFunction( |
| '_registerName', _registerNameFunc, (Writer w, String name) { |
| final selType = _registerNameFunc.functionType.returnType.getCType(w); |
| return ''' |
| $selType $name(String name) { |
| final cstr = name.toNativeUtf8(); |
| final sel = ${_registerNameFunc.name}(cstr.cast()); |
| ${w.ffiPkgLibraryPrefix}.calloc.free(cstr); |
| return sel; |
| } |
| '''; |
| }); |
| |
| late final _getClassFunc = Func( |
| name: '_objc_getClass', |
| originalName: 'objc_getClass', |
| returnType: PointerType(objCObjectType), |
| parameters: [Parameter(name: 'str', type: PointerType(charType))], |
| isInternal: true, |
| ); |
| late final getClass = |
| ObjCInternalFunction('_getClass', _getClassFunc, (Writer w, String name) { |
| final objType = _getClassFunc.functionType.returnType.getCType(w); |
| return ''' |
| $objType $name(String name) { |
| final cstr = name.toNativeUtf8(); |
| final clazz = ${_getClassFunc.name}(cstr.cast()); |
| ${w.ffiPkgLibraryPrefix}.calloc.free(cstr); |
| if (clazz == ${w.ffiLibraryPrefix}.nullptr) { |
| throw Exception('Failed to load Objective-C class: \$name'); |
| } |
| return clazz; |
| } |
| '''; |
| }); |
| |
| late final _retainFunc = Func( |
| name: '_objc_retain', |
| originalName: 'objc_retain', |
| returnType: PointerType(objCObjectType), |
| parameters: [Parameter(name: 'value', type: PointerType(objCObjectType))], |
| isInternal: true, |
| ); |
| late final _releaseFunc = Func( |
| name: '_objc_release', |
| originalName: 'objc_release', |
| returnType: voidType, |
| parameters: [Parameter(name: 'value', type: PointerType(objCObjectType))], |
| isInternal: true, |
| ); |
| late final _releaseFinalizer = ObjCInternalGlobal( |
| '_objc_releaseFinalizer', |
| (Writer w) => '${w.ffiLibraryPrefix}.NativeFinalizer(' |
| '${_releaseFunc.funcPointerName}.cast())', |
| _releaseFunc, |
| ); |
| |
| late final _blockCopyFunc = Func( |
| name: '_Block_copy', |
| originalName: '_Block_copy', |
| returnType: PointerType(voidType), |
| parameters: [Parameter(name: 'value', type: PointerType(voidType))], |
| isInternal: true, |
| ); |
| late final _blockReleaseFunc = Func( |
| name: '_Block_release', |
| originalName: '_Block_release', |
| returnType: voidType, |
| parameters: [Parameter(name: 'value', type: PointerType(voidType))], |
| isInternal: true, |
| ); |
| late final _blockReleaseFinalizer = ObjCInternalGlobal( |
| '_objc_releaseFinalizer', |
| (Writer w) => '${w.ffiLibraryPrefix}.NativeFinalizer(' |
| '${_blockReleaseFunc.funcPointerName}.cast())', |
| _blockReleaseFunc, |
| ); |
| |
| // We need to load a separate instance of objc_msgSend for each signature. If |
| // the return type is a struct, we need to use objc_msgSend_stret instead, and |
| // for float return types we need objc_msgSend_fpret. |
| final _msgSendFuncs = <String, ObjCMsgSendFunc>{}; |
| ObjCMsgSendFunc getMsgSendFunc( |
| Type returnType, List<ObjCMethodParam> params) { |
| var key = returnType.cacheKey(); |
| for (final p in params) { |
| key += ' ${p.type.cacheKey()}'; |
| } |
| return _msgSendFuncs[key] ??= ObjCMsgSendFunc( |
| '_objc_msgSend_${_msgSendFuncs.length}', returnType, params); |
| } |
| |
| final _selObjects = <String, ObjCInternalGlobal>{}; |
| ObjCInternalGlobal getSelObject(String methodName) { |
| return _selObjects[methodName] ??= ObjCInternalGlobal( |
| '_sel_${methodName.replaceAll(":", "_")}', |
| (Writer w) => '${registerName.name}("$methodName")', |
| registerName, |
| ); |
| } |
| |
| // See https://clang.llvm.org/docs/Block-ABI-Apple.html |
| late final blockStruct = Struct( |
| name: '_ObjCBlock', |
| isInternal: true, |
| members: [ |
| Member(name: 'isa', type: PointerType(voidType)), |
| Member(name: 'flags', type: intType), |
| Member(name: 'reserved', type: intType), |
| Member(name: 'invoke', type: PointerType(voidType)), |
| Member(name: 'descriptor', type: PointerType(blockDescStruct)), |
| Member(name: 'target', type: PointerType(voidType)), |
| ], |
| ); |
| late final blockDescStruct = Struct( |
| name: '_ObjCBlockDesc', |
| isInternal: true, |
| members: [ |
| Member(name: 'reserved', type: unsignedLongType), |
| Member(name: 'size', type: unsignedLongType), |
| Member(name: 'copy_helper', type: PointerType(voidType)), |
| Member(name: 'dispose_helper', type: PointerType(voidType)), |
| Member(name: 'signature', type: PointerType(charType)), |
| ], |
| ); |
| late final newBlockDesc = |
| ObjCInternalFunction('_newBlockDesc', null, (Writer w, String name) { |
| final blockType = blockStruct.getCType(w); |
| final descType = blockDescStruct.getCType(w); |
| final descPtr = PointerType(blockDescStruct).getCType(w); |
| return ''' |
| $descPtr $name() { |
| final d = ${w.ffiPkgLibraryPrefix}.calloc.allocate<$descType>( |
| ${w.ffiLibraryPrefix}.sizeOf<$descType>()); |
| d.ref.reserved = 0; |
| d.ref.size = ${w.ffiLibraryPrefix}.sizeOf<$blockType>(); |
| d.ref.copy_helper = ${w.ffiLibraryPrefix}.nullptr; |
| d.ref.dispose_helper = ${w.ffiLibraryPrefix}.nullptr; |
| d.ref.signature = ${w.ffiLibraryPrefix}.nullptr; |
| return d; |
| } |
| '''; |
| }); |
| late final blockDescSingleton = ObjCInternalGlobal( |
| '_objc_block_desc', |
| (Writer w) => '${newBlockDesc.name}()', |
| blockDescStruct, |
| ); |
| late final concreteGlobalBlock = ObjCInternalGlobal( |
| '_objc_concrete_global_block', |
| (Writer w) => '${w.lookupFuncIdentifier}<${voidType.getCType(w)}>(' |
| "'_NSConcreteGlobalBlock')", |
| ); |
| late final newBlock = ObjCInternalFunction('_newBlock', _blockCopyFunc, |
| (Writer w, String name) { |
| final blockType = blockStruct.getCType(w); |
| final blockPtr = PointerType(blockStruct).getCType(w); |
| final voidPtr = PointerType(voidType).getCType(w); |
| return ''' |
| $blockPtr $name($voidPtr invoke, $voidPtr target) { |
| final b = ${w.ffiPkgLibraryPrefix}.calloc.allocate<$blockType>( |
| ${w.ffiLibraryPrefix}.sizeOf<$blockType>()); |
| b.ref.isa = ${concreteGlobalBlock.name}; |
| b.ref.flags = 0; |
| b.ref.reserved = 0; |
| b.ref.invoke = invoke; |
| b.ref.target = target; |
| b.ref.descriptor = ${blockDescSingleton.name}; |
| final copy = ${_blockCopyFunc.name}(b.cast()).cast<$blockType>(); |
| ${w.ffiPkgLibraryPrefix}.calloc.free(b); |
| return copy; |
| } |
| '''; |
| }); |
| |
| void _writeFinalizableClass( |
| Writer w, |
| StringBuffer s, |
| String name, |
| String kind, |
| String idType, |
| String retain, |
| String release, |
| String finalizer) { |
| s.write(''' |
| class $name implements ${w.ffiLibraryPrefix}.Finalizable { |
| final $idType _id; |
| final ${w.className} _lib; |
| bool _pendingRelease; |
| |
| $name._(this._id, this._lib, |
| {bool retain = false, bool release = false}) : _pendingRelease = release { |
| if (retain) { |
| _lib.$retain(_id.cast()); |
| } |
| if (release) { |
| _lib.$finalizer.attach(this, _id.cast(), detach: this); |
| } |
| } |
| |
| /// Releases the reference to the underlying ObjC $kind held by this wrapper. |
| /// Throws a StateError if this wrapper doesn't currently hold a reference. |
| void release() { |
| if (_pendingRelease) { |
| _pendingRelease = false; |
| _lib.$release(_id.cast()); |
| _lib.$finalizer.detach(this); |
| } else { |
| throw StateError( |
| 'Released an ObjC $kind that was unowned or already released.'); |
| } |
| } |
| |
| @override |
| bool operator ==(Object other) { |
| return other is $name && _id == other._id; |
| } |
| |
| @override |
| int get hashCode => _id.hashCode; |
| |
| /// Return a pointer to this object. |
| $idType get pointer => _id; |
| |
| $idType _retainAndReturnId() { |
| _lib.$retain(_id.cast()); |
| return _id; |
| } |
| } |
| '''); |
| } |
| |
| bool utilsExist = false; |
| void ensureUtilsExist(Writer w, StringBuffer s) { |
| if (utilsExist) return; |
| utilsExist = true; |
| _writeFinalizableClass( |
| w, |
| s, |
| '_ObjCWrapper', |
| 'object', |
| PointerType(objCObjectType).getCType(w), |
| _retainFunc.name, |
| _releaseFunc.name, |
| _releaseFinalizer.name); |
| } |
| |
| bool blockUtilsExist = false; |
| void ensureBlockUtilsExist(Writer w, StringBuffer s) { |
| if (blockUtilsExist) return; |
| blockUtilsExist = true; |
| _writeFinalizableClass( |
| w, |
| s, |
| '_ObjCBlockBase', |
| 'block', |
| PointerType(blockStruct).getCType(w), |
| _blockCopyFunc.name, |
| _blockReleaseFunc.name, |
| _blockReleaseFinalizer.name); |
| } |
| |
| void addDependencies(Set<Binding> dependencies) { |
| registerName.addDependencies(dependencies); |
| getClass.addDependencies(dependencies); |
| _retainFunc.addDependencies(dependencies); |
| _releaseFunc.addDependencies(dependencies); |
| _releaseFinalizer.addDependencies(dependencies); |
| for (final msgSendFunc in _msgSendFuncs.values) { |
| msgSendFunc.func.addDependencies(dependencies); |
| } |
| for (final sel in _selObjects.values) { |
| sel.addDependencies(dependencies); |
| } |
| } |
| |
| void addBlockDependencies(Set<Binding> dependencies) { |
| newBlockDesc.addDependencies(dependencies); |
| blockDescSingleton.addDependencies(dependencies); |
| blockStruct.addDependencies(dependencies); |
| concreteGlobalBlock.addDependencies(dependencies); |
| newBlock.addDependencies(dependencies); |
| _blockCopyFunc.addDependencies(dependencies); |
| _blockReleaseFunc.addDependencies(dependencies); |
| _blockReleaseFinalizer.addDependencies(dependencies); |
| } |
| |
| final _interfaceRegistry = <String, ObjCInterface>{}; |
| void registerInterface(ObjCInterface interface) { |
| _interfaceRegistry[interface.originalName] = interface; |
| } |
| |
| ObjCInterface get nsData { |
| return _interfaceRegistry["NSData"] ?? |
| (ObjCInterface( |
| originalName: "NSData", |
| builtInFunctions: this, |
| )); |
| } |
| |
| void generateNSStringUtils(Writer w, StringBuffer s) { |
| // Generate a constructor that wraps stringWithCharacters, and a toString |
| // method that wraps dataUsingEncoding. |
| s.write(''' |
| factory NSString(${w.className} _lib, String str) { |
| final cstr = str.toNativeUtf16(); |
| final nsstr = stringWithCharacters_length_(_lib, cstr.cast(), str.length); |
| ${w.ffiPkgLibraryPrefix}.calloc.free(cstr); |
| return nsstr; |
| } |
| |
| @override |
| String toString() { |
| final data = dataUsingEncoding_( |
| 0x94000100 /* NSUTF16LittleEndianStringEncoding */); |
| return data!.bytes.cast<${w.ffiPkgLibraryPrefix}.Utf16>().toDartString( |
| length: length); |
| } |
| '''); |
| } |
| |
| void generateStringUtils(Writer w, StringBuffer s) { |
| // Generate an extension on String to convert to NSString |
| s.write(''' |
| extension StringToNSString on String { |
| NSString toNSString(${w.className} lib) => NSString(lib, this); |
| } |
| '''); |
| } |
| } |
| |
| /// Functions only used internally by ObjC bindings, which may or may not wrap a |
| /// native function, such as getClass. |
| class ObjCInternalFunction extends LookUpBinding { |
| final Func? _wrappedFunction; |
| final String Function(Writer, String) _toBindingString; |
| |
| ObjCInternalFunction( |
| String name, this._wrappedFunction, this._toBindingString) |
| : super(originalName: name, name: name, isInternal: true); |
| |
| @override |
| BindingString toBindingString(Writer w) { |
| name = w.wrapperLevelUniqueNamer.makeUnique(name); |
| return BindingString( |
| type: BindingStringType.func, string: _toBindingString(w, name)); |
| } |
| |
| @override |
| void addDependencies(Set<Binding> dependencies) { |
| if (dependencies.contains(this)) return; |
| dependencies.add(this); |
| _wrappedFunction?.addDependencies(dependencies); |
| } |
| } |
| |
| /// Globals only used internally by ObjC bindings, such as classes and SELs. |
| class ObjCInternalGlobal extends LookUpBinding { |
| final String Function(Writer) makeValue; |
| Binding? binding; |
| |
| ObjCInternalGlobal(String name, this.makeValue, [this.binding]) |
| : super(originalName: name, name: name, isInternal: true); |
| |
| @override |
| BindingString toBindingString(Writer w) { |
| final s = StringBuffer(); |
| name = w.wrapperLevelUniqueNamer.makeUnique(name); |
| s.write('late final $name = ${makeValue(w)};'); |
| return BindingString(type: BindingStringType.global, string: s.toString()); |
| } |
| |
| @override |
| void addDependencies(Set<Binding> dependencies) { |
| if (dependencies.contains(this)) return; |
| dependencies.add(this); |
| binding?.addDependencies(dependencies); |
| } |
| } |
| |
| enum ObjCMsgSendVariant { |
| normal('objc_msgSend'), |
| stret('objc_msgSend_stret'), |
| fpret('objc_msgSend_fpret'); |
| |
| final String name; |
| const ObjCMsgSendVariant(this.name); |
| |
| static ObjCMsgSendVariant fromReturnType(Type returnType) { |
| if (returnType is Compound && returnType.isStruct) { |
| return ObjCMsgSendVariant.stret; |
| } else if (returnType == floatType || returnType == doubleType) { |
| return ObjCMsgSendVariant.fpret; |
| } |
| return ObjCMsgSendVariant.normal; |
| } |
| } |
| |
| /// A wrapper around the objc_msgSend function, or the stret or fpret variants. |
| class ObjCMsgSendFunc { |
| final ObjCMsgSendVariant variant; |
| late final Func func; |
| |
| ObjCMsgSendFunc(String name, Type returnType, List<ObjCMethodParam> params) |
| : variant = ObjCMsgSendVariant.fromReturnType(returnType) { |
| func = Func( |
| name: name, |
| originalName: variant.name, |
| returnType: isStret ? voidType : returnType, |
| parameters: [ |
| if (isStret) Parameter(name: 'stret', type: PointerType(returnType)), |
| Parameter(name: 'obj', type: PointerType(objCObjectType)), |
| Parameter(name: 'sel', type: PointerType(objCSelType)), |
| for (final p in params) Parameter(name: p.name, type: p.type), |
| ], |
| isInternal: true, |
| ); |
| } |
| |
| String get name => func.name; |
| bool get isStret => variant == ObjCMsgSendVariant.stret; |
| |
| void addDependencies(Set<Binding> dependencies) { |
| func.addDependencies(dependencies); |
| } |
| } |