-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathjni_cpp.h
More file actions
534 lines (467 loc) · 19.4 KB
/
jni_cpp.h
File metadata and controls
534 lines (467 loc) · 19.4 KB
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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
/**
* Android JNI Utils, Copyright (c) 2024 Jorma Rebane
* Distributed under MIT Software License
*/
#pragma once
#if __ANDROID__
#include <jni.h>
#include <string>
#include <vector>
namespace rpp { namespace jni {
////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Explicitly initializes JVM and JNI Environment
* @param env Optionally provide an already attached JNIEnv
*/
jint initVM(JavaVM* vm, JNIEnv* env = nullptr) noexcept;
/**
* @brief if RPP_DEFINE_JNI_ONLOAD is defined, then JNI_OnLoad will be defined
*/
#if RPP_DEFINE_JNI_ONLOAD
// inline - to avoid multiple definition errors
extern "C" inline jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
return rpp::jni::initVM(vm);
}
#endif // RPP_DEFINE_JNI_ONLOAD
/**
* @brief Gets the JNI environment for the CURRENT THREAD ONLY
* @warning You must first call `initVM()` or define RPP_DEFINE_JNI_ONLOAD
*
* @warning JNIEnv is only valid per-thread, so do not store this globally,
* except if using thread_local storage.
*/
JNIEnv* getEnv() noexcept;
/**
* @brief Get the main Android `Activity` object from the MyMainApp.currentActivity field
* @param mainActivityClass The main activity class name, eg: "com/unity3d/player/UnityPlayer"
* if null, then returns the previously configured main activity
* @warning The main activity can only be configured once per app init
*/
jobject getMainActivity(const char* mainActivityClass = nullptr);
/**
* @brief Manually sets the main activity object for the app.
* The ref count is increased and ref is internally converted to a Global Ref,
* so the caller of this function just needs to perform usual ref management.
* @code
* jobject activity = myGetJniActivityLocalRef(...);
* rpp::jni::initMainActivity(activity);
* env->DeleteLocalRef(activity);
* @endcode
* @param mainActivityRef Reference to the main activity object
* @warning The main activity can only be configured once per application
*/
void initMainActivity(jobject mainActivityRef) noexcept;
/**
* @brief Checks for pending JNI exceptions and rethrows them as a C++ exception
* @param message Exception message to show
*/
void checkForJNIException(const char* message = nullptr);
////////////////////////////////////////////////////////////////////////////////////////////////
struct Class;
struct Method;
struct Field;
struct JniRef
{
jobject obj = nullptr;
bool isGlobal = false;
JniRef() = default;
explicit JniRef(jobject localOrGlobalRef) noexcept;
~JniRef() noexcept { reset(); }
void reset() noexcept;
void reset(const JniRef& r) noexcept;
/** @brief Converts this Local Ref to a Global Ref (if needed) */
void makeGlobal() noexcept;
JniRef(const JniRef& r) noexcept
{
reset(r);
}
JniRef& operator=(const JniRef& r) noexcept
{
if (this != &r) reset(r);
return *this;
}
JniRef(JniRef&& r) noexcept
{
std::swap(obj, r.obj);
std::swap(isGlobal, r.isGlobal);
}
JniRef& operator=(JniRef&& r) noexcept
{
std::swap(obj, r.obj);
std::swap(isGlobal, r.isGlobal);
return *this;
}
};
/**
* JNI LocalRef need to be deleted to avoid running out of allowed references.
* This is a smart pointer to automatically manage LocalRef-s and GlobalRef-s.
*
* @warning Only GlobalRef can be stored across JNI calls!
* @warning LocalRefs are only valid on a single thread and until code execution
* returns to JVM context
*/
template<class JObject>
struct Ref : public JniRef
{
using JniRef::JniRef;
/**
* @brief Initialize from a previous untracked object.
* It can be a LocalRef, GlobalRef or Invalid ref.
*/
explicit Ref(JObject localOrGlobalRef) noexcept
: JniRef{localOrGlobalRef}
{
}
/**
* @brief Initialize from a previously untracked object.
* The type is specified by @param global.
*/
Ref(JObject o, bool global) noexcept
{
obj = o;
isGlobal = global;
}
/**
* @return Gets the raw internal JNI jobject.
* @warning Implicit operators are forbidden since they can cause ownership bugs.
*/
JObject get() const noexcept { return static_cast<JObject>(obj); }
/**
* @brief static_cast the object to another type
* @warning This is unsafe and the returned object is not tracked or type-checked!
*/
template<class T> T as() const noexcept { return static_cast<T>(obj); }
explicit operator bool() const noexcept { return obj != nullptr; }
/**
* @brief Converts the Ref<JObject> to a Ref<T> and resets this Ref
*/
template<class T> Ref<T> releaseAs() noexcept
{
Ref<T> ref { static_cast<T>(obj), isGlobal };
obj = nullptr;
isGlobal = false;
return ref;
}
/**
* @returns A new Global Ref from this Ref<T>
*/
Ref<JObject> toGlobal() noexcept
{
Ref<JObject> g;
if (obj)
{
// even if it's already global, we need to increase the global refcount
g.obj = static_cast<JObject>(getEnv()->NewGlobalRef(obj));
g.isGlobal = true;
}
return g;
}
};
/**
* @brief Takes immeidate ownership of a JNI object and makes a new LocalRef of it.
* LocalRefs are limited within default JVM context and this can help manage them.
* @warning DO NOT STORE LocalRef into static/global storage!!!
* @param untrackedLocalOrGlobalRef jobject to turn into a smart LocalRef
* - it can be a LocalRef or GlobalRef but is expected to be untracked
*/
template<class JObject>
Ref<JObject> makeRef(JObject untrackedLocalOrGlobalRef) noexcept
{
return Ref<JObject>{untrackedLocalOrGlobalRef};
}
/**
* @brief Takes immeidate ownership of a JNI object and makes a new GlobalRef of it.
* Global Refs can be used across JNI calls and are not automaticaly GC-ed
* @note GlobalRefs can be safely stored in global static state
* @param untrackedLocalOrGlobalRef jobject to turn into a smart GlobalRef
* - it can be a LocalRef or a GlobalRef but is expected to be untracked
*/
template<class JObject>
Ref<JObject> makeGlobalRef(JObject untrackedLocalOrGlobalRef) noexcept
{
Ref<JObject> r{untrackedLocalOrGlobalRef};
r.makeGlobal(); // no-op if already global
return r;
}
/**
* @brief Describes all JNI types
*/
enum class JniType
{
Object,
Boolean,
Byte,
Char,
Short,
Int,
Long,
Float,
Double
};
/**
* JNI string class wrapper
*/
struct JString
{
Ref<jstring> s;
JString() noexcept = default;
JString(const Ref<jstring>& s) noexcept : s{s} {}
JString(Ref<jstring>&& s) noexcept : s{std::move(s)} {}
explicit JString(jstring s) noexcept : s{s} {}
jstring get() const noexcept { return s.get(); }
explicit operator bool() const noexcept { return (bool)s; }
/** @returns New GlobalRef JString that can be stored into global state */
JString toGlobal() noexcept { return { s.toGlobal() }; }
int getLength() const noexcept { return s ? getEnv()->GetStringLength(s.get()) : 0; }
/** @returns jstring converted to C++ std::string */
std::string str() const noexcept;
/**
* @brief Creates a new local jstring
*/
static JString from(const char* text) noexcept;
inline static JString from(const std::string& text) noexcept
{
return from(text.c_str());
}
};
// core util: JNI jstring to std::string
std::string toString(JNIEnv* env, jstring s) noexcept;
/**
* Provides access to raw array elements such as jbyte*, jint*
*/
struct ElementsRef
{
const jarray arr;
const JniType type;
void* e;
// acquires ref to jarray elements
ElementsRef(jarray a, JniType t) noexcept;
~ElementsRef() noexcept;
ElementsRef(const ElementsRef&) = delete;
ElementsRef(ElementsRef&&) = delete;
ElementsRef& operator=(const ElementsRef&) = delete;
ElementsRef& operator=(ElementsRef&&) = delete;
int getLength() const noexcept
{
return arr ? getEnv()->GetArrayLength(arr) : 0;
}
jobject objectAt(int i) const noexcept
{
return getEnv()->GetObjectArrayElement((jobjectArray)arr, i);
}
void setObjectAt(int i, jobject obj) noexcept
{
getEnv()->SetObjectArrayElement((jobjectArray)arr, i, obj);
}
jboolean& boolAt(int i) noexcept { return ((jboolean*)e)[i]; }
jbyte& byteAt(int i) noexcept { return ((jbyte*)e)[i]; }
jchar& charAt(int i) noexcept { return ((jchar*)e)[i]; }
jshort& shortAt(int i) noexcept { return ((jshort*)e)[i]; }
jint& intAt(int i) noexcept { return ((jint*)e)[i]; }
jlong& longAt(int i) noexcept { return ((jlong*)e)[i]; }
jfloat& floatAt(int i) noexcept { return ((jfloat*)e)[i]; }
jdouble& doubleAt(int i) noexcept { return ((jdouble*)e)[i]; }
};
/**
* Provides a wrapper for accessing JNI array elements
*/
struct JArray
{
Ref<jarray> array;
const JniType type;
JArray() noexcept : array{}, type{JniType::Object}
{
}
JArray(jarray arr, JniType t) noexcept : array{arr}, type{t}
{
}
JArray(const Ref<jarray>& a, JniType t) noexcept : array{a}, type{t}
{
}
JArray(Ref<jarray>&& a, JniType t) noexcept : array{std::move(a)}, type{t}
{
}
JArray(const JArray& arr) noexcept : array{arr.array}, type{arr.type}
{
}
JArray(JArray&& arr) noexcept : array{std::move(arr.array)}, type{arr.type}
{
}
~JArray() noexcept = default;
jarray get() const noexcept { return array.get(); }
explicit operator bool() const noexcept { return (bool)array; }
/** @returns New GlobalRef JArray that can be stored into global state */
JArray toGlobal() noexcept { return { array.toGlobal(), type }; }
int getLength() const noexcept
{
return array ? getEnv()->GetArrayLength(array.get()) : 0;
}
jobject getObjectAt(int index) const noexcept
{
return getEnv()->GetObjectArrayElement((jobjectArray)array.get(), index);
}
void setObjectAt(int i, jobject obj) noexcept
{
getEnv()->SetObjectArrayElement((jobjectArray)array.get(), i, obj);
}
JString getStringAt(int index) const noexcept;
/**
* @brief Access jbyteArray, jintArray, etc.
* @warning This can be inefficient for large arrays
*/
ElementsRef getElements() noexcept { return ElementsRef{array.get(), type}; }
/**
* @brief Creates a String array: java.lang.String[]
* @throws if creating new array fails (type lookup error)
*/
static JArray from(const std::vector<const char*>& strings);
};
/**
* @brief JNI Class wrapper for accessing methods and fields
* @note The clazz is a GlobalRef, so it can be stored in global storage
*/
struct Class
{
Ref<jclass> clazz; // Always a GlobalRef (required by JNI)
const char* name;
Class(const char* className);
Class(const char* className, std::nothrow_t) noexcept;
jclass get() const noexcept { return clazz.get(); }
explicit operator bool() const noexcept { return (bool)clazz; }
jni::Method method(const char* methodName, const char* signature);
jni::Method method(const char* methodName, const char* signature, std::nothrow_t) noexcept;
jni::Method staticMethod(const char* methodName, const char* signature);
jni::Method staticMethod(const char* methodName, const char* signature, std::nothrow_t) noexcept;
jni::Field field(const char* fieldName, const char* type);
jni::Field field(const char* fieldName, const char* type, std::nothrow_t) noexcept;
jni::Field staticField(const char* fieldName, const char* type);
jni::Field staticField(const char* fieldName, const char* type, std::nothrow_t) noexcept;
};
// unwrap arguments to their native JNI types
template<class T> inline const T& unwrap(const T& value) noexcept { return value; }
template<class T> inline T unwrap(const Ref<T>& ref) noexcept { return ref.get(); }
inline jstring unwrap(const JString& str) noexcept { return str.get(); }
inline jarray unwrap(const JArray& arr) noexcept { return arr.get(); }
// a parameter which accepts jobject or JniRef
struct JniRefParam
{
jobject o;
JniRefParam(jobject o) noexcept : o{o} {}
JniRefParam(JniRef&& r) noexcept : o{r.obj} {}
JniRefParam(const JniRef& r) noexcept : o{r.obj} {}
JniRefParam(std::nullptr_t) noexcept : o{nullptr} {}
};
/**
* @brief JNI Method wrapper for calling Java methods
*/
struct Method
{
Class& clazz;
const jmethodID method; // method ID-s don't need to be freed
const char* name;
const char* signature;
const bool isStatic;
Method(Class& clazz, jmethodID method, const char* name, const char* signature, bool isStatic) noexcept;
~Method() noexcept = default;
explicit operator bool() const noexcept { return method != nullptr; }
// call this Method with the specified return type;
// if instance is nullptr, it assumes static method
Ref<jobject> objectV(jobject instance, ...) noexcept;
JString stringV(jobject instance, ...) noexcept;
JArray arrayV(JniType type, jobject instance, ...) noexcept;
void voidV(jobject instance, ...) noexcept;
jboolean booleanV(jobject instance, ...) noexcept;
jbyte byteV(jobject instance, ...) noexcept;
jchar charV(jobject instance, ...) noexcept;
jshort shortV(jobject instance, ...) noexcept;
jint intV(jobject instance, ...) noexcept;
jlong longV(jobject instance, ...) noexcept;
jfloat floatV(jobject instance, ...) noexcept;
jdouble doubleV(jobject instance, ...) noexcept;
/** @brief Invokes method which returns LocalRef Object */
template<class...Args> Ref<jobject> objectF(JniRefParam instance, const Args&... args) noexcept
{
return objectV(instance.o, unwrap(args)...);
}
/** @brief Invokes method which return GlobalRef Object */
template<class...Args> Ref<jobject> globalObjectF(JniRefParam instance, const Args&... args) noexcept
{
return objectV(instance.o, unwrap(args)...).toGlobal();
}
template<class...Args> JString stringF(JniRefParam instance, const Args&... args) noexcept
{
return stringV(instance.o, unwrap(args)...);
}
template<class...Args> JArray arrayF(JniType type, JniRefParam instance, const Args&... args) noexcept
{
return arrayV(type, instance.o, unwrap(args)...);
}
template<class...Args> void voidF(JniRefParam instance, const Args&... args) noexcept
{
voidV(instance.o, unwrap(args)...);
}
template<class...Args> jboolean booleanF(JniRefParam instance, const Args&... args) noexcept
{
return booleanV(instance.o, unwrap(args)...);
}
template<class...Args> jbyte byteF(JniRefParam instance, const Args&... args) noexcept
{
return byteV(instance.o, unwrap(args)...);
}
template<class...Args> jchar charF(JniRefParam instance, const Args&... args) noexcept
{
return charV(instance.o, unwrap(args)...);
}
template<class...Args> jshort shortF(JniRefParam instance, const Args&... args) noexcept
{
return shortV(instance.o, unwrap(args)...);
}
template<class...Args> jint intF(JniRefParam instance, const Args&... args) noexcept
{
return intV(instance.o, unwrap(args)...);
}
template<class...Args> jlong longF(JniRefParam instance, const Args&... args) noexcept
{
return longV(instance.o, unwrap(args)...);
}
template<class...Args> jfloat floatF(JniRefParam instance, const Args&... args) noexcept
{
return floatV(instance.o, unwrap(args)...);
}
template<class...Args> jdouble doubleF(JniRefParam instance, const Args&... args) noexcept
{
return doubleV(instance.o, unwrap(args)...);
}
};
/**
* @brief JNI Field wrapper for accessing Java fields
*/
struct Field
{
Class& clazz;
const jfieldID field;// field ID-s don't need to be freed
const char* name;
const char* type;
const bool isStatic;
Field(Class& clazz, jfieldID field, const char* name, const char* type, bool isStatic) noexcept;
~Field() noexcept = default;
explicit operator bool() const noexcept { return field != nullptr; }
// access this field with the specified type;
// if instance is nullptr, it assumes static field
Ref<jobject> getObject(jobject instance = nullptr) noexcept;
Ref<jobject> getGlobalObject(jobject instance = nullptr) noexcept;
JString getString(jobject instance = nullptr) noexcept;
JArray getArray(JniType type, jobject instance = nullptr) noexcept;
jboolean getBoolean(jobject instance = nullptr) noexcept;
jbyte getByte(jobject instance = nullptr) noexcept;
jchar getChar(jobject instance = nullptr) noexcept;
jshort getShort(jobject instance = nullptr) noexcept;
jint getInt(jobject instance = nullptr) noexcept;
jlong getLong(jobject instance = nullptr) noexcept;
jfloat getFloat(jobject instance = nullptr) noexcept;
jdouble getDouble(jobject instance = nullptr) noexcept;
};
////////////////////////////////////////////////////////////////////////////////////////////////
}} // namespace rpp::jni
#endif // __ANDROID__