I have created my first native call in Java with the Android SDK today.
I found a few examples but there aren't consistent with the function head.
I used always
JNIEXPORT void JNICALL Java_com_test_Calculator_calcFileSha1
(JNIEnv *, jclass, jstring);
but I have seen
JNIEXPORT void JNICALL Java_com_test_Calculator_calcFileSha1
(JNIEnv *, jobject, jstring);
Belonging to the heads are different was to get the class of the caller.
But what is the preferred way?
From the C++ code I want to call a java method. I found the JNI documentation (Calling Instance Methods).
But I don't know what the first parameter (object) should be.
I tried to give the class instance which I get from the call of the native method, which fails with an AbstractMethodError.
Fixed source code:
public class TestCalc extends Activity {
static {
System.loadLibrary("Test");
}
private void setFilesize(long size) {
}
}
Native Library:
// header
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT void JNICALL Java_com_test_TestCalc_calcFilesize
(JNIEnv *, jobject, jstring);
void setFilesize(const INT_64 size);
#ifdef __cplusplus
}
#endif
#endif
// code
JNIEnv * callEnv;
jobject callObj;
JNIEXPORT void JNICALL Java_com_test_SHA1Calc_calcFileSha1
(JNIEnv * env, jobject jobj, jstring file)
{
callEnv = env;
callObj = jobj;
[...]
}
void setFilesize(const INT_64 size) {
jmethodID mid;
jclass cls;
cls=callEnv->FindClass("com/test/TestCalc");
mid=callEnv->GetMethodID(cls, "setFilesize", "(J)V");
if (mid == 0) {
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NDK:LC: [%s]", "Cannot find method setFilesize");
return;
}
callEnv->ExceptionClear();
callEnv->CallVoidMethod(callObj, mid, size);
if(callEnv->ExceptionOccurred()) {
callEnv->ExceptionDescribe();
callEnv->ExceptionClear();
}
}
Thanks for any advices.
Call[type]Method is only for private methods and constructors. (Calling Instance Methods) When you call a public method, you will get an AbstractMethodError.
jobject could be any java object while jclass is supposed to be an object representing java.lang.Class. In C API, jobject and jclass is the same thing while C++ API is trying to enforce type safety and declares those as different types where jclass inherits jobject. The second argument is supposed to represent a class, so jclass is preferred. Even if in C it doesn't matter, it can hint the developer or even save you time if you ever decide to switch to C++. Read more about JNI types here.
Related
I am looking at the MoreTeapots sample:
https://github.com/googlesamples/android-ndk/tree/master/MoreTeapots
and I see that is easy to call a Java method from the C++ code using android_native_app_glue.c by using this method:
jclass clazz = jni->GetObjectClass(app_->activity->clazz);
jmethodID methodID = jni->GetMethodID(clazz, "updateCamera", "(FFF)V");
jni->CallVoidMethod(app_->activity->clazz, methodID, x, y, z);
yet, can I do the reverse in that sample?
Declare a function into java code and that calls the native code. Notice that sample uses "glue"; therefore, the answer is not just these steps:
https://developer.android.com/ndk/samples/sample_hellojni.html
it is different. I see no hooks from java to glue, only from glue to java. I believe glue is running in its own thread.
any leads?
thx!
you could declare a callback in your native code
JNIEXPORT void JNICALL Java_package_Activity_nativeCallback(JNIEnv* jenv, jobject obj);
And then from Java code, just declared as native
native void nativeCallback();
Remember that this execute in main thread, and you should send a command to the background thread to process.
Best regards.
That does not work with "glue". If you look at the sample I mentioned it is different. It uses glue. So I added to the java code:
public native void nativeCallback();
then to native:
extern "C" {
JNIEXPORT void JNICALL Java_com_sample_moreteapots_MoreTeapotsNativeActivity_nativeCallback(JNIEnv *env, jobject obj);
};
JNIEXPORT void JNICALL Java_com_sample_moreteapots_MoreTeapotsNativeActivity_nativeCallback(JNIEnv *env, jobject obj) {
int i = 0;
return;
}
it compiled and linked. But when I tried to invoke the method from Java I get this:
FATAL EXCEPTION: main
Process: com.sample.moreteapots, PID: 31733
java.lang.UnsatisfiedLinkError: No implementation found for void com.sample.moreteapots.MoreTeapotsNativeActivity.nativeCallback()
(tried Java_com_sample_moreteapots_MoreTeapotsNativeActivity_nativeCallback
and Java_com_sample_moreteapots_MoreTeapotsNativeActivity_nativeCallback__)
In my function, i am trying to get the size of a String array that has been passed to a C++ class through JNI, but i keep getting the error: "jobjectarray has not been declared". here is my C++:
int targetCount = 0;
JNIEXPORT void JNICALL
Java_com_example_processArray(JNIEnv *env, jobject obj, jobjectarray targetsArray){
targetCount = env->GetArrayLenght(env, targetsArray);
}
Not sure how to go about this because i thought it uses the Array already passed directly. Solutions greatly appreciated as always. Thanks
Use javah to create your jni signatures. jobjectarray is not correct, the correct syntax is jobjectArray
See Documentation
I try to compiled this:
#include <stdio.h>
#include <jni.h>
#include "callJNITest.h"
JNIEXPORT jint JNICALL Java_callJNITest_displayHelloWorld
(JNIEnv env, jclass jcls) {
printf("Hello World!");
return 1;
}
But I always get these errors:
../HelloWorldJNI.c:5:24: error: conflicting types for ‘Java_callJNITest_displayHelloWorld’
../callJNITest.h:15:24: note: previous declaration of ‘Java_callJNITest_displayHelloWorld’ was here
make: * [HelloWorldJNI.o] Error 1
The error happened also in this case:
JNIEXPORT void JNICALL Java_callJNITest_displayHelloWorld
(JNIEnv env, jclass jcls) {
printf("Hello World!");
return;
}
but
JNIEXPORT JNICALL Java_callJNITest_displayHelloWorld
(JNIEnv env, jclass jcls) {
printf("Hello World!");
return;
}
works fine.
My question is how return types should be declared in JNI? Thanks!
Creating an answer from my comment since it solved the OP's problem.
The header file contains a declaration where the type of the first argument of Java_callJNITest_displayHelloWorldis a JNIEnv*, while in the cpp file it has the type JNIEnv.
The fix is to change the type of env in the cpp file to JNIEnv* so that it matches the declaration in the header file.
I have a working implementation of NDK library and corresponding Java-class. But I am not able to add overloaded method to that class. Currently my class contains:
package com.package;
public class MyClass
{
public static native String getFileName();
static
{
System.loadLibrary("mylib");
}
}
My jniwrappers.cpp file has the following declaration:
JNIEXPORT jstring JNICALL
Java_com_package_MyClass_getFileName(_JNIEnv* env, jobject thiz);
Up to this point everything is working fine. But next I modify my class:
package com.package;
public class MyClass
{
public static native String getFileName();
public static native String getFileName(int index);
...
}
And add to jniwrappers.cpp another declaration:
JNIEXPORT jstring JNICALL
Java_com_package_MyClass_getFileName__I(_JNIEnv* env, jobject thiz, jint index);
It compiles fine, Android application starts, does not get UnsatisfiedLinkError but when it calls the second method with the argument the first C++ function is being called but not the second. I have other methods with arguments in that class but none of them are overloaded so their respective JNI signatures do not contain arguments.
So, what am I doing wrong?
You should use javah tool to generate those signatures.
To use it, build the class file where you have your native function. You'll get a class file.
Run javah -jni com.organisation.class_with_native_func, it'll generate a header file for you.
It's far cleaner than editing it yourself.
You have to add a __ onto the end of the original getFileName function now that it is overloaded. Your 2 C function prototypes should now look like this:
JNIEXPORT jstring JNICALL Java_com_package_MyClass_getFileName__
(JNIEnv *, jclass);
JNIEXPORT jstring JNICALL Java_com_package_MyClass_getFileName__I
(JNIEnv *, jclass, jint);
I am trying to compile the following for the android ndk
#include <jni.h>
#include <string.h>
extern "C" {
JNIEXPORT jstring JNICALL Java_com_knucklegames_helloCpp_testFunction(JNIEnv * env, jobject obj);
};
JNIEXPORT jstring JNICALL Java_com_knucklegames_helloCpp_testFunction(JNIEnv *env, jobject obj) {
return env->NewStringUTF(env, "Hello from native code!");
}
but it is giving the following error
Compile++ thumb: helloCpp <= /cygdrive/c/workspace/helloCpp/jni/main.cpp
/cygdrive/c/workspace/helloCpp/jni/main.cpp: In function '_jstring* Java_com_knucklegames_hello
Cpp_testFunction(JNIEnv*, _jobject*)':
/cygdrive/c/workspace/helloCpp/jni/main.cpp:10: error: no matching function for call to '_JNIEn
v::NewStringUTF(JNIEnv*&, const char [24])'
/cygdrive/d/android/android-ndk-r4b/build/platforms/android-8/arch-arm/usr/include/jni.h:839: note: candidates
are: _jstring* _JNIEnv::NewStringUTF(const char*)
make: *** [/cygdrive/c/workspace/helloCpp/obj/local/armeabi/objs/helloCpp/main.o] Error 1
The NewStringUTF function only takes one argument, a c-string:
env->NewStringUTF("Hello from native code!");
There is a C version that goes like this:
NewStringUTF(env, "Hello from native code!");
But you are obviously using the C++ version.