-
Notifications
You must be signed in to change notification settings - Fork 16
/
jmi.h
1264 lines (1159 loc) · 52.4 KB
/
jmi.h
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
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* JMI: JNI Modern Interface
* Copyright (C) 2016-2024 Wang Bin - wbsecg1@gmail.com
* https://github.com/wang-bin/JMI
* MIT License
*/
// requres: c++14. compile time signaure requires c++17
// TODO: reset error before each call, reset exception after each call (Aspect pattern?)
// TODO: query class path if return/parameter type is jobject
// TODO: object convert
// TODO: gnu literal extension template<typename Ch, Ch ...c> constexpr auto operator""_jmis() { return StrLiteralToType<c...>{} }
// java template, e.g. Range<T>
// https://developer.android.com/training/articles/perf-jni#threads
#pragma once
#include <algorithm>
#include <array>
#include <functional> // std::ref
#include <string>
#include <type_traits>
#include <jni.h>
#define JMI_USE_CXX17 1
#if (__cplusplus + 0) >= 201707L || (_MSVC_LANG+0) > 201703L
#define JMI_CXX20 1
#endif
#if (__cplusplus + 0) >= 201703L || (_MSVC_LANG+0) > 201402L
#define JMI_CXX17 1
#endif
#if (JMI_CXX17 + 0)
# include <string_view>
#elif !defined(_LIBCPP_STRING_VIEW)
using string_view = std::string;
#endif
#if (JMI_CXX17+0) && (JMI_USE_CXX17 + 0)
# define CONSTEXPR17 constexpr
#else
# define CONSTEXPR17 const
#endif
namespace jmi {
using namespace std;
/*************************** JMI Public APIs Begin ***************************/
#define JMI_MAJOR 0
#define JMI_MINOR 1
#define JMI_MICRO 0
#define JMI_VERSION_STR JMI_STRINGIFY(JMI_MAJOR) "." JMI_STRINGIFY(JMI_MINOR) "." JMI_STRINGIFY(JMI_MICRO)
// set JavaVM to vm if not null. return previous JavaVM
JavaVM* javaVM(JavaVM *vm = nullptr, jint version = JNI_VERSION_1_4);
JNIEnv *getEnv();
// to_string: local ref is deleted internally
string to_string(jstring s, JNIEnv* env = nullptr);
// You have to call DeleteLocalRef() manually for the returned jstring
jstring from_string(const string& s, JNIEnv* env = nullptr);
namespace android {
// current android/app/Application object containing a local ref
jobject application(JNIEnv* env = nullptr); // TODO: return LocalRef
} // namespace android
#define JMISTR(cstr) jmi::to_array(cstr) // cstr is a c string literal. the result is a const char* for c++14, array<char,N> for c++17
struct ClassTag {}; // used by JObject<Tag>. subclasses must define static constexpr auto name() {return JMISTR("someName");}, with or without "L ;" around someName
struct MethodTag {}; // used by call() and callStatic(). subclasses must define static const char* name() or static constexpr const char*();
struct FieldTag {}; // subclasses must define static const char* name() or static constexpr const char*();
namespace detail {
// using var template requires c++14
template<class T>
struct is_jobject : integral_constant<bool, is_base_of<typename remove_pointer<jobject>::type, typename remove_pointer<T>::type>::value> {};
template<class T>
using if_jobject = typename enable_if<is_jobject<T>::value, bool>::type;
template<class T>
using if_not_jobject = typename enable_if<!is_jobject<T>::value, bool>::type;
template<class Tag>
using if_ClassTag = typename enable_if<is_base_of<ClassTag, Tag>::value, bool>::type;
template<class Tag>
using if_MethodTag = typename enable_if<is_base_of<MethodTag, Tag>::value, bool>::type;
template<class Tag>
using if_FieldTag = typename enable_if<is_base_of<FieldTag, Tag>::value, bool>::type;
template<class T> struct is_JObject : is_base_of<ClassTag, T>::type {}; // TODO: is_detected<signature>
template<class T>
using if_JObject = typename enable_if<is_JObject<T>::value, bool>::type;
template<class T>
using if_not_JObject = typename enable_if<!is_JObject<T>::value, bool>::type;
}
//template<typename T> // jni primitive types(not all c++ arithmetic types?), jobject, jstring, ..., JObject, c++ array types
//using if_jni_type = typename enable_if<is_arithmetic<T>::value || is_array_like<T>::value || is_same<T,jobject> || ... || is_JObject<T>::value
template<typename T, bool = is_enum<T>::value> struct signature;
#if (JMI_CXX17+0)
template<typename T>
inline constexpr auto signature_v = signature<T, is_enum_v<T>>::value;
#endif
// auto signature_of<T>() returns signature of object of type T, return type is array<char,N> for c++17+, string for c++14, and char. signature_of() returns signature of void type
// signature of function ptr
template<typename R, typename... Args> CONSTEXPR17 auto signature_of(R(*)(Args...));
class LocalRef {
public:
template<typename J, detail::if_jobject<J> = true>
LocalRef(J j, JNIEnv* env = nullptr) : j_(j), env_(env) {}
LocalRef(const LocalRef&) = delete;
LocalRef& operator=(const LocalRef&) = delete;
LocalRef(LocalRef&& that) : j_(that.j_), env_(that.env_) { that.j_ = nullptr;} // total ref obj is 1
LocalRef& operator=(LocalRef&& that) { // total ref obj is 2, or delete 1 here
swap(j_, that.j_);
swap(env_, that.env_);
return *this;
}
~LocalRef() {
if (!j_)
return;
if (!env_)
env_ = getEnv();
env_->DeleteLocalRef(j_);
}
explicit operator bool() const { return !!j_; }
template<typename J, detail::if_jobject<J> = true>
operator J() const {return static_cast<J>(j_);}
template<typename J, detail::if_jobject<J> = true>
J get() const {return static_cast<J>(j_);}
private:
jobject j_ = nullptr;
JNIEnv* env_ = nullptr;
};
// object must be a class template, thus we can cache class id using static member and call FindClass() only once, and also make it possible to cache method id because method id
template<class CTag>
class JObject : public ClassTag
{
public:
using Tag = CTag;
static CONSTEXPR17 auto className(); // array<char, N> for c++17+, string for otherwise
static CONSTEXPR17 auto signature(); // array<char, N> for c++17+, string for otherwise
// construct from an existing jobject. Usually obj is from native jni api containing a local ref, and it's local ref will be deleted if del_localref is true
JObject(jobject obj = nullptr, bool del_localref = true) {
JNIEnv *env = getEnv();
reset(obj, env);
if (obj && del_localref)
env->DeleteLocalRef(obj);
}
JObject(LocalRef&& ref) : JObject((jobject)ref, false) {}
JObject(const LocalRef& ref) = delete; // required
JObject(const JObject &other) { reset(other.id()).setError(other.error()); }
JObject &operator=(const JObject &other) {
if (this == &other)
return *this;
return reset(other.id()).setError(other.error());
}
JObject(JObject &&other) { // default implementation does not reset other.oid_
swap(oid_, other.oid_);
swap(error_, other.error_);
}
JObject &operator=(JObject &&other) { // default implementation does not reset other.oid_
swap(oid_, other.oid_);
swap(error_, other.error_);
return *this;
}
~JObject() { reset(); }
operator jobject() const { return oid_;}
operator jclass() const { return classId();}
jobject id() const { return oid_; }
explicit operator bool() const { return !!oid_;}
const string& error() const {return error_;}
JObject& reset(jobject obj = nullptr, JNIEnv *env = nullptr);
template<typename... Args>
bool create(Args&&... args);
/* with MethodTag we can avoid calling GetMethodID() in every call()
struct MyMethod : jmi::MethodTag { static const char* name() { return "myMethod";} };
return call<T, MyMethod>(args...);
*/
template<typename T, class MTag, typename... Args, detail::if_MethodTag<MTag> = true>
inline T call(Args&&... args) const;
template<class MTag, typename... Args, detail::if_MethodTag<MTag> = true>
inline void call(Args&&... args) const;
/* with MethodTag we can avoid calling GetStaticMethodID() in every callStatic()
struct MyStaticMethod : jmi::MethodTag { static const char* name() { return "myStaticMethod";} };
JObject<CT>::callStatic<R, MyStaticMethod>(args...);
*/
template<typename T, class MTag, typename... Args, detail::if_MethodTag<MTag> = true>
static T callStatic(Args&&... args);
template<class MTag, typename... Args, detail::if_MethodTag<MTag> = true>
static void callStatic(Args&&... args);
// get/set field and static field
template<class FTag, typename T, detail::if_FieldTag<FTag> = true>
T get() const;
template<class FTag, typename T, detail::if_FieldTag<FTag> = true>
bool set(T&& v);
template<class FTag, typename T, detail::if_FieldTag<FTag> = true>
static T getStatic();
template<class FTag, typename T, detail::if_FieldTag<FTag> = true>
static bool setStatic(T&& v);
// the following call()/callStatic() will always invoke GetMethodID()/GetStaticMethodID()
template<typename T, typename... Args>
T call(const string_view& methodName, Args&&... args) const; // ambiguous methodName and arg?
template<typename... Args>
void call(const string_view& methodName, Args&&... args) const;
template<typename T, typename... Args>
static T callStatic(const string_view& name, Args&&... args);
template<typename... Args>
static void callStatic(const string_view& name, Args&&... args);
template<typename T>
T get(string_view&& fieldName) const;
template<typename T>
bool set(string_view&& fieldName, T&& v);
template<typename T>
static T getStatic(string_view&& fieldName);
template<typename T>
static bool setStatic(string_view&& fieldName, T&& v);
/*
Field API
Field lifetime is bounded to JObject, it does not add object ref, when object is destroyed/reset, accessing Field will fail (TODO: how to avoid crash?)
jfieldID is cacheable if MayBeFTag is a FieldTag
Usage:
auto f = obj.field<int, MyFieldTag>(), obj.field<int>("MyField"), JObject<...>::staticField<string>("MySField");
auto& sf = JObject<...>::staticField<string, MySFieldTag>();
f.set(123)/get(), sf.set("test")/get();
f = 345; int fv = f;
*/
// F can be supported types: jni primitives(jint, jlong, ... not jobject because we can't know class name) and JObject
template<typename F, class MayBeFTag, bool isStaticField>
class Field { // JObject.classId() works in Field?
public:
jfieldID id() const { return fid_; }
operator jfieldID() const { return fid_; }
operator F() const { return get(); }
F get() const;
void set(F&& v);
Field& operator=(F&& v) {
set(std::forward<F>(v));
return *this;
}
protected:
static jfieldID cachedId(jclass cid); // usually cid is used only once
// oid nullptr: static field
// it's protected so we can sure cacheable ctor will not be called for uncacheable Field
Field(jclass cid, jobject oid = nullptr);
Field(jclass cid, const char* name, jobject oid = nullptr);
union {
jobject oid_;
jclass cid_;
};
jfieldID fid_ = nullptr;
friend class JObject<CTag>;
};
template<class FTag, typename T, detail::if_FieldTag<FTag> = true>
auto field() const->Field<T, FTag, false> {
return Field<T, FTag, false>(classId(), oid_);
}
template<typename T>
auto field(string_view&& name) const->Field<T, void, false> {
return Field<T, void, false>(classId(), name.data(), oid_);
}
template<class FTag, typename T, detail::if_FieldTag<FTag> = true>
static auto staticField()->Field<T, FTag, true>& { // cacheable and static java storage, so returning ref is better
static Field<T, FTag, true> f(classId());
return f;
}
template<typename T>
static auto staticField(string_view&& name)->Field<T, void, true> {
return Field<T, void, true>(classId(), name.data());
}
private:
static jclass classId(JNIEnv* env = nullptr);
JObject& setError(const string& s) const {
error_ = std::move(s);
return *const_cast<JObject*>(this);
}
jobject oid_ = nullptr;
mutable string error_;
};
template<class CTag>
using Object = JObject<CTag>;
/*************************** JMI Public APIs End ***************************/
} // namespace jmi
#define JMI_STRINGIFY(X) _JMI_STRINGIFY(X)
#define _JMI_STRINGIFY(X) #X
namespace jmi {
#if !(JMI_CXX20 + 0)
template< class T >
struct remove_cvref {
using type = typename remove_cv<typename std::remove_reference<T>::type>::type;
};
template< class T >
using remove_cvref_t = typename remove_cvref<T>::type;
#endif
namespace detail {
using namespace std;
template <typename T, typename = void>
struct is_array_like : false_type {};
template <typename T>
struct is_array_like<T, decltype(void(declval<T>()[0]), void(declval<T>().size()))> : true_type {};
template <typename T, typename = void>
struct is_string : false_type {};
template <typename T>
struct is_string<T, decltype(void(declval<T>().substr()))> : true_type {};
template <typename T>
struct is_jarray_cpp : integral_constant<bool, (is_array_like<T>::value || is_array<T>::value)
&& !is_string<T>::value
&& !is_same<typename decay<T>::type, char*>::value
&& !is_same<typename decay<T>::type, const char*>::value> {};
template<class T, typename = void>
struct is_ref_wrap :false_type{};
template<class T>
struct is_ref_wrap<T, decltype(void(!declval<is_same<reference_wrapper<typename T::type>, remove_cvref_t<T>>>()))>: true_type{};
template<typename T>
using if_jarray_cpp = typename enable_if<is_jarray_cpp<T>::value, bool>::type;
template<typename T>
using if_not_jarray_cpp = typename enable_if<!is_jarray_cpp<T>::value, bool>::type;
// T* and T(&)[N] are treated as the same. use enable_if to select 1 of them. The function parameter is (const T&), so the default implementation of signature_of(const T&) must check is_pointer too.
template<typename T> using if_pointer = typename enable_if<is_pointer<T>::value, bool>::type;
template<typename T> using if_not_pointer = typename enable_if<!is_pointer<T>::value, bool>::type;
template<class T>
struct is_jarray : integral_constant<bool, is_base_of<typename remove_pointer<jarray>::type, typename remove_pointer<T>::type>::value> {};
template<class T>
using if_jarray = typename enable_if<is_jarray<T>::value, bool>::type;
template<class T>
using if_not_jarray = typename enable_if<!is_jarray<T>::value, bool>::type;
template<class T>
using if_ref_wrap = typename enable_if<is_ref_wrap<T>::value, bool>::type;
template<class T>
using if_not_ref_wrap = typename enable_if<!is_ref_wrap<T>::value, bool>::type;
template<class T1, typename T2>
using if_same = typename enable_if<is_same<T1, T2>::value, bool>::type;
template<typename T>
using if_cstring = enable_if_t<is_same<decay_t<T>, char*>::value || is_same<decay_t<T>, const char*>::value, bool>;
template<typename T>
using if_not_cstring = enable_if_t<!is_same<decay_t<T>, char*>::value && !is_same<decay_t<T>, const char*>::value, bool>;
} // namespace detail
inline namespace impl {
static inline string to_string(const string& s) noexcept { return s;}
#if (JMI_CXX17+0) && (JMI_USE_CXX17 + 0)
template<typename F, size_t... I>
constexpr auto make_array(F&& f, index_sequence<I...>) { return array{ f(I)... };}
template<size_t N, typename F>
constexpr auto make_array(F&& f) { return make_array(std::forward<F>(f), make_index_sequence<N>{}); }
template<size_t N, typename A>
constexpr auto sub_array(A&& a, size_t i0) {
return make_array<N>([&](size_t i){ return a[i + i0]; });
}
template <size_t N, typename T, size_t... I>
constexpr auto to_array(T const* a, index_sequence<I...>) noexcept
{
return array{ a[I]... };
}
template <typename T, size_t N>
constexpr auto to_array(array<T, N> a) noexcept { return a; }
template <size_t N>
constexpr auto to_array(char const(&s)[N]) noexcept
{
return to_array<N>(s, make_index_sequence<N>());
}
template<typename T>
constexpr auto to_array(T c) noexcept { return array{c, T{}}; }
template <typename T, size_t N1, size_t N2, size_t... I1, size_t... I2>
constexpr auto concat(array<T, N1> a1, array<T, N2> a2, index_sequence<I1...>, index_sequence<I2...>) noexcept
{
return array{ a1[I1]..., a2[I2]... };
}
template <typename T, size_t N1, size_t N2>
constexpr auto concat(array<T, N1> a1, array<T, N2> a2) noexcept
{
return concat(a1, a2, make_index_sequence<N1>(), make_index_sequence<N2>());
}
// zconcat: assume input arrays are end with T{}, remove the last T{} in a1, like c string concat
template <typename T, size_t N1, size_t N2>
constexpr auto zconcat(array<T, N1> a1, array<T, N2> a2) noexcept
{
return concat(a1, a2, make_index_sequence<N1-1>(), make_index_sequence<N2>());
}
template <typename T1, typename T2>
constexpr auto zconcat(T1&& t1, T2&& t2) noexcept
{
return zconcat(to_array(std::forward<T1>(t1)), to_array(std::forward<T2>(t2)));
}
template <typename T1, typename T2, typename... Rest>
constexpr auto zconcat(T1&& t1, T2&& t2, Rest&&... rest) noexcept
{
return zconcat(zconcat(std::forward<T1>(t1), std::forward<T2>(t2)), std::forward<Rest>(rest)...);
}
// input a and returned sub array are T{}(null) terminated
template<size_t S, typename T, size_t N>
constexpr auto zsub(array<T, N> a, size_t i0) {
return concat(sub_array<S>(a, i0), array{T{}});
}
template<size_t N, size_t... I>
constexpr auto norm_impl(array<char, N> a, index_sequence<I...>) noexcept
{
return array{(a[I] == '.' ? '/' : a[I])...};
}
template<size_t N>
constexpr auto norm(array<char, N> a) noexcept
{
// if constexpr (a[0] == 'L' && a[N - 1] == ';'): a[0] is not a constant expression
return norm_impl(a, make_index_sequence<N>{});
}
template<size_t N>
string to_string(array<char, N> const& a) noexcept
{
return {a.data(), a[N - 1] ? N : N - 1};
}
template<size_t N1, size_t N2>
inline constexpr bool operator==(const array<char, N1> a, const char (&s)[N2]) {
return N1 == N2 && equal(a.begin(), a.end(), begin(s));
}
#else
static inline string to_string(const char& s) noexcept { return {s};}
template<size_t N>
string to_string(const char (&s)[N]) noexcept { return s;}
template<size_t N>
constexpr const char* to_array(const char (&s)[N]) noexcept { return s;}
static inline auto to_array(const string& s) noexcept { return s;}
static inline string to_array(char s) noexcept { return {s};}
template <typename T1>
auto zconcat(T1 const& t1) noexcept { return to_string(t1);}
template <typename T1, typename... Rest>
auto zconcat(T1 const& t1, Rest const&... rest) noexcept
{
return to_string(t1).append(zconcat(rest...));
}
static inline auto norm(string s) noexcept
{
if (s[0] == 'L' && s.back() == ';')
s = s.substr(1, s.size()-2);
replace(s.begin(), s.end(), '.', '/');
return s;
}
#endif
} // namespace impl
/*************************** Below is JMI implementation and internal APIs***************************/
//signature_of_args<decltype(Args)...>::value, template<typename ...A> struct signature_of_args?
template<> struct signature<bool> { static constexpr char value = 'Z';}; // jboolean is uint8_t/uchar
template<> struct signature<jboolean> { static constexpr char value = 'Z';};
template<> struct signature<jbyte> { static constexpr char value = 'B';};
template<> struct signature<jchar> { static constexpr char value = 'C';};
template<> struct signature<jshort> { static constexpr char value = 'S';};
template<> struct signature<jlong> { static constexpr char value = 'J';};
template<> struct signature<jint> { static constexpr char value = 'I';};
template<> struct signature<jfloat> { static constexpr char value = 'F';};
template<> struct signature<jdouble> { static constexpr char value = 'D';};
template<> struct signature<jbooleanArray> { static constexpr auto value = to_array("[Z");};
template<> struct signature<jbyteArray> { static constexpr auto value = to_array("[B");};
template<> struct signature<jcharArray> { static constexpr auto value = to_array("[C");};
template<> struct signature<jshortArray> { static constexpr auto value = to_array("[S");};
template<> struct signature<jintArray> { static constexpr auto value = to_array("[I");};
template<> struct signature<jlongArray> { static constexpr auto value = to_array("[J");};
template<> struct signature<jfloatArray> { static constexpr auto value = to_array("[F");};
template<> struct signature<jdoubleArray> { static constexpr auto value = to_array("[D");};
// "L...;" is used in method parameter
template<> struct signature<string> { static constexpr auto value = to_array("Ljava/lang/String;");};
template<> struct signature<char*> { static constexpr auto value = to_array("Ljava/lang/String;");};
template<typename E>
struct signature<E, true> : signature<jint>{};
template<typename T, detail::if_not_pointer<T> = true, detail::if_not_JObject<T> = true, detail::if_not_jarray_cpp<T> = true
, detail::if_not_ref_wrap<T> = true, detail::if_not_cstring<T> = true>
CONSTEXPR17 auto signature_of() {
return to_array(signature<remove_cvref_t<decay_t<T>>>::value); // initializer supports both char and char*
}
//template<class CTag> inline string signature_of(const JObject<CTag>& t) { return t.signature();} // won't work if JObject subclass inherits JObject<...>
// TODO: use c++20 requires
template<class T, detail::if_JObject<T> = true>
CONSTEXPR17 auto signature_of() { return T::signature();}
// if T is jobject or LocalRef, signature can get from GetObjectClass=>getName, but can not be cached
constexpr auto signature_of() { return 'V';}
template<typename T, detail::if_jarray_cpp<T> = true>
CONSTEXPR17 auto signature_of() {
return zconcat('[', signature_of<remove_cvref_t<decltype(T{}[0])>>()); // both c array and cpp containers
}
template<typename T, detail::if_cstring<T> = true>
constexpr auto signature_of() { return signature<char*>::value;}
template<typename T, detail::if_pointer<T> = true, detail::if_not_jarray<T> = true, detail::if_not_cstring<T> = true>
constexpr auto signature_of() { return signature<jlong>::value;}
template<typename T, detail::if_pointer<T> = true, detail::if_jarray<T> = true>
constexpr auto signature_of() { return signature<T>::value;}
// NOTE: define reference_wrapper at last. assume we only use reference_wrapper<...>, no container<reference_wrapper<...>>
template<typename T, detail::if_ref_wrap<T> = true, detail::if_not_jarray_cpp<typename T::type> = true>
CONSTEXPR17 auto signature_of() {
return signature_of<typename T::type>();
}
template<typename T, detail::if_ref_wrap<T> = true, detail::if_jarray_cpp<typename T::type> = true>
CONSTEXPR17 auto signature_of() {
using E = typename T::type;
return zconcat('[', signature_of<remove_cvref_t<decltype(E{}[0])>>());
}
// signature_of_no_ptr: consistent for any type, including void. so for call<T,MT>(...) T can be void. TODO: remove
template<typename T, typename enable_if<is_pointer<T>::value && !is_same<T, void*>::value, bool>::type = true>
CONSTEXPR17 auto signature_of_no_ptr() { return signature_of<typename remove_pointer<T>::type>();}
template<typename T, typename enable_if<is_same<T, void*>::value, bool>::type = true>
constexpr auto signature_of_no_ptr() { return signature_of();}
namespace detail {
template<typename... Args>
CONSTEXPR17 auto args_signature() {
return zconcat('(', signature_of<remove_cvref_t<Args>>()..., ')');
}
static inline CONSTEXPR17 auto args_signature() { return zconcat('(', signature_of(), ')');}
} //namespace detail
template<typename R, typename... Args>
CONSTEXPR17 auto signature_of(R (*)(Args...)) {
return zconcat(detail::args_signature<Args...>(), signature_of_no_ptr<typename add_pointer<R>::type>());;
}
namespace detail {
bool handle_exception(JNIEnv* env = nullptr);
template<class F>
class scope_exit_handler {
F f_;
bool invoke_;
public:
scope_exit_handler(F f) noexcept : f_(std::move(f)), invoke_(true) {}
scope_exit_handler(scope_exit_handler&& other) noexcept : f_(std::move(other.f_)), invoke_(other.invoke_) {
other.invoke_ = false;
}
~scope_exit_handler() {
if (invoke_)
f_();
}
scope_exit_handler(const scope_exit_handler&) = delete;
scope_exit_handler& operator=(const scope_exit_handler&) = delete;
};
template<class F>
scope_exit_handler<F> call_on_exit(const F& f) noexcept {
return scope_exit_handler<F>(f);
}
template<class F>
scope_exit_handler<F> call_on_exit(F&& f) noexcept {
return scope_exit_handler<F>(std::forward<F>(f));
}
template<typename T, if_not_JObject<T> = true>
jarray make_jarray(JNIEnv *env, const T &element, size_t size); // element is for getting jobject class
template<class T, if_JObject<T> = true>
jarray make_jarray(JNIEnv *env, const T &element, size_t size) {
return env->NewObjectArray(size, jclass(element), nullptr);
}
template<typename T, if_not_JObject<T> = true>
void set_jarray(JNIEnv *env, jarray arr, size_t position, size_t n, const T &elm);
template<class T, if_JObject<T> = true>
void set_jarray(JNIEnv *env, jarray arr, size_t position, size_t n, const T &elm) {
set_jarray(env, arr, position, n, jobject(elm));
}
template<typename T>
jarray to_jarray(JNIEnv* env, const T &c0, size_t N, bool is_ref = false);
template<typename T, size_t N>
jarray to_jarray(JNIEnv* env, const T(&c)[N], bool is_ref = false) {
return to_jarray(env, c[0], N, is_ref);
}
template<typename C> // c++ container (vector, valarray, array) to jarray. no if_jarray_cpp check (requires overload for both vector like and array like containers) because it's checked by to_jvalue
jarray to_jarray(JNIEnv* env, const C &c, bool is_ref = false) {
return to_jarray(env, c[0], c.size(), is_ref);
}
// env can be null for base types
template<typename T>
using if_enum = typename enable_if<is_enum<T>::value, bool>::type;
template<typename T>
using if_not_enum = typename enable_if<!is_enum<T>::value, bool>::type;
template<typename T, if_not_enum<T> = true, if_not_JObject<T> = true>
jvalue to_jvalue(const T &obj, JNIEnv* env = nullptr);
template<typename T, if_enum<T> = true, if_not_JObject<T> = true>
jvalue to_jvalue(const T &obj, JNIEnv* env = nullptr) {return to_jvalue((jint)obj, env);}
template<typename T> jvalue to_jvalue(T *obj, JNIEnv* env) { return to_jvalue((jlong)obj, env); } // works for jobject
jvalue to_jvalue(const char* obj, JNIEnv* env);// { return to_jvalue(string(obj)); }
template<typename T, if_not_enum<T> = true, if_JObject<T> = true>
jvalue to_jvalue(const T &obj, JNIEnv* env = nullptr) { return to_jvalue(jobject(obj), env);}
template<template<typename,class...> class C, typename T, class... A, if_jarray_cpp<C<T, A...>> = true> // if_jarray_cpp: exclude string, jarray works (copy chars)
jvalue to_jvalue(const C<T, A...> &c, JNIEnv* env) { return to_jvalue(to_jarray(env, c), env); }
template<typename T, size_t N> jvalue to_jvalue(const array<T, N> &c, JNIEnv* env) { return to_jvalue(to_jarray(env, c), env); }
template<typename T> jvalue to_jvalue(const reference_wrapper<T>& t, JNIEnv* env) { return to_jvalue(t.get(), env); } // TODO: no jvalue set
template<template<typename,class...> class C, typename T, class... A, if_jarray_cpp<C<T, A...>> = true> // if_jarray_cpp: exclude string, jarray works (copy chars)
jvalue to_jvalue(const reference_wrapper<C<T, A...>>& c, JNIEnv* env) { return to_jvalue(to_jarray(env, c.get(), true), env); }
template<typename T, size_t N> jvalue to_jvalue(const reference_wrapper<T[N]>& c, JNIEnv* env) { return to_jvalue(to_jarray<T,N>(env, c.get(), true), env); }
template<class CTag>
jvalue to_jvalue(const JObject<CTag> &obj, JNIEnv* env);
// T(&)[N]?
// from_jvalue/array() is called if parameter of call() is of type reference_wrapper<...>
template<typename T, if_not_JObject<T> = true>
void from_jarray(JNIEnv* env, const jvalue& v, T* t, size_t N);
template<typename T, if_JObject<T> = true>
void from_jarray(JNIEnv* env, const jvalue& v, T* t, size_t N) {
for (size_t i = 0; i < N; ++i) {
LocalRef s = {env->GetObjectArrayElement(static_cast<jobjectArray>(v.l), i), env};
(t + i)->reset(s);
}
}
// reference_wrapper<const T> should do nothing
template<typename T> void from_jvalue(JNIEnv* env, const jvalue& v, const T &t) {}
// env can be null for base types
template<typename T, if_not_JObject<T> = true> void from_jvalue(JNIEnv* env, const jvalue& v, T &t);
// reference_wrapper<const T[]> should do nothing
template<typename T> void from_jvalue(JNIEnv* env, const jvalue& v, const T *t, size_t n = 0) {}
template<typename T> void from_jvalue(JNIEnv* env, const jvalue& v, T *t, size_t n = 0) { // T* and T(&)[N] is the same
if (n <= 0)
from_jvalue(env, v, (jlong&)t);
else
from_jarray(env, v, t, n);
}
template<typename T, if_JObject<T> = true> void from_jvalue(JNIEnv* env, const jvalue& v, T &t) {
t.reset(v.l, env); // local ref will be deleted in caller set_ref_from_jvalue()
}
template<template<typename,class...> class C, typename T, class... A, if_jarray_cpp<C<T, A...>> = true> // if_jarray_cpp: exclude string. jarray works too (copy chars)
void from_jvalue(JNIEnv* env, const jvalue& v, C<T, A...> &t) { from_jarray(env, v, &t[0], t.size()); }
template<typename T, size_t N> void from_jvalue(JNIEnv* env, const jvalue& v, array<T, N> &t) { from_jarray(env, v, t.data(), N); }
//template<typename T, size_t N> void from_jvalue(JNIEnv* env, const jvalue& v, T(&t)[N]) { from_jarray(env, v, t, N); }
template<typename T> struct has_local_ref { // is_jobject<T>? is_jarray_cpp?
static const bool value = !is_arithmetic<T>::value && !is_pointer<T>::value && !is_JObject<T>::value;
};
template<typename T>
void set_ref_from_jvalue(JNIEnv* env, jvalue* jargs, T) {
using Tn = typename remove_reference<T>::type;
if (has_local_ref<Tn>::value)
env->DeleteLocalRef(jargs->l);
}
static inline void set_ref_from_jvalue(JNIEnv* env, jvalue *jargs, const char*) {
env->DeleteLocalRef(jargs->l);
}
template<typename T>
void set_ref_from_jvalue(JNIEnv* env, jvalue *jargs, reference_wrapper<T> ref) { // do nothing in from_jvalue for const T
from_jvalue(env, *jargs, ref.get());
using Tn = typename remove_reference<T>::type;
if (has_local_ref<Tn>::value)
env->DeleteLocalRef(jargs->l);
}
static inline void delete_array_local_ref(JNIEnv* env, jarray a, size_t n, bool delete_elements) {
if (delete_elements) {
for (jsize i = 0; i < jsize(n); ++i)
LocalRef ei = {env->GetObjectArrayElement(jobjectArray(a), i), env};
}
env->DeleteLocalRef(a);
}
template<template<typename,class...> class C, typename T, class... A, if_jarray_cpp<C<T, A...>> = true> // if_jarray_cpp: exclude string, jarray works (copy chars)
void set_ref_from_jvalue(JNIEnv* env, jvalue *jargs, reference_wrapper<C<T, A...>> ref) {
from_jvalue(env, *jargs, ref.get());
using Tn = typename remove_reference<T>::type;
delete_array_local_ref(env, static_cast<jarray>(jargs->l), ref.get().size(), has_local_ref<Tn>::value);
}
template<typename T, size_t N>
void set_ref_from_jvalue(JNIEnv* env, jvalue *jargs, reference_wrapper<T[N]> ref) {
from_jvalue(env, *jargs, ref.get(), N); // assume only T* and T[N]
delete_array_local_ref(env, static_cast<jarray>(jargs->l), N, has_local_ref<T>::value);
}
template<typename T, size_t N>
void set_ref_from_jvalue(JNIEnv* env, jvalue *jargs, reference_wrapper<array<T, N>> ref) {
from_jvalue(env, *jargs, &ref.get()[0], N); // assume only T* and T[N]
delete_array_local_ref(env, static_cast<jarray>(jargs->l), N, has_local_ref<T>::value);
}
static inline void ref_args_from_jvalues(JNIEnv*, jvalue*) {}
template<typename Arg, typename... Args>
void ref_args_from_jvalues(JNIEnv* env, jvalue *jargs, Arg&& arg, Args&&... args) {
set_ref_from_jvalue(env, jargs, std::forward<Arg>(arg));
ref_args_from_jvalues(env, jargs + 1, std::forward<Args>(args)...);
}
template<typename T, if_not_JObject<T> = true, if_not_jarray_cpp<T> = true>
T call_method(JNIEnv *env, jobject oid, jmethodID mid, jvalue *args);
template<class T, if_JObject<T> = true>
T call_method(JNIEnv *env, jobject oid, jmethodID mid, jvalue *args) {
T t;
LocalRef r = call_method<jobject>(env, oid, mid, args);
if (!r || env->ExceptionCheck())
return T();
t.reset(r, env);
return t;
}
template<typename T, if_jarray_cpp<T> = true>
T call_method(JNIEnv *env, jobject oid, jmethodID mid, jvalue *args) {
LocalRef ja = call_method<jobject>(env, oid, mid, args); // local ref will not be deleted in from_jvalue(), so manage here
if (!ja || env->ExceptionCheck())
return T();
jvalue jv;
jv.l = ja;
T t(env->GetArrayLength(ja));
from_jvalue(env, jv, t);
return t;
}
template<typename T, typename... Args>
T call_method_set_ref(JNIEnv *env, jobject oid, jmethodID mid, jvalue *jargs, Args&&... args) {
auto setter = call_on_exit([=]{
ref_args_from_jvalues(env, jargs, args...);
});
return call_method<T>(env, oid, mid, jargs);
}
template<typename T, if_not_JObject<T> = true, if_not_jarray_cpp<T> = true>
T call_static_method(JNIEnv *env, jclass classId, jmethodID methodId, jvalue *args);
template<class T, if_JObject<T> = true>
T call_static_method(JNIEnv *env, jclass cid, jmethodID mid, jvalue *args) {
LocalRef r = call_static_method<jobject>(env, cid, mid, args);
if (!r || env->ExceptionCheck())
return T();
T t;
t.reset(r, env);
return t;
}
template<class T, if_jarray_cpp<T> = true>
T call_static_method(JNIEnv *env, jclass cid, jmethodID mid, jvalue *args) {
LocalRef ja = call_static_method<jobject>(env, cid, mid, args); // local ref will not be deleted in from_jvalue(), so manage here
if (!ja || env->ExceptionCheck())
return T();
jvalue jv;
jv.l = ja;
T t(env->GetArrayLength(ja)); // TODO: array is not supported
from_jvalue(env, jv, t);
return t;
}
template<typename T, typename... Args>
T call_static_method_set_ref(JNIEnv *env, jclass cid, jmethodID mid, jvalue *jargs, Args&&... args) {
auto setter = call_on_exit([=]{ // std::forward?
ref_args_from_jvalues(env, jargs, args...);
});
return call_static_method<T>(env, cid, mid, jargs);
}
template<typename T, typename... Args>
T call_with_methodID(jobject oid, jclass cid, jmethodID* pmid, function<void(string&& err)> err_cb, const char* signature, const char* name, Args&&... args) {
if (err_cb)
err_cb(string());
if (!cid)
return T();
if (!oid) {
if (err_cb)
err_cb("Invalid object instance");
return T();
}
JNIEnv *env = getEnv();
const auto checker = call_on_exit([=]{
if (handle_exception(env)) {
if (err_cb)
err_cb(string("Failed to call method '") + name + "' with signature '" + signature + "'.");
}
});
jmethodID mid = nullptr;
if (pmid)
mid = *pmid;
if (!mid) {
mid = env->GetMethodID(cid, name, signature);
if (pmid)
*pmid = mid;
}
if (!mid || env->ExceptionCheck())
return T();
return call_method_set_ref<T>(env, oid, mid, const_cast<jvalue*>(initializer_list<jvalue>({to_jvalue(std::forward<Args>(args), env)...}).begin()), std::forward<Args>(args)...);
}
template<typename T, typename... Args>
T call_static_with_methodID(jclass cid, jmethodID* pmid, function<void(string&& err)> err_cb, const char* signature, const char* name, Args&&... args) {
if (err_cb)
err_cb(string());
if (!cid)
return T();
JNIEnv *env = getEnv();
auto checker = call_on_exit([=]{
if (handle_exception(env)) {
if (err_cb)
err_cb(string("Failed to call static method '") + name + "'' with signature '" + signature + "'.");
}
});
jmethodID mid = nullptr;
if (pmid)
mid = *pmid;
if (!mid) {
mid = env->GetStaticMethodID(cid, name, signature);
if (pmid)
*pmid = mid;
}
if (!mid || env->ExceptionCheck())
return T();
return call_static_method_set_ref<T>(env, cid, mid, const_cast<jvalue*>(initializer_list<jvalue>({to_jvalue(std::forward<Args>(args), env)...}).begin()), std::forward<Args>(args)...);
}
template<typename T>
jfieldID get_field_id(JNIEnv* env, jclass cid, const char* name, jfieldID* pfid = nullptr);
template<class T, if_not_JObject<T> = true, if_not_jarray_cpp<T> = true>
T get_field(JNIEnv* env, jobject oid, jfieldID fid);
template<class T, if_JObject<T> = true>
T get_field(JNIEnv* env, jobject oid, jfieldID fid) {
LocalRef r = env->GetObjectField(oid, fid);
if (!r)
return T();
T t;
t.reset(r, env);
return t;
}
template<class T, if_jarray_cpp<T> = true>
T get_field(JNIEnv* env, jobject oid, jfieldID fid) {
LocalRef ja = env->GetObjectField(oid, fid);
if (!ja || env->ExceptionCheck())
return T();
jvalue jv;
jv.l = ja;
T t(env->GetArrayLength(ja));
from_jvalue(env, jv, t);
return t;
}
template<typename T>
T get_field(jobject oid, jclass cid, jfieldID* pfid, const char* name) {
JNIEnv* env = getEnv();
// TODO: call_on_exit?
jfieldID fid = get_field_id<T>(env, cid, name, pfid);
if (!fid) // no exception check, already exist in get()? what about call?
return T();
return get_field<T>(env, oid, fid);
}
template<class T>
void set_field(JNIEnv* env, jobject oid, jfieldID fid, T&& v);
template<typename T>
void set_field(jobject oid, jclass cid, jfieldID* pfid, const char* name, T&& v) {
JNIEnv* env = getEnv();
// TODO: call_on_exit?
jfieldID fid = get_field_id<T>(env, cid, name, pfid);
if (!fid)
return;
set_field<T>(env, oid, fid, std::forward<T>(v));
}
template<typename T>
jfieldID get_static_field_id(JNIEnv* env, jclass cid, const char* name, jfieldID* pfid = nullptr);
template<typename T, if_not_JObject<T> = true, if_not_jarray_cpp<T> = true>
T get_static_field(JNIEnv* env, jclass cid, jfieldID fid);
template<class T, if_JObject<T> = true>
T get_static_field(JNIEnv* env, jclass cid, jfieldID fid) {
LocalRef r = env->GetStaticObjectField(cid, fid);
if (!r || env->ExceptionCheck())
return T();
T t;
t.reset(r, env);
return t;
}
template<class T, if_jarray_cpp<T> = true>
T get_static_field(JNIEnv* env, jclass cid, jfieldID fid) {
LocalRef ja = env->GetStaticObjectField(cid, fid);
if (!ja || env->ExceptionCheck())
return T();
jvalue jv;
jv.l = ja;
T t(env->GetArrayLength(ja));
from_jvalue(env, jv, t);
return t;
}
template<typename T>
T get_static_field(jclass cid, jfieldID* pfid, const char* name) {
JNIEnv* env = getEnv();
jfieldID fid = get_static_field_id<T>(env, cid, name, pfid);
if (!fid)
return T();
return get_static_field<T>(env, cid, fid);
}
template<typename T>
void set_static_field(JNIEnv* env, jclass cid, jfieldID fid, T&& v);
template<typename T>
void set_static_field(jclass cid, jfieldID* pfid, const char* name, T&& v) {
JNIEnv* env = getEnv();
jfieldID fid = get_static_field_id<T>(env, cid, name, pfid);
if (!fid)
return;
set_static_field<T>(env, cid, fid, std::forward<T>(v));
}
template<typename T>
jfieldID get_field_id(JNIEnv* env, jclass cid, const char* name, jfieldID* pfid) {
jfieldID fid = nullptr;
if (pfid)
fid = *pfid;
if (!fid) {
fid = env->GetFieldID(cid, name, signature_of<T>().data());
if (pfid)
*pfid = fid;
}
return fid;
}
template<typename T>
jfieldID get_static_field_id(JNIEnv* env, jclass cid, const char* name, jfieldID* pfid) {
jfieldID fid = nullptr;
if (pfid)
fid = *pfid;
if (!fid) {
fid = env->GetStaticFieldID(cid, name, signature_of<T>().data());
if (pfid)
*pfid = fid;
}
return fid;
}
} // namespace detail
template<class CTag>
CONSTEXPR17 auto JObject<CTag>::className()
{
#if (JMI_CXX17+0) && (JMI_USE_CXX17 + 0)
if constexpr (CTag::name()[0] == 'L' && CTag::name()[CTag::name().size() - 2] == ';') // N - 1 == '\0', check N - 2
return impl::norm(zsub<CTag::name().size() - 3>(CTag::name(), 1));
else
return impl::norm(CTag::name());
#else
static string s = impl::norm(CTag::name());
return s;
#endif // (JMI_CXX17+0) && (JMI_USE_CXX17 + 0)
}
template<class CTag>
CONSTEXPR17 auto JObject<CTag>::signature()
{
return zconcat("L", className(), ";");
}
template<class CTag>
JObject<CTag>& JObject<CTag>::reset(jobject obj, JNIEnv *env) {
if (oid_ == obj)
return *this;
error_.clear();
if (!env) {
env = getEnv();
if (!env)
return setError("Invalid JNIEnv");
}
env->DeleteGlobalRef(oid_); // can be null
oid_ = nullptr;
if (obj) {
oid_ = env->NewGlobalRef(obj);
//env->DeleteLocalRef(obj); // obj from JObject has no local ref
}
return *this;
}
template<class CTag>
template<typename... Args>
bool JObject<CTag>::create(Args&&... args) {
using namespace std;
using namespace detail;
JNIEnv* env = nullptr; // FIXME: why build error if let env be the last parameter of create()?
if (!env) {
env = getEnv();
if (!env) {