Skip to content

Refactor JObject structure #998

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 32 additions & 6 deletions pkgs/jni/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
## 0.8.0-wip

- **Breaking Change**: `JObject.reference` now returns a `JReference` instead of
`Pointer<Void>`.
- **Breaking Change** ([#981](https://github.com/dart-lang/native/issues/981)):

- `JObject.reference` now returns a `JReference` instead of `Pointer<Void>`.
- `.fromRef` constructors are now called `.fromReference` and they take a
`JReference` instead of `Pointer<Void>`.
- `JObject` reflective field retrieving and method calling methods are
removed. Use `JClass` API instead.
- The following `Jni.accessors` methods have been removed:

- `getClassOf`
- `getMethodIDOf`
- `getStaticMethodIDOf`
- `getFieldIDOf`
- `getStaticFieldIDOf`
- `newObjectWithArgs`
- `callMethodWithArgs`
- `callStaticMethodWithArgs`

Instead use the `JClass` API.

- `Jni.findJClass` is replaced with `JClass.forName(String name)`
- `JClass` has been refactored. Instead of directly calling methods, getting
and setting fields, use `JClass.instanceMethodId`, `JClass.staticMethodId`,
`JClass.constructorId`, `JClass.instanceFieldId`, and `JClass.staticFieldId`
to first get access to the member.
- Renamed `JObject.getClass()` to `JObject.jClass`.
- Removed `Jni.deleteAllRefs`.

- **Breaking Change** ([#548](https://github.com/dart-lang/native/issues/548)):
Converted various `Exception`s into `Error`s:
- `UseAfterReleaseException` -> `UseAfterReleaseError`
Expand All @@ -13,19 +39,19 @@
- `JvmExistsException` -> `JniVmExistsError`
- `NoJvmInstanceException` -> `NoJvmInstanceError`
- **Breaking Change**: Removed `InvalidJStringException`.
- **Breaking Change**: The default return `callType` of type parameter `int` for
methods such as `JObject.callMethodByName<int>` is now Java's `long` instead
of `int` to be consistent with the way arguments work.
- **Breaking Change**: `JType` is now `sealed`.
- **Breaking Change**: Primitive types and their type classes are now `final`.
- **Breaking Change**: `JArray.filled` now uses the generated type class of the
`fill` object and not its Java runtime type.

## 0.7.2

- Fixed a bug where reading non-null terminated strings would overflow.

## 0.7.1
- Removed macOS Flutter plugin until package:jni supports it ([#780](https://github.com/dart-lang/native/issues/780)).

- Removed macOS Flutter plugin until package:jni supports it
([#780](https://github.com/dart-lang/native/issues/780)).

## 0.7.0

Expand Down
10 changes: 10 additions & 0 deletions pkgs/jni/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,13 @@ Apart from being the base library for code generated by `jnigen` this can also b
The test/ directory contains files with comments explaining the basics of this module, and the example/ directory contains a flutter example which also touches some Android-specifics.

Using this library assumes some familiarity with JNI - it's threading model and object references, among other things.

## Migrating to 0.8.0

Check the [changelog](https://github.com/dart-lang/native/blob/main/pkgs/jni/CHANGELOG.md#080-wip).

The API for standalone use of `package:jni` (without `package:jnigen`) has
changed to use `JClass.forName` for finding classes. Find the methods and fields
by accessing them from the retrieved `JClass`. For example find an instance
method with a name and a signature using `jClass.instanceMethodId`. Then you can
`call` the resulting `JInstanceMethodId` to call the instance method.
64 changes: 37 additions & 27 deletions pkgs/jni/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ String toJavaStringUsingEnv(int n) => using((arena) {
i.ref.i = n;
final res = env.CallStaticObjectMethodA(cls, mId, i);
final str = env.toDartString(res);
env.deleteAllRefs([res, cls]);
env.DeleteGlobalRef(res);
env.DeleteGlobalRef(cls);
return str;
});

Expand All @@ -37,21 +38,25 @@ int randomUsingEnv(int n) => using((arena) {
final random = env.NewObject(randomCls, ctor);
final nextInt = env.GetMethodID(randomCls, "nextInt".toNativeChars(arena),
"(I)I".toNativeChars(arena));
final res = env.CallIntMethodA(random, nextInt, Jni.jvalues([n]));
env.deleteAllRefs([randomCls, random]);
final res =
env.CallIntMethodA(random, nextInt, toJValues([n], allocator: arena));
env.DeleteGlobalRef(randomCls);
env.DeleteGlobalRef(random);
return res;
});
double randomDouble() {
final math = Jni.findJClass("java/lang/Math");
final random = math.callStaticMethodByName<double>("random", "()D", []);
final math = JClass.forName("java/lang/Math");
final random =
math.staticMethodId("random", "()D").call(math, const jdoubleType(), []);
math.release();
return random;
}

int uptime() {
return Jni.findJClass("android/os/SystemClock").use(
(systemClock) => systemClock.callStaticMethodByName<int>(
"uptimeMillis", "()J", [], JniCallType.longType),
return JClass.forName("android/os/SystemClock").use(
(systemClock) => systemClock
.staticMethodId("uptimeMillis", "()J")
.call(systemClock, const jlongType(), []),
);
}

Expand All @@ -62,8 +67,9 @@ String backAndForth() {
}

void quit() {
JObject.fromRef(Jni.getCurrentActivity())
.use((ac) => ac.callMethodByName<void>("finish", "()V", []));
JObject.fromReference(Jni.getCurrentActivity()).use((ac) => ac.jClass
.instanceMethodId("finish", "()V")
.call(ac, const jvoidType(), []));
}

void showToast(String text) {
Expand All @@ -74,18 +80,21 @@ void showToast(String text) {
// In this example, Toaster class wraps android.widget.Toast so that it
// can be called from any thread. See
// android/app/src/main/java/com/github/dart_lang/jni_example/Toaster.java
Jni.invokeStaticMethod<JObject>(
"com/github/dart_lang/jni_example/Toaster",
"makeText",
"(Landroid/app/Activity;Landroid/content/Context;"
"Ljava/lang/CharSequence;I)"
"Lcom/github/dart_lang/jni_example/Toaster;",
[
Jni.getCurrentActivity(),
Jni.getCachedApplicationContext(),
"😀",
0,
]).callMethodByName<void>("show", "()V", []);
final toasterClass =
JClass.forName('com/github/dart_lang/jni_example/Toaster');
final makeText = toasterClass.staticMethodId(
'makeText',
'(Landroid/app/Activity;Landroid/content/Context;'
'Ljava/lang/CharSequence;I)'
'Lcom/github/dart_lang/jni_example/Toaster;');
final toaster = makeText.call(toasterClass, const JObjectType(), [
Jni.getCurrentActivity(),
Jni.getCachedApplicationContext(),
'😀',
0,
]);
final show = toasterClass.instanceMethodId('show', '()V');
show(toaster, const jvoidType(), []);
}

void main() {
Expand All @@ -103,13 +112,14 @@ void main() {
Example("Back and forth string conversion", () => backAndForth()),
Example(
"Device name",
() => Jni.retrieveStaticField<String>(
"android/os/Build", "DEVICE", "Ljava/lang/String;")),
() => JClass.forName("android/os/Build")
.staticFieldId("DEVICE", const JStringType().signature)),
Example(
"Package name",
() => JObject.fromRef(Jni.getCurrentActivity()).use((activity) =>
activity.callMethodByName<String>(
"getPackageName", "()Ljava/lang/String;", [])),
() => JObject.fromReference(Jni.getCurrentActivity()).use((activity) =>
activity.jClass
.instanceMethodId("getPackageName", "()Ljava/lang/String;")
.call(activity, JString.type, [])),
),
Example("Show toast", () => showToast("Hello from JNI!"),
runInitially: false),
Expand Down
16 changes: 8 additions & 8 deletions pkgs/jni/example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -213,26 +213,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: cdd14e3836065a1f6302a236ec8b5f700695c803c57ae11a1c84df31e6bcf831
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev"
source: hosted
version: "10.0.3"
version: "10.0.4"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "9b2ef90589911d665277464e0482b209d39882dffaaf4ef69a3561a3354b2ebc"
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.0.3"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: fd3cd66cb2bcd7b50dcd3b413af49d78051f809c8b3f6e047962765c15a0d23d
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.0.1"
lints:
dependency: transitive
description:
Expand Down Expand Up @@ -269,10 +269,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.12.0"
mime:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions pkgs/jni/lib/internal_helpers_for_jnigen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ library internal_helpers_for_jnigen;

export 'src/accessors.dart';
export 'src/jni.dart' show ProtectedJniExtensions;
export 'src/types.dart' show referenceType;
export 'src/jreference.dart';
export 'src/method_invocation.dart';
18 changes: 9 additions & 9 deletions pkgs/jni/lib/jni.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,19 @@
/// This library provides classes and functions for JNI interop from Dart.
library jni;

export 'dart:ffi' show nullptr;

export 'package:ffi/ffi.dart' show using, Arena;

export 'src/errors.dart';
export 'src/jni.dart' hide ProtectedJniExtensions;
export 'src/jvalues.dart' hide JValueArgs, toJValues;
export 'src/types.dart';
export 'src/jarray.dart';
export 'src/jni.dart' hide ProtectedJniExtensions;
export 'src/jobject.dart';

export 'src/jreference.dart';
export 'src/jvalues.dart';
export 'src/lang/lang.dart';
export 'src/nio/nio.dart';
export 'src/util/util.dart';

export 'src/third_party/generated_bindings.dart'
hide JniBindings, JniEnv, JniEnv1, JniExceptionDetails;

export 'package:ffi/ffi.dart' show using, Arena;
export 'dart:ffi' show nullptr;
export 'src/types.dart' hide referenceType;
export 'src/util/util.dart';
61 changes: 10 additions & 51 deletions pkgs/jni/lib/src/accessors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,8 @@
// BSD-style license that can be found in the LICENSE file.

import 'dart:ffi';
import 'package:ffi/ffi.dart' show using;

import 'package:jni/src/jvalues.dart';

import 'errors.dart';
import 'third_party/generated_bindings.dart';
import 'jni.dart';
import 'package:jni/jni.dart';

void _check(JThrowablePtr exception) {
if (exception != nullptr) {
Expand Down Expand Up @@ -55,11 +50,19 @@ extension JniResultMethods on JniResult {
return value.d;
}

JObjectPtr get object {
JObjectPtr get objectPointer {
check();
return value.l;
}

JReference get reference {
return JGlobalReference(objectPointer);
}

T object<T extends JObject>(JObjType<T> type) {
return type.fromReference(reference);
}

bool get boolean {
check();
return value.z != 0;
Expand Down Expand Up @@ -115,48 +118,4 @@ extension JniAccessorWrappers on JniAccessors {
env.DeleteGlobalRef(details.stacktrace);
throw JniException(message, stacktrace);
}

// TODO(PR): How to name these methods? These only wrap toNativeChars()
// so that generated bindings are less verbose.
JClassPtr getClassOf(String internalName) =>
using((arena) => getClass(internalName.toNativeChars(arena)))
.checkedClassRef;

JMethodIDPtr getMethodIDOf(JClassPtr cls, String name, String signature) =>
using((arena) => getMethodID(
cls, name.toNativeChars(arena), signature.toNativeChars(arena)))
.methodID;

JMethodIDPtr getStaticMethodIDOf(
JClassPtr cls, String name, String signature) =>
using((arena) => getStaticMethodID(
cls, name.toNativeChars(arena), signature.toNativeChars(arena)))
.methodID;

JFieldIDPtr getFieldIDOf(JClassPtr cls, String name, String signature) =>
using((arena) => getFieldID(
cls, name.toNativeChars(arena), signature.toNativeChars(arena)))
.fieldID;

JFieldIDPtr getStaticFieldIDOf(
JClassPtr cls, String name, String signature) =>
using((arena) => getStaticFieldID(
cls, name.toNativeChars(arena), signature.toNativeChars(arena)))
.fieldID;

JniResult newObjectWithArgs(
JClassPtr cls, JMethodIDPtr ctor, List<dynamic> args) =>
using((arena) {
return newObject(cls, ctor, toJValues(args, allocator: arena));
});

JniResult callMethodWithArgs(
JObjectPtr obj, JMethodIDPtr id, int callType, List<dynamic> args) =>
using((arena) =>
callMethod(obj, id, callType, toJValues(args, allocator: arena)));

JniResult callStaticMethodWithArgs(
JClassPtr cls, JMethodIDPtr id, int callType, List<dynamic> args) =>
using((arena) => callStaticMethod(
cls, id, callType, toJValues(args, allocator: arena)));
}
29 changes: 0 additions & 29 deletions pkgs/jni/lib/src/errors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,35 +85,6 @@ final class NoJvmInstanceError extends Error {
String toString() => 'No JNI instance is available';
}

// TODO(#395): Remove this when calltypes are removed.
extension on int {
static const _names = {
JniCallType.booleanType: 'bool',
JniCallType.byteType: 'byte',
JniCallType.shortType: 'short',
JniCallType.charType: 'char',
JniCallType.intType: 'int',
JniCallType.longType: 'long',
JniCallType.floatType: 'float',
JniCallType.doubleType: 'double',
JniCallType.objectType: 'object',
JniCallType.voidType: 'void',
};
String str() => _names[this]!;
}

// TODO(#395): Remove this when `JniCallType`s are removed.
final class InvalidCallTypeError extends Error {
final int type;
final Set<int> allowed;

InvalidCallTypeError(this.type, this.allowed);

@override
String toString() => 'Invalid type for call ${type.str()}. '
'Allowed types are ${allowed.map((t) => t.str()).toSet()}';
}

// TODO(#393): Remove this class in favor of `JThrowable`.
class JniException implements Exception {
/// Error message from Java exception.
Expand Down
Loading