Skip to content

Commit 0b20cfc

Browse files
committed
Fixes and tests for listener block native bindings
1 parent bafe220 commit 0b20cfc

File tree

14 files changed

+218
-57
lines changed

14 files changed

+218
-57
lines changed

pkgs/ffigen/lib/src/code_generator/compound.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ abstract class Compound extends BindingType {
3636

3737
ObjCBuiltInFunctions? objCBuiltInFunctions;
3838

39+
/// The way the native type is written in C source code. This isn't always the
40+
/// same as the originalName, because the type may need to be prefixed with
41+
/// `struct` or `union`, depending on whether the declaration is a typedef.
42+
final String nativeType;
43+
3944
Compound({
4045
super.usr,
4146
super.originalName,
@@ -47,7 +52,9 @@ abstract class Compound extends BindingType {
4752
List<Member>? members,
4853
super.isInternal,
4954
this.objCBuiltInFunctions,
50-
}) : members = members ?? [];
55+
String? nativeType,
56+
}) : members = members ?? [],
57+
nativeType = nativeType ?? originalName ?? name;
5158

5259
factory Compound.fromType({
5360
required CompoundType type,
@@ -59,6 +66,7 @@ abstract class Compound extends BindingType {
5966
String? dartDoc,
6067
List<Member>? members,
6168
ObjCBuiltInFunctions? objCBuiltInFunctions,
69+
String? nativeType,
6270
}) {
6371
switch (type) {
6472
case CompoundType.struct:
@@ -71,6 +79,7 @@ abstract class Compound extends BindingType {
7179
dartDoc: dartDoc,
7280
members: members,
7381
objCBuiltInFunctions: objCBuiltInFunctions,
82+
nativeType: nativeType,
7483
);
7584
case CompoundType.union:
7685
return Union(
@@ -82,6 +91,7 @@ abstract class Compound extends BindingType {
8291
dartDoc: dartDoc,
8392
members: members,
8493
objCBuiltInFunctions: objCBuiltInFunctions,
94+
nativeType: nativeType,
8595
);
8696
}
8797
}
@@ -170,7 +180,7 @@ abstract class Compound extends BindingType {
170180
String getCType(Writer w) => _isBuiltIn ? '${w.objcPkgPrefix}.$name' : name;
171181

172182
@override
173-
String getNativeType({String varName = ''}) => '$originalName $varName';
183+
String getNativeType({String varName = ''}) => '$nativeType $varName';
174184

175185
@override
176186
bool get sameFfiDartAndCType => true;

pkgs/ffigen/lib/src/code_generator/library.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class Library {
3333
StructPackingOverride? packingOverride,
3434
Set<LibraryImport>? libraryImports,
3535
bool silenceEnumWarning = false,
36+
List<String> entryPoints = const <String>[],
3637
}) {
3738
_findBindings(bindings, sort);
3839

@@ -86,6 +87,7 @@ class Library {
8687
additionalImports: libraryImports,
8788
generateForPackageObjectiveC: generateForPackageObjectiveC,
8889
silenceEnumWarning: silenceEnumWarning,
90+
entryPoints: entryPoints,
8991
);
9092
}
9193

@@ -141,7 +143,7 @@ class Library {
141143
///
142144
/// Returns whether bindings were generated.
143145
bool generateObjCFile(File file) {
144-
final bindings = writer.generateObjC();
146+
final bindings = writer.generateObjC(file.path);
145147

146148
if (bindings == null) {
147149
// No ObjC code needed. If there's already a file (eg from an earlier

pkgs/ffigen/lib/src/code_generator/objc_block.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ pointer.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType>()(
243243

244244
final s = StringBuffer();
245245
s.write('''
246+
246247
typedef ${getNativeType(varName: blockTypedef)};
247248
$blockTypedef $fnName($blockTypedef block) {
248249
$blockTypedef wrapper = [^void(${argsReceived.join(', ')}) {

pkgs/ffigen/lib/src/code_generator/objc_nullable.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class ObjCNullable extends Type {
4040

4141
@override
4242
String getNativeType({String varName = ''}) =>
43-
'${child.getNativeType(varName: varName)} _Nullable';
43+
'${child.getNativeType()} _Nullable $varName';
4444

4545
@override
4646
bool get sameFfiDartAndCType => child.sameFfiDartAndCType;

pkgs/ffigen/lib/src/code_generator/struct.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,6 @@ class Struct extends Compound {
3939
super.members,
4040
super.isInternal,
4141
super.objCBuiltInFunctions,
42+
super.nativeType,
4243
}) : super(compoundType: CompoundType.struct);
4344
}

pkgs/ffigen/lib/src/code_generator/union.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,6 @@ class Union extends Compound {
3737
super.dartDoc,
3838
super.members,
3939
super.objCBuiltInFunctions,
40+
super.nativeType,
4041
}) : super(compoundType: CompoundType.union);
4142
}

pkgs/ffigen/lib/src/code_generator/writer.dart

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'package:ffigen/src/code_generator.dart';
66
import 'package:ffigen/src/code_generator/utils.dart';
77
import 'package:logging/logging.dart';
8+
import 'package:path/path.dart' as p;
89

910
import '../strings.dart' as strings;
1011

@@ -36,6 +37,8 @@ class Writer {
3637

3738
final bool generateForPackageObjectiveC;
3839

40+
final List<String> entryPoints;
41+
3942
/// Tracks where enumType.getCType is called. Reset everytime [generate] is
4043
/// called.
4144
bool usedEnumCType = false;
@@ -132,6 +135,7 @@ class Writer {
132135
this.header,
133136
required this.generateForPackageObjectiveC,
134137
required this.silenceEnumWarning,
138+
required this.entryPoints,
135139
}) {
136140
final globalLevelNameSet = noLookUpBindings.map((e) => e.name).toSet();
137141
final wrapperLevelNameSet = lookUpBindings.map((e) => e.name).toSet();
@@ -387,13 +391,23 @@ class Writer {
387391
}
388392

389393
/// Writes the Objective C code needed for the bindings, if any. Returns null
390-
/// if there are no bindings that need generated ObjC code.
391-
String? generateObjC() {
394+
/// if there are no bindings that need generated ObjC code. This function does
395+
/// not generate the output file, but the [outFilename] does affect the
396+
/// generated code.
397+
String? generateObjC(String outFilename) {
398+
final outDir = p.dirname(outFilename);
399+
392400
final s = StringBuffer();
393401
s.write('''
394402
#include <stdint.h>
395403
396404
''');
405+
406+
for (final entryPoint in entryPoints) {
407+
final path = p.relative(entryPoint, from: outDir);
408+
s.write('#include "$path"\n');
409+
}
410+
397411
bool empty = true;
398412
for (final binding in _allBindings) {
399413
final bindingString = binding.toObjCBindingString(this);

pkgs/ffigen/lib/src/config_provider/spec_utils.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ String _replaceSeparators(String path) {
3535
String _normalizePath(String path, String? configFilename) {
3636
final skipNormalization =
3737
(configFilename == null) || p.isAbsolute(path) || path.startsWith("**");
38-
return _replaceSeparators(
39-
skipNormalization ? path : p.join(p.dirname(configFilename), path));
38+
return _replaceSeparators(skipNormalization
39+
? path
40+
: p.absolute(p.join(p.dirname(configFilename), path)));
4041
}
4142

4243
Map<String, LibraryImport> libraryImportsExtractor(

pkgs/ffigen/lib/src/header_parser/parser.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Library parse(Config c) {
3434
packingOverride: c.structPackingOverride,
3535
libraryImports: c.libraryImports.values.toSet(),
3636
silenceEnumWarning: c.silenceEnumWarning,
37+
entryPoints: c.headers.entryPoints,
3738
);
3839

3940
return library;

pkgs/ffigen/lib/src/header_parser/sub_parsers/compounddecl_parser.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ Compound? parseCompoundDeclaration(
124124
usr: declUsr,
125125
dartDoc: getCursorDocComment(cursor),
126126
objCBuiltInFunctions: objCBuiltInFunctions,
127+
nativeType: cursor.type().spelling(),
127128
);
128129
} else {
129130
_logger.finest('unnamed $className declaration');
@@ -139,6 +140,7 @@ Compound? parseCompoundDeclaration(
139140
name: configDecl.renameUsingConfig(declName),
140141
dartDoc: getCursorDocComment(cursor),
141142
objCBuiltInFunctions: objCBuiltInFunctions,
143+
nativeType: cursor.type().spelling(),
142144
);
143145
}
144146
return null;

pkgs/ffigen/test/native_objc_test/block_config.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ typedefs:
1919
- NullableObjectBlock
2020
- BlockBlock
2121
- ListenerBlock
22+
- NullableListenerBlock
23+
- StructListenerBlock
24+
- NSStringListenerBlock
25+
- NoTrampolineListenerBlock
2226
headers:
2327
entry-points:
24-
- 'block_test.m'
28+
- 'block_test.h'
2529
preamble: |
2630
// ignore_for_file: camel_case_types, non_constant_identifier_names, unnecessary_non_null_assertion, unused_element, unused_field

pkgs/ffigen/test/native_objc_test/block_test.dart

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,69 @@ void main() {
189189
expect(isCalled, isTrue);
190190
});
191191

192+
test('Nullable listener block', () async {
193+
final hasRun = Completer<void>();
194+
final block = DartNullableListenerBlock.listener((DummyObject? x) {
195+
expect(x, isNull);
196+
hasRun.complete();
197+
});
198+
199+
BlockTester.callNullableListener_(block);
200+
await hasRun.future;
201+
});
202+
203+
test('Struct listener block', () async {
204+
final hasRun = Completer<void>();
205+
final block = DartStructListenerBlock.listener(
206+
(Vec2 vec2, Vec4 vec4, NSObject dummy) {
207+
expect(vec2.x, 100);
208+
expect(vec2.y, 200);
209+
210+
expect(vec4.x, 1.2);
211+
expect(vec4.y, 3.4);
212+
expect(vec4.z, 5.6);
213+
expect(vec4.w, 7.8);
214+
215+
expect(dummy, isNotNull);
216+
217+
hasRun.complete();
218+
});
219+
220+
BlockTester.callStructListener_(block);
221+
await hasRun.future;
222+
});
223+
224+
test('NSString listener block', () async {
225+
final hasRun = Completer<void>();
226+
final block = DartNSStringListenerBlock.listener((NSString s) {
227+
expect(s.toString(), "Foo 123");
228+
hasRun.complete();
229+
});
230+
231+
BlockTester.callNSStringListener_x_(block, 123);
232+
await hasRun.future;
233+
});
234+
235+
test('No trampoline listener block', () async {
236+
final hasRun = Completer<void>();
237+
final block = DartNoTrampolineListenerBlock.listener(
238+
(int x, Vec4 vec4, Pointer<Char> charPtr) {
239+
expect(x, 123);
240+
241+
expect(vec4.x, 1.2);
242+
expect(vec4.y, 3.4);
243+
expect(vec4.z, 5.6);
244+
expect(vec4.w, 7.8);
245+
246+
expect(charPtr.cast<Utf8>().toDartString(), "Hello World");
247+
248+
hasRun.complete();
249+
});
250+
251+
BlockTester.callNoTrampolineListener_(block);
252+
await hasRun.future;
253+
});
254+
192255
test('Block block', () {
193256
final blockBlock = DartBlockBlock.fromFunction((DartIntBlock intBlock) {
194257
return DartIntBlock.fromFunction((int x) {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) 2024, 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 <Foundation/NSObject.h>
6+
#import <Foundation/NSString.h>
7+
#import <Foundation/NSThread.h>
8+
9+
struct Vec2 {
10+
double x;
11+
double y;
12+
};
13+
14+
typedef struct {
15+
double x;
16+
double y;
17+
double z;
18+
double w;
19+
} Vec4;
20+
21+
@interface DummyObject : NSObject {
22+
int32_t* counter;
23+
}
24+
+ (instancetype)newWithCounter:(int32_t*) _counter;
25+
- (instancetype)initWithCounter:(int32_t*) _counter;
26+
- (void)setCounter:(int32_t*) _counter;
27+
- (void)dealloc;
28+
@end
29+
30+
31+
typedef int32_t (^IntBlock)(int32_t);
32+
typedef float (^FloatBlock)(float);
33+
typedef double (^DoubleBlock)(double);
34+
typedef Vec4 (^Vec4Block)(Vec4);
35+
typedef void (^VoidBlock)();
36+
typedef DummyObject* (^ObjectBlock)(DummyObject*);
37+
typedef DummyObject* _Nullable (^NullableObjectBlock)(DummyObject* _Nullable);
38+
typedef IntBlock (^BlockBlock)(IntBlock);
39+
typedef void (^ListenerBlock)(IntBlock);
40+
typedef void (^NullableListenerBlock)(DummyObject* _Nullable);
41+
typedef void (^StructListenerBlock)(struct Vec2, Vec4, NSObject*);
42+
typedef void (^NSStringListenerBlock)(NSString*);
43+
typedef void (^NoTrampolineListenerBlock)(int32_t, Vec4, const char*);
44+
45+
// Wrapper around a block, so that our Dart code can test creating and invoking
46+
// blocks in Objective C code.
47+
@interface BlockTester : NSObject {
48+
IntBlock myBlock;
49+
}
50+
+ (BlockTester*)makeFromBlock:(IntBlock)block;
51+
+ (BlockTester*)makeFromMultiplier:(int32_t)mult;
52+
- (int32_t)call:(int32_t)x;
53+
- (IntBlock)getBlock;
54+
- (void)pokeBlock;
55+
+ (void)callOnSameThread:(VoidBlock)block;
56+
+ (NSThread*)callOnNewThread:(VoidBlock)block;
57+
+ (NSThread*)callWithBlockOnNewThread:(ListenerBlock)block;
58+
+ (float)callFloatBlock:(FloatBlock)block;
59+
+ (double)callDoubleBlock:(DoubleBlock)block;
60+
+ (Vec4)callVec4Block:(Vec4Block)block;
61+
+ (DummyObject*)callObjectBlock:(ObjectBlock)block NS_RETURNS_RETAINED;
62+
+ (nullable DummyObject*)callNullableObjectBlock:(NullableObjectBlock)block;
63+
+ (void)callListener:(ListenerBlock)block;
64+
+ (void)callNullableListener:(NullableListenerBlock)block;
65+
+ (void)callStructListener:(StructListenerBlock)block;
66+
+ (void)callNSStringListener:(NSStringListenerBlock)block x:(int32_t)x;
67+
+ (void)callNoTrampolineListener:(NoTrampolineListenerBlock)block;
68+
+ (IntBlock)newBlock:(BlockBlock)block withMult:(int)mult;
69+
+ (BlockBlock)newBlockBlock:(int)mult;
70+
@end

0 commit comments

Comments
 (0)