| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:io'; |
| import 'dart:ffi'; |
| import 'dart:isolate'; |
| |
| import 'package:test/test.dart'; |
| |
| import 'package:jni/jni.dart'; |
| |
| void main() { |
| // Don't forget to initialize JNI. |
| if (!Platform.isAndroid) { |
| try { |
| Jni.spawn(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]); |
| } on JvmExistsException catch (_) { |
| // TODO(#51): Support destroying and reinstantiating JVM. |
| } |
| } |
| |
| // The API based on JniEnv is intended to closely mimic C API of JNI, |
| // And thus can be too verbose for simple experimenting and one-off uses |
| // JObject API provides an easier way to perform some common operations. |
| // |
| // However, if binding generation using jnigen is possible, that should be |
| // the first choice. |
| test("Long.intValue() using JObject", () { |
| // JniClass wraps a local class reference, and |
| // provides convenience functions. |
| final longClass = Jni.findJniClass("java/lang/Long"); |
| |
| // looks for a constructor with given signature. |
| // equivalently you can lookup a method with name <init> |
| final longCtor = longClass.getCtorID("(J)V"); |
| |
| // note that the arguments are just passed as a list. |
| // allowed argument types are primitive types, JObject and its subclasses, |
| // and raw JNI references (JObject). Strings will be automatically converted |
| // to JNI strings. |
| final long = longClass.newInstance(longCtor, [176]); |
| |
| final intValue = long.callMethodByName<int>("intValue", "()I", []); |
| expect(intValue, equals(176)); |
| |
| // delete any JObject and JniClass instances using .delete() after use. |
| // Deletion is not strictly required since JNI objects / classes have |
| // a NativeFinalizer. But deleting them after use is a good practice. |
| long.delete(); |
| longClass.delete(); |
| }); |
| |
| test("call a static method using JniClass APIs", () { |
| final integerClass = Jni.findJniClass("java/lang/Integer"); |
| final result = integerClass.callStaticMethodByName<JString>( |
| "toHexString", "(I)Ljava/lang/String;", [JValueInt(31)]); |
| |
| // if the object is supposed to be a Java string |
| // you can call toDartString on it. |
| final resultString = result.toDartString(); |
| |
| // Dart string is a copy, original object can be deleted. |
| result.delete(); |
| expect(resultString, equals("1f")); |
| |
| // Also don't forget to delete the class |
| integerClass.delete(); |
| }); |
| |
| test("Call method with null argument, expect exception", () { |
| final integerClass = Jni.findJniClass("java/lang/Integer"); |
| expect( |
| () => integerClass.callStaticMethodByName<int>( |
| "parseInt", "(Ljava/lang/String;)I", [nullptr]), |
| throwsException); |
| integerClass.delete(); |
| }); |
| |
| test("Try to find a non-exisiting class, expect exception", () { |
| expect(() => Jni.findJniClass("java/lang/NotExists"), throwsException); |
| }); |
| |
| /// callMethodByName will be expensive if making same call many times |
| /// Use getMethodID to get a method ID and use it in subsequent calls |
| test("Example for using getMethodID", () { |
| final longClass = Jni.findJniClass("java/lang/Long"); |
| final bitCountMethod = longClass.getStaticMethodID("bitCount", "(J)I"); |
| |
| // Use newInstance if you want only one instance. |
| // It finds the class, gets constructor ID and constructs an instance. |
| final random = Jni.newInstance("java/util/Random", "()V", []); |
| |
| // You don't need a JniClass reference to get instance method IDs |
| final nextIntMethod = random.getMethodID("nextInt", "(I)I"); |
| |
| for (int i = 0; i < 100; i++) { |
| int r = random.callMethod<int>(nextIntMethod, [JValueInt(256 * 256)]); |
| int bits = 0; |
| final jbc = longClass.callStaticMethod<int>(bitCountMethod, [r]); |
| while (r != 0) { |
| bits += r % 2; |
| r = (r / 2).floor(); |
| } |
| expect(jbc, equals(bits)); |
| } |
| Jni.deleteAll([random, longClass]); |
| }); |
| |
| // One-off invocation of static method in single call. |
| test("invoke_", () { |
| final m = Jni.invokeStaticMethod<int>("java/lang/Short", "compare", "(SS)I", |
| [JValueShort(1234), JValueShort(1324)]); |
| expect(m, equals(1234 - 1324)); |
| }); |
| |
| test("Java char from string", () { |
| final m = Jni.invokeStaticMethod<bool>("java/lang/Character", "isLowerCase", |
| "(C)Z", [JValueChar.fromString('X')]); |
| expect(m, isFalse); |
| }); |
| |
| // One-off access of static field in single call. |
| test("Get static field directly", () { |
| final maxLong = Jni.retrieveStaticField<int>( |
| "java/lang/Short", "MAX_VALUE", "S", JniCallType.shortType); |
| expect(maxLong, equals(32767)); |
| }); |
| |
| // Use callStringMethod if all you care about is a string result |
| test("callStaticStringMethod", () { |
| final longClass = Jni.findJniClass("java/lang/Long"); |
| const n = 1223334444; |
| final strFromJava = longClass.callStaticMethodByName<String>( |
| "toOctalString", "(J)Ljava/lang/String;", [n]); |
| expect(strFromJava, equals(n.toRadixString(8))); |
| longClass.delete(); |
| }); |
| |
| // In JObject, JniClass, and retrieve_/invoke_ methods |
| // you can also pass Dart strings, apart from range of types |
| // allowed by Jni.jvalues |
| // They will be converted automatically. |
| test( |
| "Passing strings in arguments", |
| () { |
| final out = Jni.retrieveStaticField<JObject>( |
| "java/lang/System", "out", "Ljava/io/PrintStream;"); |
| // uncomment next line to see output |
| // (\n because test runner prints first char at end of the line) |
| //out.callMethodByName<Null>( |
| // "println", "(Ljava/lang/Object;)V", ["\nWorks (Apparently)"]); |
| out.delete(); |
| }, |
| ); |
| |
| test("Passing strings in arguments 2", () { |
| final twelve = Jni.invokeStaticMethod<int>("java/lang/Byte", "parseByte", |
| "(Ljava/lang/String;)B", ["12"], JniCallType.byteType); |
| expect(twelve, equals(12)); |
| }); |
| |
| // You can use() method on JObject for using once and deleting. |
| test("use() method", () { |
| final randomInt = Jni.newInstance("java/util/Random", "()V", []).use( |
| (random) => |
| random.callMethodByName<int>("nextInt", "(I)I", [JValueInt(15)])); |
| expect(randomInt, lessThan(15)); |
| }); |
| |
| // The JObject and JniClass have NativeFinalizer. However, it's possible to |
| // explicitly use `Arena`. |
| test('Using arena', () { |
| final objects = <JObject>[]; |
| using((arena) { |
| final r = Jni.findJniClass('java/util/Random')..deletedIn(arena); |
| final ctor = r.getCtorID("()V"); |
| for (int i = 0; i < 10; i++) { |
| objects.add(r.newInstance(ctor, [])..deletedIn(arena)); |
| } |
| }); |
| for (var object in objects) { |
| expect(object.isDeleted, isTrue); |
| } |
| }); |
| |
| test("enums", () { |
| // Don't forget to escape $ in nested type names |
| final ordinal = Jni.retrieveStaticField<JObject>( |
| "java/net/Proxy\$Type", "HTTP", "Ljava/net/Proxy\$Type;") |
| .use((f) => f.callMethodByName<int>("ordinal", "()I", [])); |
| expect(ordinal, equals(1)); |
| }); |
| test("casting", () { |
| using((arena) { |
| final str = "hello".toJString()..deletedIn(arena); |
| final obj = str.castTo(JObject.type)..deletedIn(arena); |
| final backToStr = obj.castTo(JString.type); |
| expect(backToStr.toDartString(), str.toDartString()); |
| final _ = backToStr.castTo(JObject.type, deleteOriginal: true) |
| ..deletedIn(arena); |
| expect(backToStr.toDartString, throwsA(isA<UseAfterFreeException>())); |
| expect(backToStr.delete, throwsA(isA<DoubleFreeException>())); |
| }); |
| }); |
| |
| test("Isolate", () { |
| Isolate.spawn(doSomeWorkInIsolate, null); |
| }); |
| } |
| |
| void doSomeWorkInIsolate(Void? _) { |
| // On standalone target, make sure to call [setDylibDir] before accessing |
| // any JNI function. |
| // |
| // otherwise getInstance will throw a "library not found" exception. |
| Jni.setDylibDir(dylibDir: "build/jni_libs"); |
| final random = Jni.newInstance("java/util/Random", "()V", []); |
| // final r = random.callMethodByName<int>("nextInt", "(I)I", [256]); |
| // expect(r, lessThan(256)); |
| // Expect throws an [OutsideTestException] |
| // but you can uncomment below print and see it works |
| // print("\n$r"); |
| random.delete(); |
| } |