Calling JAVA class member from Native C/C++ code - android

I'm writing an OpenGL C/C++ application which i'm porting to Android through Android NDK, JNI support. I'm having difficulties executing code from JAVA callback signaled from native.
Here is the code:
class OpenGLSurfaceView extends GLSurfaceView
{
…
public OpenGLSurfaceView(Context context, int deviceWidth, int deviceHeight)
{
super(context);
nativeObj = new NativeLib();
mRenderer = new OpenGLRenderer(context, nativeObj, deviceWidth, deviceHeight);
setRenderer(mRenderer);
setRenderMode(RENDERMODE_WHEN_DIRTY);
}
…
private void CallBack()
{
// force redraw
requestRender();
}
}
class OpenGLRenderer implements GLSurfaceView.Renderer
{
…
public void onSurfaceCreated(GL10 gl, EGLConfig config)
{
nativeObj.init(…);
nativeObj.cachejavaobject(JNIEnv *env, jobject obj); // for caching obj on native side
}
public void onSurfaceChanged(GL10 gl, int w, int h)
{
}
public void onDrawFrame(GL10 gl)
{
nativeObj.draw(…);
}
}
And in native code i have a function texturesLoaded() that is signaled when some textures are loaded completely on another thread and i need to force a refresh from nativeLib.draw(…) on the JAVA side. Here is how i do it :
I cache the JavaVM, jClass, jMethodID on JNI_OnLoad, and gJObjectCached
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)
{
gJVM = jvm; // cache the JavaVM pointer
LogNativeToAndroidExt("JNILOAD!!!!!!!!!!");
JNIEnv *env;
int status = gJVM->GetEnv((void **)&env, JNI_VERSION_1_6);
if(status < 0)
{
LogNativeToAndroidExt("Failed to get JNI environment, assuming native thread");
status = gJVM->AttachCurrentThread(&env, NULL);
if(status < 0)
{
LogNativeToAndroidExt("callback_handler: failed to attach current thread");
return JNI_ERR;
}
}
gJClass = env->FindClass("com/android/newlineactivity/NewLineGLSurfaceView");
if(gJClass == NULL)
{
LogNativeToAndroidExt("Can't find Java class.");
return JNI_ERR;
}
gJMethodID = env->GetMethodID(gJClass, "callback", "()V");
if(gJMethodID == NULL)
{
LogNativeToAndroidExt("Can't find Java method void callback()");
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
JNIEXPORT void Java_com_android_OpenGL_NativeLib_cachejavaobject(JNIEnv* env, jobject obj)
{
// cache the java object
gJObjectCached = obj;
...
}
and then on texturesLoaded() i do :
void texturesLoaded()
{
// Cannot share a JNIEnv between threads. You should share the JavaVM, and use JavaVM->GetEnv to discover the thread's JNIEnv
JNIEnv *env = NULL;
int status = gJVM->GetEnv((void **)&env, JNI_VERSION_1_6);
if(status < 0)
{
LogNativeToAndroidExt("callback_handler: failed to get JNI environment, assuming native thread");
status = gJVM->AttachCurrentThread(&env, NULL);
if(status < 0)
{
LogNativeToAndroidExt("callback_handler: failed to attach current thread");
return;
}
}
LogNativeToAndroidExt("Calling JAVA method from NATIVE C/C++");
env->CallVoidMethod(gJObjectCached, gJMethodID);
LogNativeToAndroidExt("DONE!!!");
}
Result… from native side i get the class, i get the method, method gets called, but when it reaches/calls requestRender() inside(or trying to access any other member method of GLSurfaceView it crashes!)
I cannot try with CallStaticVoidMethod(gJClass, gjMethodID); because then i don't have access to requestRender();
Any ideas or opinions, maybe i'm doing something wrong here.
Thanks

You need to create global references to the class/object that you stash away. The references you're saving are local references, which can't be shared across threads and disappear when the runtime cleans up the JNI local reference stack.
Check out the Sun/Oracle documentation for global and local references, and check out JNI methods JNIEnv::NewGlobalRef and JNIEnv::DeleteGlobalRef.
gJClass = env->NewGlobalRef(env->FindClass( ... ));
gJObjectCached = env->NewGlobalRef(obj);
(Edit: Turns out you don't need global references for method IDs.)

Related

on android how to execute a c++ function from a worker a thread in the main thread and wait for the execution to complete

thread 1 is main thread.
I have a c++ code that executes in thread 2 ( my worker thread that I have spawned ).
I would like to execute a native function in main thread and wait for the result
printf("I am on thread : %s", getthreadid());
int ret = executeOnMainWait(mynativefunc1("hello"));
printf("ret : %d", ret);
printf("I am on thread : %s", getthreadid());
bool b = executeOnMainWait(mynativefunc2(4, 5));
printf("b : %d", b);
int mynativefunc1(char* param) {
printf("mynativefunc1 I am on thread : %s", getthreadid());
if(strcmp(param, "hello")) {
return 1;
}
return 2;
}
bool mynativefunc2(int val1, int val2) {
printf("mynativefunc2 I am on thread : %s", getthreadid());
return (val1 + val2) == 5;
}
so this code should display :
I am on thread 2
mynativefunc1 I am on thread 1
ret : 1
I am on thread 2
mynativefunc2 I am on thread 1
b : true
I think we need to go to the java world through jni and use the handler and post something to main thread then wait it is complete, but the problem I don't know how to pass a function pointer directly with its own parameters. this example just show 2 native functions, but in reality I have 20.
many thanks
How to do (some parts are pseudo-code):
Java code:
public native void startSecondThread();
public native void fakeCallbackFromJava();
public native void fakeCallbackFromJava2();
public void blockingMethod(int chooseCallbackMethod, #Nullable Object obj, #Nullable String string) {
final Object syncObj = new Object();
runOnUiThread(new Runnable() {
#Override
public void run() {
...run Java code here in UiThread...
...here you can use OBJ and STRING arguments...
//call callbacks according to Argument
if (chooseCallbackMethod == 1) fakeCallbackFromJava();
else if (chooseCallbackMethod == 2) fakeCallbackFromJava2();
syncObj.notify();
}
});
syncObj.wait();
//we arrive HERE only after "syncObj.notify()" is called
}
C++ Thread code:
void foo() {
JNIEnv *env;
jvm->AttachCurrentThread((void **)&env, NULL);
..."env" is currently synced object that could be used for Java calls...
...all following rows depends of what you want to call in Java...
jclass class = env->GetObjectClass(...);
jmethod method = env->GetMethodID(class, "blockingMethod", "ILjava/lang/Object;Ljava/lang/String;");
//first call
env->CallVoidMethod(method, 1, null, env->NewStringUTF("hello"));
//(you will arrive HERE only after "blockingmethod(1,null," hello")" is finished from Java UiThread)
//second call
env->CallVoidMethod(method, 2, null, null);
//finishing second thread
jvm->DetachCurrentThread();
}
C++ starting Thread:
#include <thread>
JavaVM *jvm;
JNIEXPORT JNICALL void Java_com_your_package_startSecondThread(JNIEnv* env, jclass clazz) {
env->GetJavaVM(&jvm);
std::thread first (foo);
first.join();
}
C++ Callback:
JNIEXPORT JNICALL void Java_com_your_package_fakeCallbackFromJava(JNIEnv* env, jclass clazz) {
...code called from Java MainThread/UiThread as CALLBACK for arguments: 1,null,"hello"...
}
JNIEXPORT JNICALL void Java_com_your_package_fakeCallbackFromJava2(JNIEnv* env, jclass clazz) {
...code called from Java MainThread/UiThread as CALLBACK for arguments: 2,null,null...
}

How to properly and simply use callbacks in JNI NDK Android

I am trying to have Java call a C function in Android and some caveats that I am met with is the documentation for callbacks in general seem very unclear and most answers on SO are just talking about theory and posting broken links with no actual code. So please post code in your answer.
I am trying to create a joystick in java and have it send input to the NDK to perform actions.
if I can just have Java call a C function and pass a int to it ,
that would be good. but I think that wont work because C in running
it's own main loop.
perhaps I need to use C to call a java
function to get controller state and execute code from there.
I did manage to find some code Here but it seems the code while it does compile by itself, but doesn't appear to work when moved to modern code .
How should I implement call backs in a Java C relationship on Android? It seems that from the code example above that it is C that is calling Java functions and these need to be used like getters functions.
Here is some code that I have and I appear to be missing somethings because it is unclear to me how I should have these call backs performed. I will mark this code with function name and where is it located, if java or C++ (CPP) .
I think there are two ways that I would like this to work.
My Javafunction that I want to call
public int Cat(){
Log.e("JniHandler", "CAT WAS CALLED!!!");
return 333;
}
Structure information CPP . Not sure if it is needed.
#include <string.h>
#include <inttypes.h>
#include <pthread.h>
#include <jni.h>
#include <android/log.h>
#include <assert.h>
typedef struct tick_context {
JavaVM *javaVM;
jclass jniHelperClz;
jobject jniHelperObj;
jclass mainActivityClz;
jobject mainActivityObj;
pthread_mutex_t lock;
int done;
} TickContext;
TickContext g_ctx;
This section works. It looks that as soon as the JVM is launched, this will initialize.
JNI_OnLoad CPP
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
memset(&g_ctx, 0, sizeof(g_ctx));
SDL_Log(" DEBUG -- JNI INIT!!!");
g_ctx.javaVM = vm;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR; // JNI version not supported.
}
jclass clz = env->FindClass("org/foo/bar/mainactivity");
g_ctx.jniHelperClz = (jclass)env->NewGlobalRef(clz); // hack
jmethodID jniHelperCtor = env->GetMethodID(g_ctx.jniHelperClz,"<init>", "()V");
jobject handler = env->NewObject(g_ctx.jniHelperClz, jniHelperCtor);
g_ctx.jniHelperObj = env->NewGlobalRef(handler);
//queryRuntimeInfo(env, g_ctx.jniHelperObj); // This when getting version returns null.
g_ctx.done = 0;
g_ctx.mainActivityObj = NULL;
return JNI_VERSION_1_6;
}
queryRuntimeInfo CPP
void queryRuntimeInfo(JNIEnv *env, jobject instance) {
SDL_Log("DEBUG QUERYRUNTIME INTO");
// Find out which OS we are running on. It does not matter for this app
// just to demo how to call static functions.
// Our java JniHelper class id and instance are initialized when this
// shared lib got loaded, we just directly use them
// static function does not need instance, so we just need to feed
// class and method id to JNI
jmethodID versionFunc = env->GetStaticMethodID(g_ctx.jniHelperClz, "getBuildVersion", "()Ljava/lang/String;"); // This returns null.
if (!versionFunc) {
SDL_Log("DEBUG versionFunc INTO");
//LOGE("Failed to retrieve getBuildVersion() methodID # line %d",__LINE__);
return;
}
SDL_Log("DEBUG BUILD VERSION INTO");
jstring buildVersion = (jstring)env->CallStaticObjectMethod(g_ctx.jniHelperClz, versionFunc); // hack
const char *version = env->GetStringUTFChars(buildVersion, NULL);
if (!version) {
SDL_Log("DEBUG buildVersion INTO");
//LOGE("Unable to get version string # line %d", __LINE__);
return;
}
//LOGI("Android Version - %s", version);
SDL_Log("DEBUG ANDROID VERSION %s", version);
env->ReleaseStringUTFChars(buildVersion, version);
// we are called from JNI_OnLoad, so got to release LocalRef to avoid leaking
env->DeleteLocalRef(buildVersion);
// Query available memory size from a non-static public function
// we need use an instance of JniHelper class to call JNI
jmethodID memFunc = env->GetMethodID(g_ctx.jniHelperClz, "getRuntimeMemorySize", "()J");
if (!memFunc) {
//LOGE("Failed to retrieve getRuntimeMemorySize() methodID # line %d",__LINE__);
SDL_Log("DEBUG Failed to retrieve memory size");
return;
}
jlong result = env->CallLongMethod(instance, memFunc);
//LOGI("Runtime free memory size: %" PRId64, result);
SDL_Log("DEBUG RUN TIME FREE MEMORY SIZE");
(void)result; // silence the compiler warning
}
This function isn't ever called and I am not sure who is the caller. CPP
extern "C" JNIEXPORT void JNICALL InvokeFunction(JNIEnv *env, jobject instance) {
SDL_Log("JNI CALL START TICKET");
pthread_t threadInfo_;
pthread_attr_t threadAttr_;
pthread_attr_init(&threadAttr_);
pthread_attr_setdetachstate(&threadAttr_, PTHREAD_CREATE_DETACHED);
pthread_mutex_init(&g_ctx.lock, NULL);
jclass clz = env->GetObjectClass(instance);
g_ctx.mainActivityClz = (jclass)env->NewGlobalRef(clz); // hack
g_ctx.mainActivityObj = env->NewGlobalRef(instance);
int result = pthread_create( &threadInfo_, &threadAttr_, UpdateTicks, &g_ctx);
assert(result == 0);
pthread_attr_destroy(&threadAttr_);
(void)result;
}
updateticks CPP // This code is never actually called.
void* UpdateTicks(void* context) {
SDL_Log("DEBUG -- UPDATE TICKS IS CALLED");
TickContext *pctx = (TickContext*) context;
JavaVM *javaVM = pctx->javaVM;
JNIEnv *env;
jint res = javaVM->GetEnv((void**)&env, JNI_VERSION_1_6);
if (res != JNI_OK) {
res = javaVM->AttachCurrentThread(&env, NULL);
if (JNI_OK != res) {
//LOGE("Failed to AttachCurrentThread, ErrorCode = %d", res);
SDL_Log("FAILED TO ATTACH THREAD");
return NULL;
}
}
jmethodID statusId = env->GetMethodID(pctx->jniHelperClz, "updateStatus", "(Ljava/lang/String;)V");
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread status: initializing...");
// get mainActivity updateTimer function
jmethodID timerId = env->GetMethodID(pctx->mainActivityClz, "updateTimer", "()V");
struct timeval beginTime, curTime, usedTime, leftTime;
const struct timeval kOneSecond = {
(__kernel_time_t)1,
(__kernel_suseconds_t) 0
};
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread status: start ticking ...");
while(1) {
gettimeofday(&beginTime, NULL);
pthread_mutex_lock(&pctx->lock);
int done = pctx->done;
if (pctx->done) {
pctx->done = 0;
}
pthread_mutex_unlock(&pctx->lock);
if (done) {
break;
}
env->CallVoidMethod(pctx->mainActivityObj, timerId);
gettimeofday(&curTime, NULL);
timersub(&curTime, &beginTime, &usedTime);
timersub(&kOneSecond, &usedTime, &leftTime);
struct timespec sleepTime;
sleepTime.tv_sec = leftTime.tv_sec;
sleepTime.tv_nsec = leftTime.tv_usec * 1000;
if (sleepTime.tv_sec <= 1) {
nanosleep(&sleepTime, NULL);
} else {
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread error: processing too long!");
}
}
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread status: ticking stopped");
javaVM->DetachCurrentThread();
return context;
}

JNI on Android: Callback from native with Integer parameter works, with String or Void it doesn't. Why?

I have a callback class for doing callbacks from native C++ code to Kotlin (not sure if Kotlin/Java makes a difference here, if so, is there any documentation on that?).
I have a working callback with an integer parameter, that I call from different native threads without a problem. Now I want to add a second one that sends a String, but for some reason that doesn't work.
My callback class implementation looks like this:
jclass target;
jmethodID id;
Callback::Callback(JavaVM &jvm, jobject object) : g_jvm(jvm), g_object(object) {
JNIEnv *g_env;
int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);
if (g_env != NULL) {
target = g_env->GetObjectClass(g_object);
id = g_env->GetMethodID(target, "integerCallback", "(I)V");
}
}
void Callback::call(int integerValue, const char *stringValue) {
JNIEnv *g_env;
int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);
if (getEnvStat == JNI_EDETACHED) {
if (g_jvm.AttachCurrentThread(&g_env, NULL) != 0) {
LOGD("GetEnv: Failed to attach");
}
} else if (getEnvStat == JNI_OK) {
LOGD("GetEnv: JNI_OK");
} else if (getEnvStat == JNI_EVERSION) {
LOGD("GetEnv: version not supported");
}
g_env->CallVoidMethod(g_object, id, (jint) integerValue);
}
It gets instantiated in my native-lib.cpplike this:
extern "C" {
std::unique_ptr<Callback> callback;
JavaVM *g_jvm = nullptr;
static jobject myJNIClass;
jint JNI_OnLoad(JavaVM *pJvm, void *reserved) {
g_jvm = pJvm;
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL
Java_com_my_app_common_jni_JniBridge_loadNative(JNIEnv *env, jobject instance,
jstring URI, jboolean isWaitMode) {
myJNIClass = env->NewGlobalRef(instance);
if (callback == nullptr) {
callback = std::make_unique<Callback>(*g_jvm, myJNIClass);
}
}
The callback method it talks to is in my JniBridge.kt (the threading part is probbaly irrelevant to the problem):
object JniBridge {
init {
System.loadLibrary("native-lib")
Timber.d("Native Lib loaded!")
}
fun load(fileName: String, isWaitMode: Boolean) {
loadNative(fileName, isWaitMode)
}
fun integerCallback(value: Int) {
someListener?.invoke(value)
}
private external fun loadNative(fileName: String, isWaitMode: Boolean)
}
So now if my native code triggers the call() method in my callback, my integerCallback() in JniBridge.kt gets called correctly with an integer.
But here's what I don't get: If I change my callback to send a String it doesn't work. If I change it like this:
// in JniBridge.kt
fun stringCallback(value: String) {
someListener?.invoke(value)
}
// in Callback.cpp
//getting the method
id = g_env->GetMethodID(target, "stringCallback", "(Ljava/lang/String)V");
//making the call
g_env->CallVoidMethod(g_object, id, (jstring) stringValue);
Now my app crashes with this error:
JNI DETECTED ERROR IN APPLICATION: JNI GetStringUTFChars called with pending exception java.lang.NoSuchMethodError: no non-static method "Lcom/my/app/common/jni/JniBridge;.stringCallback(Ljava/lang/String)V"
The same happens if I try calling a void method (like this: id = g_env->GetMethodID(target, "voidCallback", "(V)V");or calling one that takes two integers (like this: id = g_env->GetMethodID(target, "twoIntegerCallback", "(I;I)V");, of course with having the corresponding methods in JniBridge.kt in place.
Why does this happen and how can I fix it?
Note: For clarity I have omitted all parts of my code that I believe are not related to the problem, if something crucial is missing, please let me know and I fix it.
You're missing a semi-colon in the method signature that you pass to GetMethodID.
It should be:
id = g_env->GetMethodID(target, "stringCallback", "(Ljava/lang/String;)V");
Note the semi-colon after Ljava/lang/String.
See the part about Type Signatures in Oracle's JNI documentation.
Regarding your other variations:
A Java void function() would have the signature ()V.
A Java void function(int i, int j) would have the signature (II)V.
As a side note, you really ought to verify the return value and check for exceptions after every JNI call.

JNI: Segfaults when calling Java callbacks from C

I a Library in C that I'm leveraging for an Android application. This library has an audio stream that it occasionally flushes. When this happens it calls a write callback function of my design.
My intent is to have that C callback call a method on a specific Java Object which will handle stuff with the strem.
Currently I have code like so:
methodID compressionHandler=0;
jobject compressionHandlerClass;
int audioBufferChunkSize;
static JavaVM *gJavaVM;
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
gJavaVM = vm;
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL
Java_com_my_code_init(JNIEnv* env, jobject obj, /*classpath of the class we want to call against*/jstring compressedAudioHandlerPath, /*class instance we want to call against*/jobject callbackClass) {
......
// this is a global ref as per:
//http://stackoverflow.com/questions/14765776/jni-error-app-bug-accessed-stale-local-reference-0xbc00021-index-8-in-a-tabl
compressionHandlerClass = (*env)->NewGlobalRef(env,callbackClass);
// name of the class
const char *classLocation;
// convert jString to c String
classLocation = (*env)->GetStringUTFChars( env, compressedAudioHandlerPath , NULL ) ;
// tmp variable for holding the class location, relates to the above issue with garbage collection
jclass clazz = (*env)->FindClass(env, classLocation);
// the actual method that we want to call, this gets used in the writeCallback
compressionHandler = (*env)->GetMethodID(env, clazz, "handleCompressedAudio", "([B)V");
......
}
The callback method looks like so:
void writeCallback(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame, void *client_data) {
JNIEnv *env;
int isAttached = 0;
if ((status = (*gJavaVM)->GetEnv(gJavaVM, (void**)&env, JNI_VERSION_1_6)) < 0) {
if ((status = (*gJavaVM)->AttachCurrentThread(gJavaVM, &env, NULL)) < 0) {
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
isAttached = 1;
}
if(*env!=0 && compressionHandler!=0){
jbyteArray arr = (*env)->NewByteArray(env,bytes);
(*env)->SetByteArrayRegion(env,arr, 0, bytes, (jbyte*)buffer);
(*env)->CallVoidMethod(env,compressionHandlerClass, compressionHandler,arr);
free(arr);
free(env);
free(isAttached);
}
}
I'm getting crashes at the CallVoidMethod, that signature of which is an interface implemented by whatever object I pass in:
public interface CompressedAudioHandler {
void handleCompressedAudio(byte[] buff);
}
I suspect that I am improperly attaining/keep references to these objects, but I haven't found a great way to handle that. Any advice on how I can more correctly handle this?

JNI calling a Java Method from C++

I am having a problem with JNI, calling a method from C++ to Java.
I am trying to call a void method that takes a boolean. My java code is the following:
public void setStatus(boolean bool) {
// Do stuff...
}
public native void initialize(int defaultPort);
In my C++ code, I am making a struct to hold the env and object and pass it to a thread:
JNIEXPORT void JNICALL Java_com_device_client_HostConnection_initialize
(JNIEnv * env, jobject obj, jint port)
{
struct javaInfo* data = (struct javaInfo*) malloc(sizeof(struct javaInfo));
data->env = env;
data->javaObjHost = obj;
pthread_t pth;
pthread_create(&pth, NULL, startServer, (void *) data);
free(data);
}
In the actual function, I am trying to obtain the class and then the MethodID and then call the void method, as follows:
void *startServer(void* arg) {
struct javaInfo* data = (struct javaInfo*) arg;
JNIEnv* env = data->env;
jobject javaObjHost = data->javaObjHost;
cls = env->GetObjectClass(javaObjHost);
mid = env->GetMethodID(cls, "setStatus", "(Z)V");
if (mid == 0) {
exit(-1);
}
env->CallVoidMethod(javaObjHost, mid, true);
}
It is hard for me to debug with JNI. I have tried putting a breakpoint in Eclipse in setStatus() but it never gets called. exit() is not called as well. The programs stomps for a second or two, then continues. I am not sure what is going on.
Could anyone please help me?
Thank you very much.
You cannot pass env pointers to other threads. You need to join the thread to the JVM.
In the original thread, called GetJavaVM to obtain a JavaVM pointer:
JavaVM *vm = 0;
env->GetJavaVM(&vm);
Then in the other thread, attach the VM to that thread and get a new env pointer:
vm->AttachCurrentThread(&env, 0);

Categories

Resources