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

dart-lang / ffigen / 4904287101

07 May 2023 12:19AM UTC coverage: 91.87%. Remained the same
4904287101

push

github

GitHub
Add the ability to get a pointer to an objective C object (#568)

3356 of 3653 relevant lines covered (91.87%)

25.29 hits per line

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

99.43
/lib/src/code_generator/objc_built_in_functions.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
/// Built in functions used by the Objective C bindings.
11
class ObjCBuiltInFunctions {
12
  late final _registerNameFunc = Func(
4✔
13
    name: '_sel_registerName',
14
    originalName: 'sel_registerName',
15
    returnType: PointerType(objCSelType),
4✔
16
    parameters: [Parameter(name: 'str', type: PointerType(charType))],
8✔
17
    isInternal: true,
18
  );
19
  late final registerName = ObjCInternalFunction(
4✔
20
      '_registerName', _registerNameFunc, (Writer w, String name) {
4✔
21
    final selType = _registerNameFunc.functionType.returnType.getCType(w);
8✔
22
    return '''
23
$selType $name(String name) {
24
  final cstr = name.toNativeUtf8();
25
  final sel = ${_registerNameFunc.name}(cstr.cast());
4✔
26
  ${w.ffiPkgLibraryPrefix}.calloc.free(cstr);
2✔
27
  return sel;
28
}
29
''';
2✔
30
  });
31

32
  late final _getClassFunc = Func(
4✔
33
    name: '_objc_getClass',
34
    originalName: 'objc_getClass',
35
    returnType: PointerType(objCObjectType),
4✔
36
    parameters: [Parameter(name: 'str', type: PointerType(charType))],
8✔
37
    isInternal: true,
38
  );
39
  late final getClass =
2✔
40
      ObjCInternalFunction('_getClass', _getClassFunc, (Writer w, String name) {
6✔
41
    final objType = _getClassFunc.functionType.returnType.getCType(w);
8✔
42
    return '''
43
$objType $name(String name) {
44
  final cstr = name.toNativeUtf8();
45
  final clazz = ${_getClassFunc.name}(cstr.cast());
4✔
46
  ${w.ffiPkgLibraryPrefix}.calloc.free(cstr);
2✔
47
  if (clazz == ${w.ffiLibraryPrefix}.nullptr) {
2✔
48
    throw Exception('Failed to load Objective-C class: \$name');
49
  }
50
  return clazz;
51
}
52
''';
2✔
53
  });
54

55
  late final _retainFunc = Func(
4✔
56
    name: '_objc_retain',
57
    originalName: 'objc_retain',
58
    returnType: PointerType(objCObjectType),
4✔
59
    parameters: [Parameter(name: 'value', type: PointerType(objCObjectType))],
8✔
60
    isInternal: true,
61
  );
62
  late final _releaseFunc = Func(
4✔
63
    name: '_objc_release',
64
    originalName: 'objc_release',
65
    returnType: voidType,
2✔
66
    parameters: [Parameter(name: 'value', type: PointerType(objCObjectType))],
8✔
67
    isInternal: true,
68
  );
69
  late final _releaseFinalizer = ObjCInternalGlobal(
4✔
70
    '_objc_releaseFinalizer',
71
    (Writer w) => '${w.ffiLibraryPrefix}.NativeFinalizer('
6✔
72
        '${_releaseFunc.funcPointerName}.cast())',
4✔
73
    _releaseFunc,
2✔
74
  );
75

76
  late final _blockCopyFunc = Func(
4✔
77
    name: '_Block_copy',
78
    originalName: '_Block_copy',
79
    returnType: PointerType(voidType),
4✔
80
    parameters: [Parameter(name: 'value', type: PointerType(voidType))],
8✔
81
    isInternal: true,
82
  );
83
  late final _blockReleaseFunc = Func(
4✔
84
    name: '_Block_release',
85
    originalName: '_Block_release',
86
    returnType: voidType,
2✔
87
    parameters: [Parameter(name: 'value', type: PointerType(voidType))],
8✔
88
    isInternal: true,
89
  );
90
  late final _blockReleaseFinalizer = ObjCInternalGlobal(
4✔
91
    '_objc_releaseFinalizer',
92
    (Writer w) => '${w.ffiLibraryPrefix}.NativeFinalizer('
6✔
93
        '${_blockReleaseFunc.funcPointerName}.cast())',
4✔
94
    _blockReleaseFunc,
2✔
95
  );
96

97
  // We need to load a separate instance of objc_msgSend for each signature.
98
  final _msgSendFuncs = <String, Func>{};
99
  Func getMsgSendFunc(Type returnType, List<ObjCMethodParam> params) {
2✔
100
    var key = returnType.cacheKey();
2✔
101
    for (final p in params) {
4✔
102
      key += ' ${p.type.cacheKey()}';
8✔
103
    }
104
    return _msgSendFuncs[key] ??= Func(
6✔
105
      name: '_objc_msgSend_${_msgSendFuncs.length}',
6✔
106
      originalName: 'objc_msgSend',
107
      returnType: returnType,
108
      parameters: [
2✔
109
        Parameter(name: 'obj', type: PointerType(objCObjectType)),
6✔
110
        Parameter(name: 'sel', type: PointerType(objCSelType)),
6✔
111
        for (final p in params) Parameter(name: p.name, type: p.type),
8✔
112
      ],
113
      isInternal: true,
114
    );
115
  }
116

117
  final _selObjects = <String, ObjCInternalGlobal>{};
118
  ObjCInternalGlobal getSelObject(String methodName) {
2✔
119
    return _selObjects[methodName] ??= ObjCInternalGlobal(
6✔
120
      '_sel_${methodName.replaceAll(":", "_")}',
4✔
121
      (Writer w) => '${registerName.name}("$methodName")',
8✔
122
      registerName,
2✔
123
    );
124
  }
125

126
  // See https://clang.llvm.org/docs/Block-ABI-Apple.html
127
  late final blockStruct = Struct(
4✔
128
    name: '_ObjCBlock',
129
    isInternal: true,
130
    members: [
2✔
131
      Member(name: 'isa', type: PointerType(voidType)),
6✔
132
      Member(name: 'flags', type: intType),
4✔
133
      Member(name: 'reserved', type: intType),
4✔
134
      Member(name: 'invoke', type: PointerType(voidType)),
6✔
135
      Member(name: 'descriptor', type: PointerType(blockDescStruct)),
6✔
136
      Member(name: 'target', type: PointerType(voidType)),
6✔
137
    ],
138
  );
139
  late final blockDescStruct = Struct(
4✔
140
    name: '_ObjCBlockDesc',
141
    isInternal: true,
142
    members: [
2✔
143
      Member(name: 'reserved', type: unsignedLongType),
4✔
144
      Member(name: 'size', type: unsignedLongType),
4✔
145
      Member(name: 'copy_helper', type: PointerType(voidType)),
6✔
146
      Member(name: 'dispose_helper', type: PointerType(voidType)),
6✔
147
      Member(name: 'signature', type: PointerType(charType)),
6✔
148
    ],
149
  );
150
  late final newBlockDesc =
2✔
151
      ObjCInternalFunction('_newBlockDesc', null, (Writer w, String name) {
4✔
152
    final blockType = blockStruct.getCType(w);
4✔
153
    final descType = blockDescStruct.getCType(w);
4✔
154
    final descPtr = PointerType(blockDescStruct).getCType(w);
6✔
155
    return '''
156
$descPtr $name() {
157
  final d = ${w.ffiPkgLibraryPrefix}.calloc.allocate<$descType>(
2✔
158
      ${w.ffiLibraryPrefix}.sizeOf<$descType>());
2✔
159
  d.ref.reserved = 0;
160
  d.ref.size = ${w.ffiLibraryPrefix}.sizeOf<$blockType>();
2✔
161
  d.ref.copy_helper = ${w.ffiLibraryPrefix}.nullptr;
2✔
162
  d.ref.dispose_helper = ${w.ffiLibraryPrefix}.nullptr;
2✔
163
  d.ref.signature = ${w.ffiLibraryPrefix}.nullptr;
2✔
164
  return d;
165
}
166
''';
2✔
167
  });
168
  late final blockDescSingleton = ObjCInternalGlobal(
4✔
169
    '_objc_block_desc',
170
    (Writer w) => '${newBlockDesc.name}()',
8✔
171
    blockDescStruct,
2✔
172
  );
173
  late final concreteGlobalBlock = ObjCInternalGlobal(
4✔
174
    '_objc_concrete_global_block',
175
    (Writer w) => '${w.lookupFuncIdentifier}<${voidType.getCType(w)}>('
10✔
176
        "'_NSConcreteGlobalBlock')",
177
  );
178
  late final newBlock = ObjCInternalFunction('_newBlock', _blockCopyFunc,
6✔
179
      (Writer w, String name) {
2✔
180
    final blockType = blockStruct.getCType(w);
4✔
181
    final blockPtr = PointerType(blockStruct).getCType(w);
6✔
182
    final voidPtr = PointerType(voidType).getCType(w);
6✔
183
    return '''
184
$blockPtr $name($voidPtr invoke, $voidPtr target) {
185
  final b = ${w.ffiPkgLibraryPrefix}.calloc.allocate<$blockType>(
2✔
186
      ${w.ffiLibraryPrefix}.sizeOf<$blockType>());
2✔
187
  b.ref.isa = ${concreteGlobalBlock.name};
4✔
188
  b.ref.flags = 0;
189
  b.ref.reserved = 0;
190
  b.ref.invoke = invoke;
191
  b.ref.target = target;
192
  b.ref.descriptor = ${blockDescSingleton.name};
4✔
193
  final copy = ${_blockCopyFunc.name}(b.cast()).cast<$blockType>();
4✔
194
  ${w.ffiPkgLibraryPrefix}.calloc.free(b);
2✔
195
  return copy;
196
}
197
''';
2✔
198
  });
199

200
  void _writeFinalizableClass(
2✔
201
      Writer w,
202
      StringBuffer s,
203
      String name,
204
      String kind,
205
      String idType,
206
      String retain,
207
      String release,
208
      String finalizer) {
209
    s.write('''
2✔
210
class $name implements ${w.ffiLibraryPrefix}.Finalizable {
2✔
211
  final $idType _id;
212
  final ${w.className} _lib;
2✔
213
  bool _pendingRelease;
214

215
  $name._(this._id, this._lib,
216
      {bool retain = false, bool release = false}) : _pendingRelease = release {
217
    if (retain) {
218
      _lib.$retain(_id.cast());
219
    }
220
    if (release) {
221
      _lib.$finalizer.attach(this, _id.cast(), detach: this);
222
    }
223
  }
224

225
  /// Releases the reference to the underlying ObjC $kind held by this wrapper.
226
  /// Throws a StateError if this wrapper doesn't currently hold a reference.
227
  void release() {
228
    if (_pendingRelease) {
229
      _pendingRelease = false;
230
      _lib.$release(_id.cast());
231
      _lib.$finalizer.detach(this);
232
    } else {
233
      throw StateError(
234
          'Released an ObjC $kind that was unowned or already released.');
235
    }
236
  }
237

238
  @override
239
  bool operator ==(Object other) {
240
    return other is $name && _id == other._id;
241
  }
242

243
  @override
244
  int get hashCode => _id.hashCode;
245

246
  /// Return a pointer to this object.
247
  $idType get pointer => _id;
248
}
249
''');
2✔
250
  }
251

252
  bool utilsExist = false;
253
  void ensureUtilsExist(Writer w, StringBuffer s) {
2✔
254
    if (utilsExist) return;
2✔
255
    utilsExist = true;
2✔
256
    _writeFinalizableClass(
2✔
257
        w,
258
        s,
259
        '_ObjCWrapper',
260
        'object',
261
        PointerType(objCObjectType).getCType(w),
6✔
262
        _retainFunc.name,
4✔
263
        _releaseFunc.name,
4✔
264
        _releaseFinalizer.name);
4✔
265
  }
266

267
  bool blockUtilsExist = false;
268
  void ensureBlockUtilsExist(Writer w, StringBuffer s) {
2✔
269
    if (blockUtilsExist) return;
2✔
270
    blockUtilsExist = true;
2✔
271
    _writeFinalizableClass(
2✔
272
        w,
273
        s,
274
        '_ObjCBlockBase',
275
        'block',
276
        PointerType(blockStruct).getCType(w),
6✔
277
        _blockCopyFunc.name,
4✔
278
        _blockReleaseFunc.name,
4✔
279
        _blockReleaseFinalizer.name);
4✔
280
  }
281

282
  void addDependencies(Set<Binding> dependencies) {
2✔
283
    registerName.addDependencies(dependencies);
4✔
284
    getClass.addDependencies(dependencies);
4✔
285
    _retainFunc.addDependencies(dependencies);
4✔
286
    _releaseFunc.addDependencies(dependencies);
4✔
287
    _releaseFinalizer.addDependencies(dependencies);
4✔
288
    for (final func in _msgSendFuncs.values) {
6✔
289
      func.addDependencies(dependencies);
2✔
290
    }
291
    for (final sel in _selObjects.values) {
6✔
292
      sel.addDependencies(dependencies);
2✔
293
    }
294
  }
295

296
  void addBlockDependencies(Set<Binding> dependencies) {
2✔
297
    newBlockDesc.addDependencies(dependencies);
4✔
298
    blockDescSingleton.addDependencies(dependencies);
4✔
299
    blockStruct.addDependencies(dependencies);
4✔
300
    concreteGlobalBlock.addDependencies(dependencies);
4✔
301
    newBlock.addDependencies(dependencies);
4✔
302
    _blockCopyFunc.addDependencies(dependencies);
4✔
303
    _blockReleaseFunc.addDependencies(dependencies);
4✔
304
    _blockReleaseFinalizer.addDependencies(dependencies);
4✔
305
  }
306

307
  final _interfaceRegistry = <String, ObjCInterface>{};
308
  void registerInterface(ObjCInterface interface) {
2✔
309
    _interfaceRegistry[interface.originalName] = interface;
6✔
310
  }
311

312
  ObjCInterface get nsData {
2✔
313
    return _interfaceRegistry["NSData"] ??
4✔
314
        (ObjCInterface(
×
315
          originalName: "NSData",
316
          builtInFunctions: this,
317
        ));
318
  }
319

320
  void generateNSStringUtils(Writer w, StringBuffer s) {
2✔
321
    // Generate a constructor that wraps stringWithCharacters, and a toString
322
    // method that wraps dataUsingEncoding.
323
    s.write('''
2✔
324
  factory NSString(${w.className} _lib, String str) {
2✔
325
    final cstr = str.toNativeUtf16();
326
    final nsstr = stringWithCharacters_length_(_lib, cstr.cast(), str.length);
327
    ${w.ffiPkgLibraryPrefix}.calloc.free(cstr);
2✔
328
    return nsstr;
329
  }
330

331
  @override
332
  String toString() {
333
    final data = dataUsingEncoding_(
334
        0x94000100 /* NSUTF16LittleEndianStringEncoding */);
335
    return data.bytes.cast<${w.ffiPkgLibraryPrefix}.Utf16>().toDartString(
2✔
336
        length: length);
337
  }
338
''');
2✔
339
  }
340

341
  void generateStringUtils(Writer w, StringBuffer s) {
2✔
342
    // Generate an extension on String to convert to NSString
343
    s.write('''
2✔
344
extension StringToNSString on String {
345
  NSString toNSString(${w.className} lib) => NSString(lib, this);
2✔
346
}
347
''');
2✔
348
  }
349
}
350

351
/// Functions only used internally by ObjC bindings, which may or may not wrap a
352
/// native function, such as getClass.
353
class ObjCInternalFunction extends LookUpBinding {
354
  final Func? _wrappedFunction;
355
  final String Function(Writer, String) _toBindingString;
356

357
  ObjCInternalFunction(
2✔
358
      String name, this._wrappedFunction, this._toBindingString)
359
      : super(originalName: name, name: name, isInternal: true);
2✔
360

361
  @override
2✔
362
  BindingString toBindingString(Writer w) {
363
    name = w.wrapperLevelUniqueNamer.makeUnique(name);
8✔
364
    return BindingString(
2✔
365
        type: BindingStringType.func, string: _toBindingString(w, name));
6✔
366
  }
367

368
  @override
2✔
369
  void addDependencies(Set<Binding> dependencies) {
370
    if (dependencies.contains(this)) return;
2✔
371
    dependencies.add(this);
2✔
372
    _wrappedFunction?.addDependencies(dependencies);
4✔
373
  }
374
}
375

376
/// Globals only used internally by ObjC bindings, such as classes and SELs.
377
class ObjCInternalGlobal extends LookUpBinding {
378
  final String Function(Writer) makeValue;
379
  Binding? binding;
380

381
  ObjCInternalGlobal(String name, this.makeValue, [this.binding])
2✔
382
      : super(originalName: name, name: name, isInternal: true);
2✔
383

384
  @override
2✔
385
  BindingString toBindingString(Writer w) {
386
    final s = StringBuffer();
2✔
387
    name = w.wrapperLevelUniqueNamer.makeUnique(name);
8✔
388
    s.write('late final $name = ${makeValue(w)};');
10✔
389
    return BindingString(type: BindingStringType.global, string: s.toString());
4✔
390
  }
391

392
  @override
2✔
393
  void addDependencies(Set<Binding> dependencies) {
394
    if (dependencies.contains(this)) return;
2✔
395
    dependencies.add(this);
2✔
396
    binding?.addDependencies(dependencies);
4✔
397
  }
398
}
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

© 2026 Coveralls, Inc