1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <cassert>
18 #include <cstring>
19 #include <stdexcept>
20 #include <type_traits>
21 #include <vector>
22
23 // SECTION registration
24 #include <fbjni/fbjni.h>
25 using namespace facebook::jni;
26 // END
27
28 // SECTION byte_buffer
29 #include <fbjni/ByteBuffer.h>
30 // END
31
32 // We can put all of our code in an anonymous namespace if
33 // it is not used from any other C++ code.
34 namespace {
35
36 // SECTION inheritance
37 struct JMyBaseClass : JavaClass<JMyBaseClass> {
38 static constexpr auto kJavaDescriptor = "Lcom/facebook/jni/MyBaseClass;";
39 };
40
41 struct JMyDerivedClass : JavaClass<JMyDerivedClass, JMyBaseClass> {
42 static constexpr auto kJavaDescriptor = "Lcom/facebook/jni/MyDerivedClass;";
43 };
44
45 /* MARKDOWN
46 This will allow implicit casts from Derived to Base and explicit downcasts.
47 When no base class is given, JObject will be used as the base.
48 // END
49 */
50
51 // SECTION nested_class
52 struct JNested : JavaClass<JNested> {
53 static constexpr auto kJavaDescriptor = "Lcom/facebook/jni/Outer$Nested;";
create__anon99d09a770111::JNested54 static local_ref<JNested> create() {
55 return newInstance();
56 }
57 };
58 // END
59
60 // SECTION constructor
61 struct JDataHolder : JavaClass<JDataHolder> {
62 static constexpr auto kJavaDescriptor = "Lcom/facebook/jni/DataHolder;";
63 // newInstance should be wrapped to ensure compile-time checking of call
64 // sites.
create__anon99d09a770111::JDataHolder65 static local_ref<JDataHolder> create(int i, std::string const& s) {
66 // Constructor is looked up by argument types at *runtime*.
67 return newInstance(i, s);
68 }
69 // END
70
71 // SECTION fields
getAndSetFields__anon99d09a770111::JDataHolder72 void getAndSetFields() {
73 static const auto cls = javaClassStatic();
74 // Primitive fields.
75 static const auto iField = cls->getField<jint>("i");
76 jint i = this->getFieldValue(iField);
77 this->setFieldValue(iField, i + 1);
78 // Object fields work for standard classes and your own JavaObject classes.
79 static const auto sField = cls->getField<JString>("s");
80 // Object are returned as local refs ...
81 local_ref<JString> s = this->getFieldValue(sField);
82 // and can be set from any ref.
83 this->setFieldValue(sField, make_jstring(s->toStdString() + "1").get());
84 // Static fields work the same, but getStaticField, getStaticFieldValue,
85 // and setStaticFieldValue must all be called on the class object.
86 static const auto someInstanceField =
87 cls->getStaticField<JDataHolder>("someInstance");
88 auto inst = cls->getStaticFieldValue(someInstanceField);
89 if (!inst) {
90 // NOTE: Can't use cls here because it is declared const.
91 getClass()->setStaticFieldValue(someInstanceField, self());
92 }
93 }
94 // END
95
getStr__anon99d09a770111::JDataHolder96 local_ref<JString> getStr() {
97 static const auto cls = javaClassStatic();
98 static const auto sField = cls->getField<JString>("s");
99 return getFieldValue(sField);
100 }
101 };
102
103 // SECTION registration
104 // Standard declaration for a normal class (no C++ fields).
105 struct DocTests : JavaClass<DocTests> {
106 static constexpr auto kJavaDescriptor = "Lcom/facebook/jni/DocTests;";
107 // END
108
109 // SECTION constructor
110 // Call-site in another file.
runConstructor__anon99d09a770111::DocTests111 static local_ref<JDataHolder> runConstructor(alias_ref<JClass> clazz) {
112 // Call to ordinatry C++ function is checked at *compile time*.
113 return JDataHolder::create(1, "hi");
114 }
115 // END
116
117 // SECTION basic_methods
118 public:
119 // Java methods should usually be wrapped by C++ methods for ease-of-use.
120 // (Most other examples in this document will inline these for brevity.)
callVoidMethod__anon99d09a770111::DocTests121 void callVoidMethod() {
122 static const auto method = getClass()->getMethod<void()>("voidMethod");
123 // self() returns the raw JNI reference to this object.
124 method(self());
125 }
callStaticVoidMethod__anon99d09a770111::DocTests126 static void callStaticVoidMethod() {
127 static const auto cls = javaClassStatic();
128 static const auto method = cls->getStaticMethod<void()>("staticVoidMethod");
129 method(cls);
130 }
131
132 // Native implementations of Java methods can be private.
133 private:
134 // For non-Hybrid objects, all JNI methods must be static on the C++ side
135 // because only Hybrid objects can have C++ state.
nativeVoidMethod__anon99d09a770111::DocTests136 static void nativeVoidMethod(
137 // All non-static methods receive "this" as a first argument.
138 alias_ref<DocTests> thiz) {
139 // Make sure we got the right object.
140 assert(thiz->toString() == "instance of DocTests");
141 thiz->callVoidMethod();
142 }
staticNativeVoidMethod__anon99d09a770111::DocTests143 static void staticNativeVoidMethod(
144 // All static methods receive the class as a first argument.
145 alias_ref<JClass> clazz) {
146 assert(clazz->toString() == "class com.facebook.jni.DocTests");
147 DocTests::callStaticVoidMethod();
148 }
149 // END
150
151 // SECTION primitives
152 static jlong
addSomeNumbers__anon99d09a770111::DocTests153 addSomeNumbers(alias_ref<JClass> clazz, jbyte b, jshort s, jint i) {
154 static const auto doubler = clazz->getStaticMethod<jlong(jint)>("doubler");
155 jlong l = doubler(clazz, 4);
156 return b + s + i + l;
157 }
158
159 /* MARKDOWN
160 Argument and return types can be in either JNI style or C++ style.
161
162 | Java type | JNI types |
163 | --- | --- |
164 | `boolean` | `jboolean`, `bool` |
165 | `byte` | `jbyte`, `int8_t` |
166 | `char` | `jchar` |
167 | `short` | `jshort`, `short`, `int16_t` |
168 | `int` | `jint`, `int`, `int32_t` |
169 | `long` | `jlong`, `int64_t` |
170 | `float` | `jfloat`, `float` |
171 | `double` | `jdouble`, `double` |
172 // END
173 */
174
175 // SECTION strings
fancyCat__anon99d09a770111::DocTests176 static std::string fancyCat(
177 alias_ref<JClass> clazz,
178 // Native methods can receive strings as JString (direct JNI reference)
179 // ...
180 alias_ref<JString> s1,
181 // or as std::string (converted to real UTF-8).
182 std::string s2) {
183 // Convert JString to std::string.
184 std::string result = s1->toStdString();
185 // Java methods can receive and return JString ...
186 static const auto doubler_java =
187 clazz->getStaticMethod<JString(JString)>("doubler");
188 result += doubler_java(clazz, *s1)->toStdString();
189 // and also std::string (converted from real UTF-8).
190 static const auto doubler_std =
191 clazz->getStaticMethod<std::string(std::string)>("doubler");
192 result += doubler_std(clazz, s2)->toStdString();
193 // They can also receive const char*, but not return it.
194 static const auto doubler_char =
195 clazz->getStaticMethod<std::string(const char*)>("doubler");
196 result += doubler_char(clazz, s2.c_str())->toStdString();
197 // All 3 formats can be returned (std::string shown here, const char*
198 // below).
199 return result;
200 }
201
getCString__anon99d09a770111::DocTests202 static const char* getCString(alias_ref<JClass>) {
203 // This string is converted to JString *after* getCString returns.
204 // Watch your memory lifetimes.
205 return "Watch your memory.";
206 }
207 // END
208
209 // SECTION primitive_arrays
primitiveArrays__anon99d09a770111::DocTests210 static local_ref<JArrayInt> primitiveArrays(
211 alias_ref<JClass> clazz,
212 // JArrayX is available for all primitives.
213 alias_ref<JArrayInt> arr) {
214 size_t size = arr->size();
215 std::vector<jint> buffer(size + 1L);
216 // Copy elements into native memory.
217 arr->getRegion(0, size, buffer.data());
218 // Copy elements into fresh memory (returns unique_ptr<int[]>).
219 auto elements = arr->getRegion(0, size);
220 // Pin can eliminate the need for a copy.
221 {
222 auto pin = arr->pin();
223 for (size_t i = 0; i < pin.size(); i++) {
224 // Can read and/or write pin[i].
225 buffer[size] += pin[i];
226 }
227 }
228 // Allocating a new array and copying data in.
229 // (Data can also be assigned by writing to a pin.)
230 auto ret = JArrayInt::newArray(size + 1);
231 ret->setRegion(0, size + 1, buffer.data());
232 return ret;
233 }
234 // END
235
236 // SECTION class_arrays
classArrays__anon99d09a770111::DocTests237 static local_ref<JArrayClass<JString>> classArrays(
238 alias_ref<JClass> clazz,
239 alias_ref<JArrayClass<JDataHolder>> arr) {
240 size_t size = arr->size();
241 local_ref<JArrayClass<JString>> ret = JArrayClass<JString>::newArray(size);
242 for (int i = 0; i < size; ++i) {
243 local_ref<JString> str = arr->getElement(i)->getStr();
244 ret->setElement(i, *str);
245 }
246 return ret;
247 }
248 // END
249
250 // SECTION references
251 /* MARKDOWN
252
253 ### `alias_ref<JFoo>`
254 `alias_ref` is a non-owning reference, like a bare pointer.
255 It is used almost exclusively for function arguments.
256
257 ### `local_ref<JFoo>`
258 `local_ref` is a ref-counted thread-specific pointer that is invalidated upon
259 returning to Java. For variables used within a function, use `local_ref`. Most
260 functions should return `local_ref` (and let the caller convert to a
261 `global_ref` if necessary).
262
263 ### `global_ref<JFoo>`
264 `global_ref` is a ref-counted pointer.
265 Use this for storing a reference to a Java object that may
266 outlive the current call from Java into C++
267 (e.g. class member fields are usually global refs).
268 You can create a new `global_ref` (from an `alias_ref`/`local_ref`) by calling
269 `make_global`.
270 // END
271 */
272 // SECTION references
convertReferences__anon99d09a770111::DocTests273 static local_ref<JObject> convertReferences(
274 alias_ref<JClass> clazz,
275 alias_ref<JMyDerivedClass> derived) {
276 local_ref<JMyDerivedClass> local_derived = make_local(derived);
277 global_ref<JMyDerivedClass> global_derived = make_global(derived);
278 // Store global_derived somewhere.
279 return local_derived;
280 }
281 // END
282
283 // SECTION inheritance
castReferences__anon99d09a770111::DocTests284 static void castReferences(
285 alias_ref<JClass> clazz,
286 alias_ref<JMyBaseClass> base) {
287 // Just like raw pointers, upcasting is implicit.
288 alias_ref<JObject> obj = base;
289 // static_ref_cast is like C++ static_cast. No runtime checking is done.
290 alias_ref<JMyDerivedClass> derived_1 =
291 static_ref_cast<JMyDerivedClass>(base);
292 // dynamic_ref_cast is like C++ dynamic_cast.
293 // It will check that the runtime Java type is actually derived from the
294 // target type.
295 try {
296 alias_ref<JMyDerivedClass> derived_2 =
297 dynamic_ref_cast<JMyDerivedClass>(base);
298 (void)derived_2;
299 } catch (const JniException& exn) {
300 // Throws ClassCastException if the cast fails.
301 throw;
302 }
303 // END
304 // Supress warnings.
305 (void)obj;
306 (void)derived_1;
307 }
308
callGetAndSetFields__anon99d09a770111::DocTests309 static void callGetAndSetFields(
310 alias_ref<JClass> clazz,
311 alias_ref<JDataHolder> data) {
312 data->getAndSetFields();
313 }
314
315 // SECTION jobject_jclass
showJObject__anon99d09a770111::DocTests316 static std::string showJObject(
317 alias_ref<JClass> clazz,
318 // JObject is the base class of all fbjni types. It corresponds to
319 // java.lang.Object.
320 alias_ref<JObject> obj,
321 alias_ref<JDataHolder> data) {
322 local_ref<JClass> objClass = obj->getClass();
323 local_ref<JClass> dataClass = data->getClass();
324 local_ref<JClass> parent = dataClass->getSuperclass();
325 assert(isSameObject(parent, objClass));
326 assert(data->isInstanceOf(parent));
327 assert(objClass->isAssignableFrom(clazz));
328 std::string str = "data=";
329 {
330 // Acquires the object lock until this object goes out of scope.
331 auto lock = data->lock();
332 // Calls Object.toString and converts to std::string.
333 str += data->toString();
334 }
335 // All JavaClass types have a `javaobject` typedef, which is their raw JNI
336 // type.
337 static_assert(std::is_same<JObject::javaobject, jobject>::value, "");
338 static_assert(std::is_same<JClass::javaobject, jclass>::value, "");
339 static_assert(!std::is_same<JDataHolder::javaobject, jobject>::value, "");
340 static_assert(
341 std::is_convertible<JDataHolder::javaobject, jobject>::value, "");
342 return str;
343 }
344 // END
345
346 // SECTION simple_exceptions
catchAndThrow__anon99d09a770111::DocTests347 static void catchAndThrow(alias_ref<JClass> clazz) {
348 try {
349 clazz->getStaticMethod<void()>("doesNotExist");
350 assert(!"Exception wasn't thrown.");
351 } catch (JniException& exn) {
352 // JniException extends std::exception, so "catch (std::exception& exn)"
353 // also works.
354 local_ref<JThrowable> underlying = exn.getThrowable();
355 const char* msg = exn.what();
356 // Throwing exceptions from C++ is fine.
357 // They will be translated to an appropriate Java exception type.
358 throw std::runtime_error(std::string() + "Caught '" + msg + "'");
359 }
360 }
361 // END
362
363 // SECTION boxed
scaleUp__anon99d09a770111::DocTests364 static local_ref<JDouble> scaleUp(
365 alias_ref<JClass> clazz,
366 alias_ref<JInteger> number) {
367 // Boxed types exist for all Java primitive types.
368 // Unbox with ->value() or ->intValue.
369 jint unboxed = number->value();
370 jdouble scaled = unboxed * 1.5;
371 // Box with autobox() or JDouble::valueOf.
372 local_ref<JDouble> ret = autobox(scaled);
373 return ret;
374 }
375 // END
376
377 // SECTION iterables
concatMatches__anon99d09a770111::DocTests378 static std::string concatMatches(
379 alias_ref<JClass> clazz,
380 // Note that generic types are *not* checked against Java declarations.
381 alias_ref<JList<JInteger>> values,
382 alias_ref<JMap<JString, JInteger>> names) {
383 int sum = 0;
384 std::string ret;
385 // Iterator and Iterable support C++ iteration.
386 // Collection, List, and Set support iteration and ->size().
387 for (const auto& elem : *values) {
388 sum += elem->value();
389 }
390 // Maps iterate like C++ maps.
391 for (const auto& entry : *names) {
392 if (entry.second->value() == sum) {
393 ret += entry.first->toStdString();
394 }
395 }
396 // This works if you build with C++17.
397 // for (const auto& [key, value] : *names) {
398 return ret;
399 }
400 // END
401
402 // SECTION collections
buildCollections__anon99d09a770111::DocTests403 static local_ref<JMap<JString, JList<JInteger>>> buildCollections(
404 alias_ref<JClass> clazz) {
405 auto primes = JArrayList<JInteger>::create();
406 primes->add(autobox(2));
407 primes->add(autobox(3));
408 auto wrapper = JHashMap<JString, JList<JInteger>>::create();
409 wrapper->put(make_jstring("primes"), primes);
410 return wrapper;
411 }
412 // END
413
414 // SECTION byte_buffer
transformBuffer__anon99d09a770111::DocTests415 static local_ref<JByteBuffer> transformBuffer(
416 alias_ref<JClass> clazz,
417 alias_ref<JByteBuffer> data) {
418 // Direct ByteBuffers are an efficient way to transfer bulk data between
419 // Java and C++.
420 if (!data->isDirect()) {
421 throw std::runtime_error("Argument is not a direct buffer.");
422 }
423 // Transform data into a local buffer.
424 std::vector<uint8_t> buffer(data->getDirectSize());
425 uint8_t* raw_data = data->getDirectBytes();
426 for (size_t i = 0; i < buffer.size(); ++i) {
427 buffer[i] = raw_data[i] + 1;
428 }
429 // Wrap our data in a buffer and pass to Java.
430 // Note that the buffer *directly* references our memory.
431 local_ref<JByteBuffer> wrapper =
432 JByteBuffer::wrapBytes(buffer.data(), buffer.size());
433 static const auto receiver =
434 clazz->getStaticMethod<void(alias_ref<JByteBuffer>)>("receiveBuffer");
435 receiver(clazz, wrapper);
436 // We can create a new buffer that owns its own memory and safely return it.
437 local_ref<JByteBuffer> ret = JByteBuffer::allocateDirect(buffer.size());
438 std::memcpy(ret->getDirectBytes(), buffer.data(), buffer.size());
439 return ret;
440 }
441 // END
442
443 public:
444 // SECTION registration
445 // NOTE: The name of this method doesn't matter.
registerNatives__anon99d09a770111::DocTests446 static void registerNatives() {
447 javaClassStatic()->registerNatives({
448 makeNativeMethod("nativeVoidMethod", DocTests::nativeVoidMethod),
449 makeNativeMethod(
450 "staticNativeVoidMethod", DocTests::staticNativeVoidMethod),
451 // END
452 makeNativeMethod("addSomeNumbers", DocTests::addSomeNumbers),
453 makeNativeMethod("fancyCat", DocTests::fancyCat),
454 makeNativeMethod("getCString", DocTests::getCString),
455 makeNativeMethod("primitiveArrays", DocTests::primitiveArrays),
456 makeNativeMethod("convertReferences", DocTests::convertReferences),
457 makeNativeMethod("castReferences", DocTests::castReferences),
458 makeNativeMethod("runConstructor", DocTests::runConstructor),
459 makeNativeMethod("callGetAndSetFields", DocTests::callGetAndSetFields),
460 makeNativeMethod("showJObject", DocTests::showJObject),
461 makeNativeMethod("catchAndThrow", DocTests::catchAndThrow),
462 makeNativeMethod("scaleUp", DocTests::scaleUp),
463 makeNativeMethod("concatMatches", DocTests::concatMatches),
464 makeNativeMethod("buildCollections", DocTests::buildCollections),
465 makeNativeMethod("transformBuffer", DocTests::transformBuffer),
466 });
467 }
468 };
469
470 } // Anonymous namespace
471
472 // SECTION registration
JNI_OnLoad(JavaVM * vm,void *)473 jint JNI_OnLoad(JavaVM* vm, void*) {
474 return facebook::jni::initialize(vm, [] { DocTests::registerNatives(); });
475 }
476 // END
477