• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dart-lang / ffigen / 6727396944

02 Nov 2023 02:02AM UTC coverage: 91.853% (-0.3%) from 92.193%
6727396944

push

github

web-flow
ObjC static functions (#633)

* WIP static functions

* Revert old code gen changes

* Refactor function code gen

* Arg and return conversions

* fix bugs

* Fix analysis, add more tests

* fmt

* Fix autorelease pool test

* Handle NS_RETURNS_RETAINED

* Daco's comments

54 of 54 new or added lines in 9 files covered. (100.0%)

3732 of 4063 relevant lines covered (91.85%)

28.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.0
/lib/src/code_generator/objc_block.dart
1
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2
// for details. All rights reserved. Use of this source code is governed by a
3
// BSD-style license that can be found in the LICENSE file.
4

5
import 'package:ffigen/src/code_generator.dart';
6

7
import 'binding_string.dart';
8
import 'writer.dart';
9

10
class ObjCBlock extends BindingType {
11
  final Type returnType;
12
  final List<Type> argTypes;
13
  final ObjCBuiltInFunctions builtInFunctions;
14

15
  ObjCBlock({
2✔
16
    required String usr,
17
    required Type returnType,
18
    required List<Type> argTypes,
19
    required ObjCBuiltInFunctions builtInFunctions,
20
  }) : this._(
2✔
21
          usr: usr,
22
          name: _getBlockName(returnType, argTypes),
2✔
23
          returnType: returnType,
24
          argTypes: argTypes,
25
          builtInFunctions: builtInFunctions,
26
        );
27

28
  ObjCBlock._({
2✔
29
    required String super.usr,
30
    required super.name,
31
    required this.returnType,
32
    required this.argTypes,
33
    required this.builtInFunctions,
34
  }) : super(originalName: name);
2✔
35

36
  // Generates a human readable name for the block based on the args and return
37
  // type. These names will be pretty verbose and unweildy, but they're at least
38
  // sensible and stable. Users can always add their own typedef with a simpler
39
  // name if necessary.
40
  static String _getBlockName(Type returnType, List<Type> argTypes) =>
2✔
41
      'ObjCBlock_${[returnType, ...argTypes].map(_typeName).join('_')}';
10✔
42
  static String _typeName(Type type) =>
2✔
43
      type.toString().replaceAll(_illegalNameChar, '');
6✔
44
  static final _illegalNameChar = RegExp(r'[^0-9a-zA-Z]');
6✔
45

46
  @override
2✔
47
  BindingString toBindingString(Writer w) {
48
    final s = StringBuffer();
2✔
49

50
    builtInFunctions.ensureBlockUtilsExist(w, s);
4✔
51

52
    final params = <Parameter>[];
2✔
53
    for (int i = 0; i < argTypes.length; ++i) {
8✔
54
      params.add(Parameter(name: 'arg$i', type: argTypes[i]));
10✔
55
    }
56

57
    final isVoid = returnType == voidType;
6✔
58
    final voidPtr = PointerType(voidType).getCType(w);
6✔
59
    final blockPtr = PointerType(builtInFunctions.blockStruct);
6✔
60
    final funcType = FunctionType(returnType: returnType, parameters: params);
4✔
61
    final natFnType = NativeFunc(funcType);
2✔
62
    final natFnPtr = PointerType(natFnType).getCType(w);
4✔
63
    final funcPtrTrampoline =
64
        w.topLevelUniqueNamer.makeUnique('_${name}_fnPtrTrampoline');
8✔
65
    final closureTrampoline =
66
        w.topLevelUniqueNamer.makeUnique('_${name}_closureTrampoline');
8✔
67
    final registerClosure =
68
        w.topLevelUniqueNamer.makeUnique('_${name}_registerClosure');
8✔
69
    final closureRegistry =
70
        w.topLevelUniqueNamer.makeUnique('_${name}_closureRegistry');
8✔
71
    final closureRegistryIndex =
72
        w.topLevelUniqueNamer.makeUnique('_${name}_closureRegistryIndex');
8✔
73
    final trampFuncType = FunctionType(
2✔
74
        returnType: returnType,
2✔
75
        parameters: [Parameter(type: blockPtr, name: 'block'), ...params]);
6✔
76
    final trampFuncCType = trampFuncType.getCType(w, writeArgumentNames: false);
2✔
77
    final trampFuncFfiDartType =
78
        trampFuncType.getFfiDartType(w, writeArgumentNames: false);
2✔
79
    final natTrampFnType = NativeFunc(trampFuncType).getCType(w);
4✔
80
    final nativeCallableType =
81
        '${w.ffiLibraryPrefix}.NativeCallable<$trampFuncCType>';
4✔
82
    final funcDartType = funcType.getDartType(w, writeArgumentNames: false);
2✔
83
    final funcFfiDartType =
84
        funcType.getFfiDartType(w, writeArgumentNames: false);
2✔
85
    final returnFfiDartType = returnType.getFfiDartType(w);
4✔
86
    final blockCType = blockPtr.getCType(w);
2✔
87

88
    final paramsNameOnly = params.map((p) => p.name).join(', ');
8✔
89
    final paramsFfiDartType =
90
        params.map((p) => '${p.type.getFfiDartType(w)} ${p.name}').join(', ');
14✔
91
    final paramsDartType =
92
        params.map((p) => '${p.type.getDartType(w)} ${p.name}').join(', ');
14✔
93

94
    // Write the function pointer based trampoline function.
95
    s.write('''
2✔
96
$returnFfiDartType $funcPtrTrampoline($blockCType block, $paramsFfiDartType) =>
97
    block.ref.target.cast<${natFnType.getFfiDartType(w)}>()
2✔
98
        .asFunction<$funcFfiDartType>()($paramsNameOnly);
99
''');
2✔
100

101
    // Write the closure registry function.
102
    s.write('''
2✔
103
final $closureRegistry = <int, $funcFfiDartType>{};
104
int $closureRegistryIndex = 0;
105
$voidPtr $registerClosure($funcFfiDartType fn) {
106
  final id = ++$closureRegistryIndex;
107
  $closureRegistry[id] = fn;
108
  return $voidPtr.fromAddress(id);
109
}
110
''');
2✔
111

112
    // Write the closure based trampoline function.
113
    s.write('''
2✔
114
$returnFfiDartType $closureTrampoline($blockCType block, $paramsFfiDartType) =>
115
    $closureRegistry[block.ref.target.address]!($paramsNameOnly);
116
''');
2✔
117

118
    // Snippet that converts a Dart typed closure to FfiDart type. This snippet
119
    // is used below. Note that the closure being converted is called `fn`.
120
    final convertedFnArgs = params
121
        .map((p) => p.type
6✔
122
            .convertFfiDartTypeToDartType(w, p.name, 'lib', objCRetain: true))
4✔
123
        .join(', ');
2✔
124
    final convFnInvocation = returnType.convertDartTypeToFfiDartType(
4✔
125
        w, 'fn($convertedFnArgs)',
2✔
126
        objCRetain: true);
127
    final convFn = '($paramsFfiDartType) => $convFnInvocation';
2✔
128

129
    // Write the wrapper class.
130
    final defaultValue = returnType.getDefaultValue(w, '_lib');
4✔
131
    final exceptionalReturn = defaultValue == null ? '' : ', $defaultValue';
2✔
132
    s.write('''
2✔
133
class $name extends _ObjCBlockBase {
2✔
134
  $name._($blockCType id, ${w.className} lib,
4✔
135
      {bool retain = false, bool release = true}) :
136
          super._(id, lib, retain: retain, release: release);
137

138
  /// Creates a block from a C function pointer.
139
  ///
140
  /// This block must be invoked by native code running on the same thread as
141
  /// the isolate that registered it. Invoking the block on the wrong thread
142
  /// will result in a crash.
143
  $name.fromFunctionPointer(${w.className} lib, $natFnPtr ptr) :
4✔
144
      this._(lib.${builtInFunctions.newBlock.name}(
6✔
145
          _cFuncTrampoline ??= ${w.ffiLibraryPrefix}.Pointer.fromFunction<
2✔
146
              $trampFuncCType>($funcPtrTrampoline
147
                  $exceptionalReturn).cast(), ptr.cast()), lib);
148
  static $voidPtr? _cFuncTrampoline;
149

150
  /// Creates a block from a Dart function.
151
  ///
152
  /// This block must be invoked by native code running on the same thread as
153
  /// the isolate that registered it. Invoking the block on the wrong thread
154
  /// will result in a crash.
155
  $name.fromFunction(${w.className} lib, $funcDartType fn) :
4✔
156
      this._(lib.${builtInFunctions.newBlock.name}(
6✔
157
          _dartFuncTrampoline ??= ${w.ffiLibraryPrefix}.Pointer.fromFunction<
2✔
158
              $trampFuncCType>($closureTrampoline
159
                  $exceptionalReturn).cast(), $registerClosure($convFn)), lib);
160
  static $voidPtr? _dartFuncTrampoline;
161

162
''');
2✔
163

164
    // Listener block constructor is only available for void blocks.
165
    if (isVoid) {
166
      s.write('''
2✔
167
  /// Creates a listener block from a Dart function.
168
  ///
169
  /// This is based on FFI's NativeCallable.listener, and has the same
170
  /// capabilities and limitations. This block can be invoked from any thread,
171
  /// but only supports void functions, and is not run synchronously. See
172
  /// NativeCallable.listener for more details.
173
  ///
174
  /// Note that unlike the default behavior of NativeCallable.listener, listener
175
  /// blocks do not keep the isolate alive.
176
  $name.listener(${w.className} lib, $funcDartType fn) :
4✔
177
      this._(lib.${builtInFunctions.newBlock.name}(
6✔
178
          (_dartFuncListenerTrampoline ??= $nativeCallableType.listener(
179
              $closureTrampoline $exceptionalReturn)..keepIsolateAlive =
180
                  false).nativeFunction.cast(),
181
          $registerClosure($convFn)), lib);
182
  static $nativeCallableType? _dartFuncListenerTrampoline;
183

184
''');
2✔
185
    }
186

187
    // Call method.
188
    s.write('  ${returnType.getDartType(w)} call($paramsDartType) =>');
8✔
189
    final callMethodArgs = params
190
        .map((p) =>
4✔
191
            p.type.convertDartTypeToFfiDartType(w, p.name, objCRetain: false))
6✔
192
        .join(', ');
2✔
193
    final callMethodInvocation = '''
194
_id.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType>()(
195
    _id, $callMethodArgs)''';
2✔
196
    s.write(returnType.convertFfiDartTypeToDartType(
6✔
197
        w, callMethodInvocation, '_lib',
198
        objCRetain: false));
199
    s.write(';\n');
2✔
200

201
    s.write('}\n');
2✔
202
    return BindingString(
2✔
203
        type: BindingStringType.objcBlock, string: s.toString());
2✔
204
  }
205

206
  @override
2✔
207
  void addDependencies(Set<Binding> dependencies) {
208
    if (dependencies.contains(this)) return;
2✔
209
    dependencies.add(this);
2✔
210

211
    returnType.addDependencies(dependencies);
4✔
212
    for (final t in argTypes) {
4✔
213
      t.addDependencies(dependencies);
2✔
214
    }
215
    builtInFunctions.addBlockDependencies(dependencies);
4✔
216
  }
217

218
  @override
2✔
219
  String getCType(Writer w) =>
220
      PointerType(builtInFunctions.blockStruct).getCType(w);
8✔
221

222
  @override
2✔
223
  String getDartType(Writer w) => name;
2✔
224

225
  @override
×
226
  bool get sameFfiDartAndCType => true;
227

228
  @override
×
229
  bool get sameDartAndCType => false;
230

231
  @override
×
232
  bool get sameDartAndFfiDartType => false;
233

234
  @override
2✔
235
  String convertDartTypeToFfiDartType(
236
    Writer w,
237
    String value, {
238
    required bool objCRetain,
239
  }) =>
240
      ObjCInterface.generateGetId(value, objCRetain);
2✔
241

242
  @override
2✔
243
  String convertFfiDartTypeToDartType(
244
    Writer w,
245
    String value,
246
    String library, {
247
    required bool objCRetain,
248
    String? objCEnclosingClass,
249
  }) =>
250
      ObjCInterface.generateConstructor(name, value, library, objCRetain);
4✔
251

252
  @override
2✔
253
  String toString() => '($returnType (^)(${argTypes.join(', ')}))';
8✔
254
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc