Skip to content

Commit 14f4ce6

Browse files
authored
[ffigen] feat: generate cpp class constructor bindings (#3395)
1 parent d8ea533 commit 14f4ce6

5 files changed

Lines changed: 114 additions & 18 deletions

File tree

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

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ enum CppMethodKind { constructor, destructor, method }
1616

1717
/// A method or constructor belonging to a C++ class.
1818
class CppMethod extends AstNode with HasLocalScope {
19-
final String name;
19+
final Symbol name;
2020
final String originalName;
2121
final Type returnType;
2222
final List<Parameter> parameters;
@@ -34,15 +34,16 @@ class CppMethod extends AstNode with HasLocalScope {
3434
this.kind = CppMethodKind.method,
3535
});
3636

37-
bool get isConstructor => kind == CppMethodKind.constructor;
38-
bool get isDestructor => kind == CppMethodKind.destructor;
37+
bool get isConstructor => kind == .constructor;
38+
bool get isDestructor => kind == .destructor;
3939

4040
@override
4141
void visit(Visitation visitation) => visitation.visitCppMethod(this);
4242

4343
@override
4444
void visitChildren(Visitor visitor) {
4545
super.visitChildren(visitor);
46+
visitor.visit(name);
4647
visitor.visit(returnType);
4748
visitor.visitAll(parameters);
4849
}
@@ -100,9 +101,12 @@ class CppClass extends BindingType with HasLocalScope {
100101

101102
final ptrVoid = '$ffiPrefix.Pointer<$ffiPrefix.Void>';
102103

103-
final classMethods = methods
104-
.where((m) => m.kind == CppMethodKind.method)
105-
.toList();
104+
// Helper to build a comma-separated Dart parameter list.
105+
String dartParamList(Iterable<Parameter> params) =>
106+
params.map((p) => '${p.type.getDartType(ctx)} ${p.name}').join(', ');
107+
108+
final classMethods = methods.where((m) => m.kind == .method).toList();
109+
final constructors = methods.where((m) => m.kind == .constructor).toList();
106110

107111
s.write(makeDartDoc(dartDoc));
108112
s.write('''
@@ -113,12 +117,39 @@ class $name {
113117
$name._(this._ptr);
114118
''');
115119

120+
for (final ctor in constructors) {
121+
final glueName = ctor.name.name;
122+
final privateName = '_$glueName';
123+
124+
final dartParams = dartParamList(ctor.parameters);
125+
126+
final localVars = LocalVariables(ctor.localScope);
127+
final callArgs = ctor.parameters
128+
.map(
129+
(p) => p.type.sameDartAndFfiDartType
130+
? p.name
131+
: p.type.convertDartTypeToFfiDartType(
132+
ctx,
133+
p.name,
134+
objCRetain: false,
135+
objCAutorelease: false,
136+
localVariables: localVars,
137+
),
138+
)
139+
.join(', ');
140+
141+
s.write('''
142+
factory $name($dartParams) {
143+
${localVars.generateDeclarations()}
144+
return $name._($privateName($callArgs));
145+
}
146+
''');
147+
}
148+
116149
for (final method in classMethods) {
117-
final glue = '_${name}_${method.name}';
150+
final glue = '_${method.name.name}';
118151
final dartReturn = method.returnType.getDartType(ctx);
119-
final dartParams = method.parameters
120-
.map((p) => '${p.type.getDartType(ctx)} ${p.name}')
121-
.join(', ');
152+
final dartParams = dartParamList(method.parameters);
122153

123154
final callArgs = [
124155
if (!method.isStatic) '_ptr',
@@ -127,14 +158,14 @@ class $name {
127158

128159
final staticKeyword = method.isStatic ? 'static ' : '';
129160
s.write(
130-
' $staticKeyword$dartReturn ${method.name}($dartParams)'
161+
' $staticKeyword$dartReturn ${method.originalName}($dartParams)'
131162
' => $glue($callArgs);\n',
132163
);
133164
}
134165
s.write('}\n');
135166

136167
for (final method in classMethods) {
137-
final symbol = '${name}_${method.name}';
168+
final symbol = method.name.name;
138169
final glue = '_$symbol';
139170

140171
final cReturn = method.returnType.getCType(ctx);
@@ -157,12 +188,36 @@ class $name {
157188
w,
158189
nativeType: cType,
159190
dartName: glue,
160-
nativeSymbolName: symbol,
191+
nativeSymbolName: Namer.cSafeName(symbol),
161192
),
162193
);
163194
s.write('\nexternal $ffiReturn $glue($ffiParams);\n\n');
164195
}
165196

197+
for (final ctor in constructors) {
198+
final symbol = ctor.name.name;
199+
final glue = '_$symbol';
200+
201+
final paramCTypes = ctor.parameters
202+
.map((p) => p.type.getCType(ctx))
203+
.join(', ');
204+
final cType = '$ptrVoid Function($paramCTypes)';
205+
206+
final ffiParams = ctor.parameters
207+
.map((p) => '${p.type.getFfiDartType(ctx)} ${p.name}')
208+
.join(', ');
209+
210+
s.write(
211+
makeNativeAnnotation(
212+
w,
213+
nativeType: cType,
214+
dartName: glue,
215+
nativeSymbolName: Namer.cSafeName(symbol),
216+
),
217+
);
218+
s.write('\nexternal $ptrVoid $glue($ffiParams);\n\n');
219+
}
220+
166221
return BindingString(
167222
type: BindingStringType.cppClass,
168223
string: s.toString(),

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

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import '../../code_generator.dart';
6+
import '../../code_generator/scope.dart';
67
import '../../config_provider/config_types.dart';
78
import '../../context.dart';
89
import '../clang_bindings/clang_bindings.dart' as clang_types;
@@ -29,7 +30,7 @@ CppClass? parseClassDeclaration(Context context, clang_types.CXCursor cursor) {
2930
// Use the libclang API to detect anonymous classes reliably.
3031
final String className;
3132
if (clang.clang_Cursor_isAnonymous(cursor) == 0) {
32-
className = usr.split('@').last;
33+
className = cursor.spelling();
3334
} else {
3435
logger.fine('Skipping anonymous C++ class.');
3536
return null;
@@ -58,6 +59,8 @@ CppClass? parseClassDeclaration(Context context, clang_types.CXCursor cursor) {
5859
final kind = clang.clang_getCursorKind(child);
5960
if (kind == clang_types.CXCursorKind.CXCursor_CXXMethod) {
6061
_parseMethod(context, child, decl, methods);
62+
} else if (kind == clang_types.CXCursorKind.CXCursor_Constructor) {
63+
_parseConstructor(context, child, decl, methods);
6164
}
6265
});
6366

@@ -105,9 +108,11 @@ void _parseMethod(
105108
}
106109

107110
logger.fine(' ++++ Method: $methodName (const=$isConst)');
111+
final cppClasses = context.config.cpp!.classes;
112+
final className = cppClasses.rename(classDecl);
108113
methods.add(
109114
CppMethod(
110-
name: methodName,
115+
name: Symbol('${className}_$methodName', SymbolKind.method),
111116
originalName: methodName,
112117
returnType: returnType,
113118
parameters: parameters,
@@ -136,3 +141,30 @@ List<Parameter>? _parseParameters(
136141
}
137142
return parsed.parameters;
138143
}
144+
145+
void _parseConstructor(
146+
Context context,
147+
clang_types.CXCursor cursor,
148+
Declaration classDecl,
149+
List<CppMethod> methods,
150+
) {
151+
final constructorName = cursor.spelling();
152+
final returnType = clang
153+
.clang_getCursorResultType(cursor)
154+
.toCodeGenType(context);
155+
final parameters = _parseParameters(context, cursor, classDecl);
156+
if (parameters == null) return;
157+
final cppClasses = context.config.cpp!.classes;
158+
final className = cppClasses.rename(classDecl);
159+
methods.add(
160+
CppMethod(
161+
name: Symbol('${className}_new', SymbolKind.method),
162+
originalName: constructorName,
163+
returnType: returnType,
164+
parameters: parameters,
165+
isConstant: false,
166+
isStatic: false,
167+
kind: CppMethodKind.constructor,
168+
),
169+
);
170+
}

pkgs/ffigen/lib/src/visitor/create_scopes.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ class CreateScopesVisitation extends Visitation {
7474
visitHasLocalScope(node, 'objc_msgSend');
7575

7676
@override
77-
void visitCppMethod(CppMethod node) => visitHasLocalScope(node, node.name);
77+
void visitCppMethod(CppMethod node) =>
78+
visitHasLocalScope(node, node.name.oldName);
7879

7980
static const objCReservedMethods = {
8081
'ref',

pkgs/ffigen/lib/src/visitor/find_symbols.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,10 @@ class FindSymbolsVisitation extends Visitation {
8484
visitInsideScope(node, node.localScope);
8585

8686
@override
87-
void visitCppMethod(CppMethod node) =>
88-
visitInsideScope(node, node.localScope);
87+
void visitCppMethod(CppMethod node) {
88+
currentScope.add(node.name);
89+
visitInsideScope(node, node.localScope);
90+
}
8991

9092
@override
9193
void visitObjCMethod(ObjCMethod node) => insideScope(

pkgs/ffigen/test/native_cpp_test/cpp_class_test_bindings.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ class Animal {
99
final ffi.Pointer<ffi.Void> _ptr;
1010

1111
Animal._(this._ptr);
12+
factory Animal(int age) {
13+
return Animal._(_Animal_new(age));
14+
}
1215
void speak() => _Animal_speak(_ptr);
1316
int getAge() => _Animal_getAge(_ptr);
1417
static int getCount() => _Animal_getCount();
@@ -22,3 +25,6 @@ external int _Animal_getAge(ffi.Pointer<ffi.Void> self);
2225

2326
@ffi.Native<ffi.Int Function()>(symbol: 'Animal_getCount')
2427
external int _Animal_getCount();
28+
29+
@ffi.Native<ffi.Pointer<ffi.Void> Function(ffi.Int)>(symbol: 'Animal_new')
30+
external ffi.Pointer<ffi.Void> _Animal_new(int age);

0 commit comments

Comments
 (0)