blob: a5588cd9625f2deebb90bca0f766c9f296b4344f [file] [log] [blame]
// 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 'package:ffi/ffi.dart';
import 'package:jni/jni.dart';
import 'package:jni/src/jvalues.dart';
import 'package:test/test.dart';
import 'test_util/test_util.dart';
const maxLongInJava = 9223372036854775807;
void main() {
// Running on Android through flutter, this plugin
// will bind to Android runtime's JVM.
// On other platforms eg Flutter desktop or Dart standalone
// You need to manually create a JVM at beginning.
//
// On flutter desktop, the C wrappers are bundled, and helperPath param
// is not required.
//
// On dart standalone, however, there's no way to bundle the wrappers.
// You have to manually pass the path to the `dartjni` dynamic library.
if (!Platform.isAndroid) {
checkDylibIsUpToDate();
Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
}
run(testRunner: test);
}
// We are factoring these tests into a run method which allows us to run same
// tests with different environments. Eg: standalone and integration tests.
void run({required TestRunnerCallback testRunner}) {
// Tests in this file demonstrate how to use `GlobalJniEnv`, a thin
// abstraction over JNIEnv in JNI C API. This can be used from multiple
// threads, and converts all returned object references to global references,
// so that you don't need to worry about whether your Dart code will be
// scheduled on another thread.
//
// GlobalJniEnv wraps all methods of JNIEnv (UpperCamelCase, reflecting the
// original name of the method) and provides few more extension methods
// (lowerCamelCase).
//
// For examples of a higher level API, see `jni_object_tests.dart`.
final env = Jni.env;
testRunner('initDLApi', () {
Jni.initDLApi();
});
testRunner('get JNI Version', () {
expect(Jni.env.GetVersion(), isNot(equals(0)));
});
testRunner(
'Manually lookup & call Integer.toHexString',
() => using((arena) {
// Method names on JniEnv* from C JNI API are capitalized
// like in original, while other extension methods
// follow Dart naming conventions.
final integerClass =
env.FindClass("java/lang/Integer".toNativeChars(arena));
// Refer JNI spec on how to construct method signatures
// Passing wrong signature leads to a segfault
final hexMethod = env.GetStaticMethodID(
integerClass,
"toHexString".toNativeChars(arena),
"(I)Ljava/lang/String;".toNativeChars(arena));
for (var i in [1, 80, 13, 76, 11344]) {
// if your argument is int, bool, or JObject (`Pointer<Void>`)
// it can be directly placed in the list. To convert into different primitive
// types, use JValue<Type> wrappers.
final jres = env.CallStaticObjectMethodA(integerClass, hexMethod,
Jni.jvalues([JValueInt(i)], allocator: arena));
// use asDartString extension method on Pointer<JniEnv>
// to convert a String jobject result to string
final res = env.toDartString(jres);
expect(res, equals(i.toRadixString(16)));
// Any object or class result from java is a local reference
// and needs to be deleted explicitly.
// Note that method and field IDs aren't local references.
// But they are valid only until a reference to corresponding
// java class exists.
env.DeleteGlobalRef(jres);
}
env.DeleteGlobalRef(integerClass);
}));
testRunner("asJString extension method", () {
const str = "QWERTY QWERTY";
// convenience method that wraps
// converting dart string to native string,
// instantiating java string, and freeing the native string
final jstr = env.toJStringPtr(str);
expect(str, equals(env.toDartString(jstr)));
env.DeleteGlobalRef(jstr);
});
testRunner(
'GlobalJniEnv should catch exceptions',
() => using((arena) {
final integerClass =
env.FindClass("java/lang/Integer".toNativeChars(arena));
final parseIntMethod = env.GetStaticMethodID(
integerClass,
"parseInt".toNativeChars(arena),
"(Ljava/lang/String;)I".toNativeChars(arena));
final args = JValueArgs(["hello"], arena);
expect(
() => env.CallStaticIntMethodA(
integerClass, parseIntMethod, args.values),
throwsA(isA<JniException>()));
}));
testRunner(
"Convert back & forth between Dart & Java strings (UTF-8)",
() => using((arena) {
const str = "ABCD EFGH";
final jstr = env.NewStringUTF(str.toNativeChars(arena));
final jchars = env.GetStringUTFChars(jstr, nullptr);
final jlen = env.GetStringUTFLength(jstr);
final dstr = jchars.toDartString(length: jlen);
env.ReleaseStringUTFChars(jstr, jchars);
expect(str, equals(dstr));
env.DeleteGlobalRef(jstr);
}));
testRunner(
"Convert back & forth between Dart & Java strings (UTF-16)",
() => using((arena) {
const str = "ABCD EFGH";
final jstr = env.NewString(str.toNativeUtf16().cast(), str.length);
final jchars = env.GetStringChars(jstr, nullptr);
final jlen = env.GetStringLength(jstr);
final dstr = jchars.cast<Utf16>().toDartString(length: jlen);
env.ReleaseStringChars(jstr, jchars);
expect(str, equals(dstr));
env.DeleteGlobalRef(jstr);
}));
testRunner(
"Print something from Java",
() => using((arena) {
final system =
env.FindClass("java/lang/System".toNativeChars(arena));
final field = env.GetStaticFieldID(
system,
"out".toNativeChars(arena),
"Ljava/io/PrintStream;".toNativeChars(arena));
final out = env.GetStaticObjectField(system, field);
final printStream = env.GetObjectClass(out);
final println = env.GetMethodID(
printStream,
"println".toNativeChars(arena),
"(Ljava/lang/String;)V".toNativeChars(arena));
const str = "\nHello World from JNI!";
final jstr = env.toJStringPtr(str);
env.CallVoidMethodA(out, println, Jni.jvalues([jstr]));
env.deleteAllRefs([system, printStream, jstr]);
}));
testRunner(
'Env create reference methods should retain their default behavior', () {
final systemOut = Jni.retrieveStaticField<JObjectPtr>(
"java/lang/System", "out", "Ljava/io/PrintStream;");
var refType = env.GetObjectRefType(systemOut);
expect(refType, equals(JObjectRefType.JNIGlobalRefType));
final localRef = env.NewLocalRef(systemOut);
refType = env.GetObjectRefType(localRef);
expect(refType, equals(JObjectRefType.JNILocalRefType));
final weakRef = env.NewWeakGlobalRef(systemOut);
refType = env.GetObjectRefType(weakRef);
expect(refType, equals(JObjectRefType.JNIWeakGlobalRefType));
final globalRef = env.NewGlobalRef(localRef);
refType = env.GetObjectRefType(globalRef);
expect(refType, equals(JObjectRefType.JNIGlobalRefType));
env.DeleteGlobalRef(globalRef);
env.DeleteWeakGlobalRef(weakRef);
env.DeleteLocalRef(localRef);
env.DeleteGlobalRef(systemOut);
});
testRunner('long methods return long int without loss of precision', () {
using((arena) {
final longClass = env.FindClass("java/lang/Long".toNativeChars(arena));
final maxField = env.GetStaticFieldID(
longClass,
'MAX_VALUE'.toNativeChars(arena),
'J'.toNativeChars(arena),
);
final maxValue = env.GetStaticLongField(longClass, maxField);
expect(maxValue, equals(maxLongInJava));
env.DeleteGlobalRef(longClass);
});
});
testRunner('class <-> object methods', () {
using((arena) {
final systemOut = Jni.retrieveStaticField<JObjectPtr>(
"java/lang/System", "out", "Ljava/io/PrintStream;");
final systemErr = Jni.retrieveStaticField<JObjectPtr>(
"java/lang/System", "err", "Ljava/io/PrintStream;");
final outClass = env.GetObjectClass(systemOut);
expect(env.IsInstanceOf(systemOut, outClass), isTrue);
expect(env.IsInstanceOf(systemErr, outClass), isTrue);
final errClass = env.GetObjectClass(systemErr);
expect(env.IsSameObject(outClass, errClass), isTrue);
env.deleteAllRefs([systemOut, systemErr, outClass, errClass]);
});
});
}