Skip to content

Commit ff12aa2

Browse files
committed
Refactor bindgen templates and UrlOpener bindings
Add richer bindgen features and generate safer Dart wrappers: dump context JSON, add reference_format and type mappings in config, and implement pre/post-call handling, native->Dart conversion helpers, enum native-value mapping, struct/string ownership handling, and singleton class support in templates. Update generated ffigen bindings to remove explicit UrlOpener create/destroy and switch URL opener functions to handle-less signatures. Update UrlOpener API to a singleton (UrlOpener.instance), introduce UrlOpenResult.fromNative and UrlOpenErrorCode with native mappings, and ensure proper malloc/free of returned native structs and strings. Also update an example to use the singleton and advance the cnativeapi cxx_impl submodule commit.
1 parent 23b02b3 commit ff12aa2

File tree

11 files changed

+266
-188
lines changed

11 files changed

+266
-188
lines changed

bindgen/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ cd nativeapi-flutter
1313
PYTHONPATH=packages/cnativeapi/cxx_impl/tools python3 -m bindgen \
1414
--config bindgen/config.yaml \
1515
--dump-ir bindgen/out/ir.json \
16+
--dump-context bindgen/out/context.json \
1617
--out bindgen/out
1718
```
1819

1920
## Notes
2021

2122
- This does **not** regenerate `lib/src/bindings_generated.dart`.
2223
- Add symbol exceptions in `mapping.options.symbol_overrides`.
24+
- `bindgen/out/context.json` is the mapped template input after IR -> API/config transformation.

bindgen/config.yaml

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,32 @@ entry_headers:
88

99
mapping:
1010
output_note: Generated wrapper from C++ API. Calls existing ffigen bindings.
11+
reference_format: "{inner}"
1112

1213
naming:
1314
enum_value_name: camel_case
1415
method_name: camel_case
1516
field_name: camel_case
1617

1718
types:
19+
void: void
1820
bool: bool
21+
int32: int
22+
uint32: int
23+
int64: int
24+
uint64: int
25+
float32: double
26+
float64: double
27+
size_t: int
28+
cstring: String
29+
string: String
1930

2031
default_type: dynamic
2132
passthrough_unknown: false
2233

2334
options:
24-
bindings_import: package:cnativeapi/src/bindings_generated.dart
25-
api_type_rules:
26-
contains:
27-
- match: "&"
28-
type: dynamic
29-
force_instance_methods:
30-
- nativeapi::UrlOpener::IsSupported
35+
singleton_classes:
36+
- UrlOpener
3137
formatters:
3238
- name: dart-format-generated
3339
cmd:

bindgen/template/file/dart.j2

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@
1010
// {{ mapping.options.output_note | default("Generated by bindgen") }}
1111
// =============================================================================
1212

13+
{%- if uses_ffi %}
1314
import 'dart:ffi' as ffi;
15+
{%- endif %}
16+
{%- if uses_ui %}
1417
import 'dart:ui' as ui;
18+
{%- endif %}
19+
{%- if uses_pkgffi %}
1520
import 'package:ffi/ffi.dart' as pkgffi;
16-
import 'package:cnativeapi/cnativeapi.dart';
17-
import '{{ mapping.options.bindings_import }}';
21+
{%- endif %}
1822
import 'package:nativeapi/src/foundation/cnativeapi_bindings_mixin.dart';
23+
{%- if has_handle_classes %}
1924
import 'package:nativeapi/src/foundation/native_handle_wrapper.dart';
25+
{%- endif %}
2026

2127
{%- if has_types %}
2228
{% for item in types %}

bindgen/template/partials/class.j2

Lines changed: 88 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,98 @@
11
{%- macro render_method_call(method) -%}
2-
{%- if method.return_bridge == 'string' %}
3-
final cString = bindings.{{ method.call_symbol }}(
2+
bindings.{{ method.call_symbol }}(
43
{%- if method.needs_instance_handle %}
5-
_nativeHandle{% if method.call_args|length > 0 %},{% endif %}
4+
_nativeHandle{% if method.call_args|length > 0 %}, {% endif %}
65
{%- endif %}
76
{%- for arg in method.call_args %}
8-
{{ arg }}{% if not loop.last %}, {% endif %}
7+
{{ arg }}{% if not loop.last %}, {% endif %}
98
{%- endfor %}
10-
);
9+
)
10+
{%- endmacro -%}
11+
12+
{%- macro render_method_body(method) -%}
13+
{%- if method.return_bridge == 'string' %}
14+
final cString = {{ render_method_call(method) }};
1115
if (cString == ffi.nullptr) return '';
1216
final value = cString.cast<pkgffi.Utf8>().toDartString();
1317
bindings.{{ method.string_free_symbol }}(cString);
1418
return value;
19+
{%- elif method.return_bridge == 'enum' %}
20+
final nativeValue = {{ render_method_call(method) }};
21+
return {{ method.api_return_type }}.fromNativeValue(nativeValue);
22+
{%- elif method.return_bridge == 'struct' %}
23+
final nativeValue = {{ render_method_call(method) }};
24+
{%- if method.struct_free_symbol %}
25+
try {
26+
return {{ method.api_return_type }}.fromNative(nativeValue);
27+
} finally {
28+
final nativeValuePtr = pkgffi.malloc<{{ method.native_return_type }}>();
29+
nativeValuePtr.ref = nativeValue;
30+
bindings.{{ method.struct_free_symbol }}(nativeValuePtr);
31+
pkgffi.malloc.free(nativeValuePtr);
32+
}
33+
{%- else %}
34+
return {{ method.api_return_type }}.fromNative(nativeValue);
35+
{%- endif %}
1536
{%- elif method.return_bridge == 'offset' %}
16-
final nativeValue = bindings.{{ method.call_symbol }}(
17-
{%- if method.needs_instance_handle %}
18-
_nativeHandle{% if method.call_args|length > 0 %},{% endif %}
19-
{%- endif %}
20-
{%- for arg in method.call_args %}
21-
{{ arg }}{% if not loop.last %}, {% endif %}
22-
{%- endfor %}
23-
);
37+
final nativeValue = {{ render_method_call(method) }};
2438
return ui.Offset(nativeValue.x, nativeValue.y);
2539
{%- elif method.return_bridge == 'size' %}
26-
final nativeValue = bindings.{{ method.call_symbol }}(
27-
{%- if method.needs_instance_handle %}
28-
_nativeHandle{% if method.call_args|length > 0 %},{% endif %}
29-
{%- endif %}
30-
{%- for arg in method.call_args %}
31-
{{ arg }}{% if not loop.last %}, {% endif %}
32-
{%- endfor %}
33-
);
40+
final nativeValue = {{ render_method_call(method) }};
3441
return ui.Size(nativeValue.width, nativeValue.height);
3542
{%- elif method.return_bridge == 'rect' %}
36-
final nativeValue = bindings.{{ method.call_symbol }}(
37-
{%- if method.needs_instance_handle %}
38-
_nativeHandle{% if method.call_args|length > 0 %},{% endif %}
39-
{%- endif %}
40-
{%- for arg in method.call_args %}
41-
{{ arg }}{% if not loop.last %}, {% endif %}
42-
{%- endfor %}
43-
);
43+
final nativeValue = {{ render_method_call(method) }};
4444
return ui.Rect.fromLTWH(nativeValue.x, nativeValue.y, nativeValue.width, nativeValue.height);
4545
{%- elif method.return_bridge == 'void' %}
46-
bindings.{{ method.call_symbol }}(
47-
{%- if method.needs_instance_handle %}
48-
_nativeHandle{% if method.call_args|length > 0 %},{% endif %}
49-
{%- endif %}
50-
{%- for arg in method.call_args %}
51-
{{ arg }}{% if not loop.last %}, {% endif %}
52-
{%- endfor %}
53-
);
46+
{{ render_method_call(method) }};
5447
{%- else %}
55-
return bindings.{{ method.call_symbol }}(
56-
{%- if method.needs_instance_handle %}
57-
_nativeHandle{% if method.call_args|length > 0 %},{% endif %}
48+
return {{ render_method_call(method) }};
5849
{%- endif %}
59-
{%- for arg in method.call_args %}
60-
{{ arg }}{% if not loop.last %}, {% endif %}
50+
{%- endmacro -%}
51+
52+
{%- macro render_callable(method) -%}
53+
{%- for line in method.pre_call_lines %}
54+
{{ line }}
55+
{%- endfor %}
56+
{%- if method.post_call_lines %}
57+
try {
58+
{{ render_method_body(method) }}
59+
} finally {
60+
{%- for line in method.post_call_lines %}
61+
{{ line }}
6162
{%- endfor %}
62-
);
63+
}
64+
{%- else %}
65+
{{ render_method_body(method) }}
6366
{%- endif %}
6467
{%- endmacro -%}
6568

6669
{%- macro render_class(item, mapping) -%}
70+
{%- if item.is_singleton and not item.singleton_has_handle %}
71+
class {{ item.name }} with CNativeApiBindingsMixin {
72+
static final {{ item.name }} instance = {{ item.name }}._();
73+
74+
{{ item.name }}._();
75+
76+
{%- for method in item.methods %}
77+
{%- if not method.skip %}
78+
{%- if method.is_property %}
79+
{{ 'static ' if method.static else '' }}{{ method.api_return_type }} get {{ method.property_name }} {
80+
{{ render_callable(method) }}
81+
}
82+
{%- else %}
83+
{{ 'static ' if method.static else '' }}{{ method.api_return_type }} {{ method.api_name }}(
84+
{%- for param in method.params %}
85+
{{ param.api_type }} {{ param.name }}{% if not loop.last %}, {% endif %}
86+
{%- endfor %}
87+
) {
88+
{{ render_callable(method) }}
89+
}
90+
{%- endif %}
91+
92+
{%- endif %}
93+
{%- endfor %}
94+
}
95+
{%- else %}
6796
class {{ item.name }} with CNativeApiBindingsMixin implements NativeHandleWrapper<{{ item.handle_alias }}> {
6897
{%- if item.is_singleton %}
6998
static final {{ item.name }} instance = {{ item.name }}._();
@@ -74,9 +103,13 @@ class {{ item.name }} with CNativeApiBindingsMixin implements NativeHandleWrappe
74103
_nativeHandle = bindings.{{ item.singleton_symbol }}();
75104
}
76105
{%- else %}
77-
final {{ item.handle_alias }} _nativeHandle;
106+
late final {{ item.handle_alias }} _nativeHandle;
107+
final bool _ownsHandle;
78108

79-
{{ item.name }}(this._nativeHandle);
109+
{{ item.name }}([{{ item.handle_alias }}? nativeHandle])
110+
: _ownsHandle = nativeHandle == null {
111+
_nativeHandle = nativeHandle ?? bindings.{{ item.create_symbol }}();
112+
}
80113
{%- endif %}
81114

82115
@override
@@ -86,15 +119,15 @@ class {{ item.name }} with CNativeApiBindingsMixin implements NativeHandleWrappe
86119
{%- if not method.skip %}
87120
{%- if method.is_property %}
88121
{{ 'static ' if method.static else '' }}{{ method.api_return_type }} get {{ method.property_name }} {
89-
{{ render_method_call(method) }}
122+
{{ render_callable(method) }}
90123
}
91124
{%- else %}
92125
{{ 'static ' if method.static else '' }}{{ method.api_return_type }} {{ method.api_name }}(
93126
{%- for param in method.params %}
94127
{{ param.api_type }} {{ param.name }}{% if not loop.last %}, {% endif %}
95128
{%- endfor %}
96129
) {
97-
{{ render_method_call(method) }}
130+
{{ render_callable(method) }}
98131
}
99132
{%- endif %}
100133

@@ -103,7 +136,14 @@ class {{ item.name }} with CNativeApiBindingsMixin implements NativeHandleWrappe
103136

104137
@override
105138
void dispose() {
106-
// Generated wrappers are non-owning by default.
139+
{%- if item.is_singleton %}
140+
// Singleton handles are managed by native code.
141+
{%- else %}
142+
if (_ownsHandle) {
143+
bindings.{{ item.destroy_symbol }}(_nativeHandle);
144+
}
145+
{%- endif %}
107146
}
108147
}
148+
{%- endif %}
109149
{%- endmacro -%}

bindgen/template/partials/enum.j2

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,18 @@
99
{%- macro render_enum(item, mapping) -%}
1010
enum {{ item.name }} {
1111
{%- for value in item.values %}
12-
{{ enum_case_name(value.name) }}{% if not loop.last %},{% endif %}
12+
{{ enum_case_name(value.name) }}({{ value.value }}){% if not loop.last %},{% endif %}
13+
{%- endfor %};
14+
15+
const {{ item.name }}(this.nativeValue);
16+
17+
final int nativeValue;
18+
19+
static {{ item.name }} fromNativeValue(int value) => switch (value) {
20+
{%- for value in item.values %}
21+
{{ value.value }} => {{ item.name }}.{{ enum_case_name(value.name) }},
1322
{%- endfor %}
23+
_ => throw ArgumentError('Unknown value for {{ item.name }}: $value'),
24+
};
1425
}
1526
{%- endmacro -%}

0 commit comments

Comments
 (0)