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