The process begins by defining a Java class containing methods declared with the native keyword. After compiling the Java source file, the javah utility generates a C/C++ header file. Java declaration: ```
public native void showGreeting();
Generated JNI function signature: ```
JNIEXPORT void JNICALL Java_Greeter_showGreeting(JNIEnv* env, jobject obj);
Breaking down the function signature: - JNIEXPORT: Marks the function as exported for JNI access.
- JNICALL: Specifies the JNI calling convention.
- Java: Prefix indicating the Java language origin.
- Greeter: The containing Java class name.
- showGreeting: The actual method name.
- JNIEnv*: Pointer providing access to JNI utility functions.
- jobject: Handle to the calling Java object instance, akin to
this.
Type Conversion
Transferring string data between Java and C++ requires explicit conversion. A common practice involves wrapping these conversions in helper classes for automatic resource management. Converting jstring to char*``` const char* GetStringUTFChars(jstring str, jboolean* isCopy); void ReleaseStringUTFChars(jstring str, const char* chars);
The `GetStringUTFChars` function returns a pointer to a modified UTF-8 string. The `isCopy` parameter indicates whether the JVM created a copy of the string data. Once processing is complete, `ReleaseStringUTFChars` notifies the JVM that the native code no longer needs the string data. **Converting char\* to jstring**```
jstring NewStringUTF(const char* utf);
This function allocates a new Java String object containing a copy of the provided UTF-8 characters. When returning a string to Java, this method is essential. The DeleteLocalRef function can release the local reference when no longer needed. ### Calling C++ from Java
When Java invokes a native method, execution transfers to the corresponding JNI function. Implementing the native function with calls to existing C++ logic completes the inetgration. ### Calling Java from C++
To invoke Java methods from native code, several JNI constructs are required: - jmethodID: Stores the identifier for a Java method.
- CallVoidMethod: Invokes a method returning void.
- AttachCurrentThread: Binds a native thread to the JVM.
- DetachCurrentThread: Releases a native thread from the JVM.
When working with threads not spawned by the JVM, attaching to the current thread is mandatory: ``` JNIEnv* env; g_javaVM->GetEnv((void**)&env, JNI_VERSION_1_6);
bool wasAttached = false; if (env == nullptr) { int result = g_javaVM->AttachCurrentThread(&env, nullptr); if (result != JNI_OK) { return; } wasAttached = true; }
jstring jstrParam = env->NewStringUTF(paramValue.c_str()); env->CallVoidMethod(callbackObj, methodId, jstrParam);
if (wasAttached) { g_javaVM->DetachCurrentThread(); }
Before invoking a method, the native layer must cache references to the Java object and method ID: ```
callbackObject = env->NewGlobalRef(javaCallback);
callbackClass = reinterpret_cast<jclass>(env->NewGlobalRef(env->GetObjectClass(callbackObject)));
callbackMethodId = env->GetMethodID(callbackClass, "onDataUpdate", "(Ljava/lang/String;I)V");
Using NewGlobalRef ensures the references persist across JNI calls, preventing premature garbage collection.