This repository has been archived by the owner on Apr 29, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 12
/
jni.dart
307 lines (275 loc) · 10.5 KB
/
jni.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
// 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:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'package:path/path.dart';
import 'third_party/jni_bindings_generated.dart';
import 'jvalues.dart';
import 'types.dart';
import 'accessors.dart';
String _getLibraryFileName(String base) {
if (Platform.isLinux || Platform.isAndroid) {
return "lib$base.so";
} else if (Platform.isWindows) {
return "$base.dll";
} else if (Platform.isMacOS) {
return "lib$base.dylib";
} else {
throw UnsupportedError("cannot derive library name: unsupported platform");
}
}
/// Load Dart-JNI Helper library.
///
/// If path is provided, it's used to load the library.
/// Else just the platform-specific filename is passed to DynamicLibrary.open
DynamicLibrary _loadDartJniLibrary({String? dir, String baseName = "dartjni"}) {
final fileName = _getLibraryFileName(baseName);
final libPath = (dir != null) ? join(dir, fileName) : fileName;
try {
final dylib = DynamicLibrary.open(libPath);
return dylib;
} on Error {
throw HelperNotFoundException(libPath);
}
}
/// Utilities to spawn and manage JNI.
abstract class Jni {
static final DynamicLibrary _dylib = _loadDartJniLibrary(dir: _dylibDir);
static final JniBindings _bindings = JniBindings(_dylib);
static final _getJniEnvFn = _dylib.lookup<Void>('GetJniEnv');
static final _getJniContextFn = _dylib.lookup<Void>('GetJniContext');
/// Store dylibDir if any was used.
static String? _dylibDir;
/// Sets the directory where dynamic libraries are looked for.
/// On dart standalone, call this in new isolate before doing
/// any JNI operation.
///
/// (The reason is that dylibs need to be loaded in every isolate.
/// On flutter it's done by library. On dart standalone we don't
/// know the library path.)
static void setDylibDir({required String dylibDir}) {
_dylibDir = dylibDir;
}
/// Spawn an instance of JVM using JNI. This method should be called at the
/// beginning of the program with appropriate options, before other isolates
/// are spawned.
///
/// [dylibDir] is path of the directory where the wrapper library is found.
/// This parameter needs to be passed manually on __Dart standalone target__,
/// since we have no reliable way to bundle it with the package.
///
/// [jvmOptions], [ignoreUnrecognized], & [jniVersion] are passed to the JVM.
/// Strings in [classPath], if any, are used to construct an additional
/// JVM option of the form "-Djava.class.path={paths}".
static void spawn({
String? dylibDir,
List<String> jvmOptions = const [],
List<String> classPath = const [],
bool ignoreUnrecognized = false,
int jniVersion = JNI_VERSION_1_6,
}) =>
using((arena) {
_dylibDir = dylibDir;
final existVm = _bindings.GetJavaVM();
if (existVm != nullptr) {
throw JvmExistsException();
}
final jvmArgs = _createVMArgs(
options: jvmOptions,
classPath: classPath,
version: jniVersion,
ignoreUnrecognized: ignoreUnrecognized,
allocator: arena,
);
_bindings.SpawnJvm(jvmArgs);
});
static Pointer<JavaVMInitArgs> _createVMArgs({
List<String> options = const [],
List<String> classPath = const [],
bool ignoreUnrecognized = false,
int version = JNI_VERSION_1_6,
required Allocator allocator,
}) {
final args = allocator<JavaVMInitArgs>();
if (options.isNotEmpty || classPath.isNotEmpty) {
final count = options.length + (classPath.isNotEmpty ? 1 : 0);
final optsPtr = (count != 0) ? allocator<JavaVMOption>(count) : nullptr;
args.ref.options = optsPtr;
for (int i = 0; i < options.length; i++) {
optsPtr.elementAt(i).ref.optionString =
options[i].toNativeChars(allocator);
}
if (classPath.isNotEmpty) {
final classPathString = classPath.join(Platform.isWindows ? ';' : ":");
optsPtr.elementAt(count - 1).ref.optionString =
"-Djava.class.path=$classPathString".toNativeChars(allocator);
}
args.ref.nOptions = count;
}
args.ref.ignoreUnrecognized = ignoreUnrecognized ? 1 : 0;
args.ref.version = version;
return args;
}
/// Returns pointer to current JNI JavaVM instance
Pointer<JavaVM> getJavaVM() {
return _bindings.GetJavaVM();
}
/// Returns the instance of [GlobalJniEnv], which is an abstraction over JNIEnv
/// without the same-thread restriction.
static Pointer<GlobalJniEnv> _fetchGlobalEnv() {
final env = _bindings.GetGlobalEnv();
if (env == nullptr) {
throw NoJvmInstanceException();
}
return env;
}
static Pointer<GlobalJniEnv>? _env;
/// Points to a process-wide shared instance of [GlobalJniEnv].
///
/// It provides an indirection over [JniEnv] so that it can be used from
/// any thread, and always returns global object references.
static Pointer<GlobalJniEnv> get env {
return _env ??= _fetchGlobalEnv();
}
static Pointer<JniAccessors> get accessors => _bindings.GetAccessors();
/// Returns current application context on Android.
static JObjectPtr getCachedApplicationContext() {
return _bindings.GetApplicationContext();
}
/// Returns current activity
static JObjectPtr getCurrentActivity() => _bindings.GetCurrentActivity();
/// Get the initial classLoader of the application.
///
/// This is especially useful on Android, where
/// JNI threads cannot access application classes using
/// the usual `JniEnv.FindClass` method.
static JObjectPtr getApplicationClassLoader() => _bindings.GetClassLoader();
/// Returns class reference found through system-specific mechanism
static JClassPtr findClass(String qualifiedName) => using((arena) {
final cls = accessors.getClass(qualifiedName.toNativeChars(arena));
return cls.checkedClassRef;
});
/// Returns class for [qualifiedName] found by platform-specific mechanism,
/// wrapped in a [JniClass].
static JniClass findJniClass(String qualifiedName) =>
JniClass.fromRef(findClass(qualifiedName));
/// Constructs an instance of class with given arguments.
///
/// Use it when one instance is needed, but the constructor or class aren't
/// required themselves.
static JObject newInstance(
String qualifiedName, String ctorSignature, List<dynamic> args) {
final cls = findJniClass(qualifiedName);
final ctor = cls.getCtorID(ctorSignature);
final obj = cls.newInstance(ctor, args);
cls.delete();
return obj;
}
/// Converts passed arguments to JValue array.
///
/// int, bool, double and JObject types are converted out of the box.
/// Wrap values in types such as [JValueLong] to convert to other primitive
/// types such as `long`, `short` and `char`.
static Pointer<JValue> jvalues(List<dynamic> args,
{Allocator allocator = calloc}) {
return toJValues(args, allocator: allocator);
}
/// Returns the value of static field identified by [fieldName] & [signature].
///
/// See [JObject.getField] for more explanations about [callType] and [T].
static T retrieveStaticField<T>(
String className, String fieldName, String signature,
[int? callType]) {
final cls = findJniClass(className);
final result = cls.getStaticFieldByName<T>(fieldName, signature, callType);
cls.delete();
return result;
}
/// Calls static method identified by [methodName] and [signature]
/// on [className] with [args] as and [callType].
///
/// For more explanation on [args] and [callType], see [JObject.getField]
/// and [JObject.callMethod] respectively.
static T invokeStaticMethod<T>(
String className, String methodName, String signature, List<dynamic> args,
[int? callType]) {
final cls = findJniClass(className);
final result =
cls.callStaticMethodByName<T>(methodName, signature, args, callType);
cls.delete();
return result;
}
/// Delete all references in [objects].
static void deleteAll(List<JReference> objects) {
for (var object in objects) {
object.delete();
}
}
}
typedef _SetJniGettersNativeType = Void Function(Pointer<Void>, Pointer<Void>);
typedef _SetJniGettersDartType = void Function(Pointer<Void>, Pointer<Void>);
/// Extensions for use by `jnigen` generated code.
extension ProtectedJniExtensions on Jni {
static Pointer<T> Function<T extends NativeType>(String) initGeneratedLibrary(
String name) {
var path = _getLibraryFileName(name);
if (Jni._dylibDir != null) {
path = join(Jni._dylibDir!, path);
}
final dl = DynamicLibrary.open(path);
final setJniGetters =
dl.lookupFunction<_SetJniGettersNativeType, _SetJniGettersDartType>(
'setJniGetters');
setJniGetters(Jni._getJniContextFn, Jni._getJniEnvFn);
final lookup = dl.lookup;
return lookup;
}
}
extension AdditionalEnvMethods on Pointer<GlobalJniEnv> {
/// Convenience method for converting a [JStringPtr]
/// to dart string.
/// if [deleteOriginal] is specified, jstring passed will be deleted using
/// DeleteLocalRef.
String asDartString(JStringPtr jstringPtr, {bool deleteOriginal = false}) {
if (jstringPtr == nullptr) {
throw NullJStringException();
}
final chars = GetStringUTFChars(jstringPtr, nullptr);
if (chars == nullptr) {
throw InvalidJStringException(jstringPtr);
}
final result = chars.cast<Utf8>().toDartString();
ReleaseStringUTFChars(jstringPtr, chars);
if (deleteOriginal) {
DeleteGlobalRef(jstringPtr);
}
return result;
}
/// Return a new [JStringPtr] from contents of [s].
JStringPtr asJString(String s) => using((arena) {
final utf = s.toNativeUtf8().cast<Char>();
final result = NewStringUTF(utf);
malloc.free(utf);
return result;
});
/// Deletes all references in [refs].
void deleteAllRefs(List<JObjectPtr> refs) {
for (final ref in refs) {
DeleteGlobalRef(ref);
}
}
}
extension StringMethodsForJni on String {
/// Returns a Utf-8 encoded Pointer<Char> with contents same as this string.
Pointer<Char> toNativeChars([Allocator allocator = malloc]) {
return toNativeUtf8(allocator: allocator).cast<Char>();
}
}
extension CharPtrMethodsForJni on Pointer<Char> {
/// Same as calling `cast<Utf8>` followed by `toDartString`.
String toDartString() {
return cast<Utf8>().toDartString();
}
}