Skip to content

Commit 6648a8a

Browse files
authored
[jnigen] Few improvements in generated C code including exception support. (#87)
* C bindings no longer use fully qualified names. Instead, it uses number-renamed simple names. The classes are sorted in the summarizer to ensure the order is deterministic. This enables shorter symbol names and less verbose bindings. * C bindings are formatted using same style as Dart SDK, using clang-format. If clang-format is not available, jnigen will issue a warning. Closes: #84 * C bindings return `JniResult`. The older `Jni.checkException` stopgap is removed. It reduces one line per function binding in dart. Lastly exceptions will work properly on android Closes: #56
1 parent f8e3a58 commit 6648a8a

File tree

56 files changed

+12415
-10635
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+12415
-10635
lines changed

.github/workflows/test-package.yml

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ jobs:
7979
distribution: 'zulu'
8080
java-version: '11'
8181
cache: maven
82+
## Committed bindings are formatted with clang-format.
83+
## So this is required to format generated bindings identically
84+
- name: install clang tools
85+
run: |
86+
sudo apt-get update -y
87+
sudo apt-get install -y clang-format
8288
- name: Install dependencies
8389
run: dart pub get
8490
- name: Run VM tests
@@ -126,7 +132,7 @@ jobs:
126132
with:
127133
distribution: 'zulu'
128134
java-version: '11'
129-
- name: install clang tools
135+
- name: install clang tools & CMake
130136
run: |
131137
sudo apt-get update -y
132138
sudo apt-get install -y clang-format build-essential cmake
@@ -143,6 +149,7 @@ jobs:
143149
dart run tool/generate_ide_files.dart
144150
ls src/compile_commands.json
145151
152+
146153
test_jni:
147154
runs-on: ubuntu-latest
148155
needs: [analyze_jni]
@@ -220,6 +227,11 @@ jobs:
220227
working-directory: pkgs/jnigen
221228
steps:
222229
- uses: actions/checkout@v3
230+
- name: Setup clang
231+
uses: egor-tensin/setup-clang@v1
232+
with:
233+
version: latest
234+
platform: x64
223235
- uses: subosito/flutter-action@v2
224236
with:
225237
channel: 'stable'
@@ -341,6 +353,10 @@ jobs:
341353
channel: 'stable'
342354
cache: true
343355
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
356+
- name: install clang tools
357+
run: |
358+
sudo apt-get update -y
359+
sudo apt-get install -y clang-format
344360
- run: flutter pub get
345361
- run: flutter analyze
346362
- run: flutter build apk
@@ -369,7 +385,7 @@ jobs:
369385
java-version: '11'
370386
- run: |
371387
sudo apt-get update -y
372-
sudo apt-get install -y ninja-build libgtk-3-dev
388+
sudo apt-get install -y ninja-build libgtk-3-dev clang-format
373389
- run: flutter config --enable-linux-desktop
374390
- run: dart pub get
375391
- name: Generate bindings
@@ -378,7 +394,7 @@ jobs:
378394
- name: Compare generated bindings
379395
run: |
380396
diff -qr _c src/
381-
diff -qr _dart lib/third_party
397+
diff -qr _dart lib/src/third_party
382398
- name: Generate full bindings
383399
run: dart run jnigen --config jnigen.yaml --override classes="org.apache.pdfbox.pdmodel;org.apache.pdfbox.text"
384400
- name: Analyze generated bindings

pkgs/jni/lib/jni.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,9 @@
6060
library jni;
6161

6262
export 'src/third_party/jni_bindings_generated.dart'
63-
hide JniBindings, JniEnv, JniEnv1;
63+
hide JniBindings, JniEnv, JniEnv1, JniExceptionDetails;
6464
export 'src/jni.dart' hide ProtectedJniExtensions;
6565
export 'src/jvalues.dart' hide JValueArgs, toJValues;
66-
export 'src/env_extensions.dart'
67-
show StringMethodsForJni, CharPtrMethodsForJni, AdditionalEnvMethods;
6866
export 'src/jni_exceptions.dart';
6967
export 'src/jni_object.dart' hide JniReference;
7068

pkgs/jni/lib/src/accessors.dart

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@ import 'package:jni/src/jvalues.dart';
99

1010
import 'third_party/jni_bindings_generated.dart';
1111
import 'jni.dart';
12-
import 'env_extensions.dart';
13-
14-
Pointer<GlobalJniEnv> env = Jni.env;
12+
import 'jni_exceptions.dart';
1513

1614
void _check(JThrowable exception) {
1715
if (exception != nullptr) {
18-
env.throwException(exception);
16+
Jni.accessors.throwException(exception);
1917
}
2018
}
2119

@@ -88,6 +86,21 @@ extension JniClassLookupResultMethods on JniClassLookupResult {
8886
}
8987

9088
extension JniAccessorWrappers on Pointer<JniAccessors> {
89+
/// Rethrows Java exception in Dart as [JniException].
90+
///
91+
/// The original exception object is deleted by this method. The message
92+
/// and Java stack trace are included in the exception.
93+
void throwException(JThrowable exception) {
94+
final details = getExceptionDetails(exception);
95+
final env = Jni.env;
96+
final message = env.asDartString(details.message);
97+
final stacktrace = env.asDartString(details.stacktrace);
98+
env.DeleteGlobalRef(exception);
99+
env.DeleteGlobalRef(details.message);
100+
env.DeleteGlobalRef(details.stacktrace);
101+
throw JniException(message, stacktrace);
102+
}
103+
91104
// TODO(PR): How to name these methods? These only wrap toNativeChars()
92105
// so that generated bindings are less verbose.
93106
JClass getClassOf(String internalName) =>

pkgs/jni/lib/src/env_extensions.dart

Lines changed: 0 additions & 99 deletions
This file was deleted.

pkgs/jni/lib/src/jni.dart

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import 'package:ffi/ffi.dart';
99
import 'package:path/path.dart';
1010

1111
import 'third_party/jni_bindings_generated.dart';
12-
import 'env_extensions.dart';
1312
import 'jvalues.dart';
1413
import 'jni_exceptions.dart';
1514
import 'jni_object.dart';
@@ -256,3 +255,54 @@ extension ProtectedJniExtensions on Jni {
256255
return lookup;
257256
}
258257
}
258+
259+
extension AdditionalEnvMethods on Pointer<GlobalJniEnv> {
260+
/// Convenience method for converting a [JString]
261+
/// to dart string.
262+
/// if [deleteOriginal] is specified, jstring passed will be deleted using
263+
/// DeleteLocalRef.
264+
String asDartString(JString jstring, {bool deleteOriginal = false}) {
265+
if (jstring == nullptr) {
266+
throw NullJniStringException();
267+
}
268+
final chars = GetStringUTFChars(jstring, nullptr);
269+
if (chars == nullptr) {
270+
throw InvalidJniStringException(jstring);
271+
}
272+
final result = chars.cast<Utf8>().toDartString();
273+
ReleaseStringUTFChars(jstring, chars);
274+
if (deleteOriginal) {
275+
DeleteGlobalRef(jstring);
276+
}
277+
return result;
278+
}
279+
280+
/// Return a new [JString] from contents of [s].
281+
JString asJString(String s) => using((arena) {
282+
final utf = s.toNativeUtf8().cast<Char>();
283+
final result = NewStringUTF(utf);
284+
malloc.free(utf);
285+
return result;
286+
});
287+
288+
/// Deletes all references in [refs].
289+
void deleteAllRefs(List<JObject> refs) {
290+
for (final ref in refs) {
291+
DeleteGlobalRef(ref);
292+
}
293+
}
294+
}
295+
296+
extension StringMethodsForJni on String {
297+
/// Returns a Utf-8 encoded Pointer<Char> with contents same as this string.
298+
Pointer<Char> toNativeChars([Allocator allocator = malloc]) {
299+
return toNativeUtf8(allocator: allocator).cast<Char>();
300+
}
301+
}
302+
303+
extension CharPtrMethodsForJni on Pointer<Char> {
304+
/// Same as calling `cast<Utf8>` followed by `toDartString`.
305+
String toDartString() {
306+
return cast<Utf8>().toDartString();
307+
}
308+
}

pkgs/jni/lib/src/jni_exceptions.dart

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ class NullJniStringException implements Exception {
2222
String toString() => 'toDartString called on null JniString reference';
2323
}
2424

25+
class InvalidJniStringException implements Exception {
26+
Pointer<Void> reference;
27+
InvalidJniStringException(this.reference);
28+
@override
29+
String toString() => 'Not a valid Java String: '
30+
'0x${reference.address.toRadixString(16)}';
31+
}
32+
2533
class DoubleFreeException implements Exception {
2634
dynamic object;
2735
Pointer<Void> ptr;
@@ -69,15 +77,16 @@ class InvalidCallTypeException implements Exception {
6977
}
7078

7179
class JniException implements Exception {
72-
/// Exception object pointer from JNI.
73-
final JObject err;
80+
/// Error message from Java exception.
81+
final String message;
7482

75-
/// brief description, usually initialized with error message from Java.
76-
final String msg;
77-
JniException(this.err, this.msg);
83+
/// Stack trace from Java.
84+
final String stackTrace;
85+
JniException(this.message, this.stackTrace);
7886

7987
@override
80-
String toString() => msg;
88+
String toString() => 'Exception in Java code called through JNI: '
89+
'$message\n\n$stackTrace\n';
8190
}
8291

8392
class HelperNotFoundException implements Exception {

pkgs/jni/lib/src/jni_object.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import 'package:ffi/ffi.dart';
99
import 'third_party/jni_bindings_generated.dart';
1010
import 'jni_exceptions.dart';
1111
import 'jni.dart';
12-
import 'env_extensions.dart';
1312
import 'accessors.dart';
1413
import 'jvalues.dart';
1514

@@ -86,7 +85,7 @@ Pointer<T> _getID<T extends NativeType>(
8685
final result = using(
8786
(arena) => f(ptr, name.toNativeChars(arena), sig.toNativeChars(arena)));
8887
if (result.exception != nullptr) {
89-
env.throwException(result.exception);
88+
_accessors.throwException(result.exception);
9089
}
9190
return result.id.cast<T>();
9291
}
@@ -213,7 +212,9 @@ class JniObject extends JniReference {
213212
JniClass getClass() {
214213
_ensureNotDeleted();
215214
final classRef = _env.GetObjectClass(reference);
216-
if (classRef == nullptr) _env.checkException();
215+
if (classRef == nullptr) {
216+
_accessors.throwException(_env.ExceptionOccurred());
217+
}
217218
return JniClass.fromRef(classRef);
218219
}
219220

@@ -425,7 +426,7 @@ class JniString extends JniObject {
425426
final chars = s.toNativeUtf8(allocator: arena).cast<Char>();
426427
final jstr = _env.NewStringUTF(chars);
427428
if (jstr == nullptr) {
428-
_env.checkException();
429+
throw 'Fatal: cannot convert string to Java string: $s';
429430
}
430431
return jstr;
431432
});

pkgs/jni/lib/src/jvalues.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import 'package:ffi/ffi.dart';
88
import 'third_party/jni_bindings_generated.dart';
99
import 'jni.dart';
1010
import 'jni_object.dart';
11-
import 'env_extensions.dart';
1211

1312
void _fillJValue(Pointer<JValue> pos, dynamic arg) {
1413
if (arg is JniObject) {

0 commit comments

Comments
 (0)