Android JNI callback seems to call a random method - java

I'm experiencing a very strange bug, which I'm thinking might be an environment issue, but I'd like to know if anyone else here is experiencing something similar.
First of all, up until now, everything worked perfectly.
I have a project which works with a jni interface.
Here's the call from my java code:
myJniWrapper.load(CallbackActivity.this); // this code is inside a Runnable
Here's the load method:
public void load(Activity activityCalledFrom) {
if (mLoaded == false) {
loadNative(mNativeHandle, activityCalledFrom); // This is being called correctly.
}
}
private native void loadNative(long nativeHandle, Object activityCalledFrom);
Here's the c++:
JNIEXPORT void JNICALL Java_com_datalogics_dlreader_jni_RMBook_loadNative
(JNIEnv *env, jobject obj, jlong handle, jobject activityCalledFrom)
{
if (handle)
{
jmethodID meth = NULL;
// Convert the long back into a pointer
RMBookNative *theBook = (RMBookNative *) handle;
// Ensure the pointer is valid
if (theBook != NULL)
{
// Get the activity that we were called from
theBook->libraryActivityObj = env->NewGlobalRef(activityCalledFrom);
// Call loadDocument to set the URL for the dpdoc::Document
theBook->loadDocument();
}
}
}
Later on, once the loading is finished, the following code is called in the C++ code:
if (libraryActivityObj != NULL)
{
// Find the loadingFinished method on the library class
meth = jenv->GetMethodID(libraryActivityCls, "loadingFinished", "()V");
// Call the method on the activity
jenv->CallVoidMethod(libraryActivityObj, meth);
}
Then, back in the same activity, I have the following method:
public void loadingFinished()
{
Log.i(getClass().getSimpleName(), "loading finished");
doStuff();
}
The strange thing is, everything is working (at least according to the logs) - and when the c++ tries to call the loadingFinished() method, it instead invokes some random public method in the activity I'm sending - or to be precise, it will call the same random method every time, but if I add another method in the activity, a different method will be called by the tag[:C++] code. It seems to randomly choose a method to call based on the total amount of methods in the activity.
I have tried: cleaning & rebuilding, restarting eclipse, restarting my Mac, restarting the device - nothing helps.
I do not understand what is happening and was hoping if anyone had any insight.
Please let me know if you need me to post more code / general info.

Related

SWIG (Java): How do I pass a struct with callback functions to C++ from an Android application?

I am developing a real-time communication app (video and audio) for Android using a WebRTC-based C++ code library. I generate a JNI bridge using SWIG to access the native code from Java. The behaviour of the call is determined via a number of callback functions that are defined in the application layer and passed in a struct to the library code. The function for passing these callbacks looks like this:
void registerCallbacks(CALL_CALLBACKS* callbacks);
where CALL_CALLBACKS is a struct containing a number of callback functions as exemplified here:
struct CALL_CALLBACKS {
// CALL_STATE is an Enum
// Called whenever the state of the call changes
void (*statusCallback)(CALL_STATE);
// FRAME is a struct representing a video frame
// Called whenever the other participant sends a video frame, typically at 30 fps
void (*frameCallback)(const FRAME*);
// ...
}
The issue is that when I let SWIG do its thing by default, the result is unusable. It generates a java type CALL_CALLBACKS which contains setters and getters for each of the callbacks, which are also types generated by SWIG. However, these types are named along the lines of SWIGTYPE_p_f_ENUM_CALL_STATUS__void and are nothing but wrappers for C pointers.
How can I write my SWIG interface file in order to pass callbacks (preferably with less nonsensical names) to the C++ library? I assume typemaps can be used somehow but I just can't wrap my head around how that can be done.
I don't think SWIG can do this by itself. Here's how I would do it in plain JNI:
First, create a Java interface that you can implement on the Java side.
interface Callbacks {
void statusCallback(int status);
void frameCallback(Frame frame);
static native void registerCallbacks(Callbacks cb);
}
Next, create functions that forward the C++ arguments to a jobject g_receiver that implements the interface:
jobject g_receiver;
void my_statusCallback(CALL_STATE s) {
if (!g_receiver) {
// Print a warning?
return;
}
JNIEnv *env = getEnv();
env->PushLocalFrame(10);
jclass cls_Callbacks = env->GetObjectClass(g_receiver);
jmethodID mid_Callbacks_statusCallback = env->GetMethodID(cls_Callbacks, "statusCallback", "(I)V");
env->CallVoidMethod(g_receiver, mid_Callbacks_statusCallback, s);
env->PopLocalFrame(nullptr);
}
void my_frameCallback(const FRAME* frame) {
if (!g_receiver) {
// Print a warning?
return;
}
JNIEnv *env = getEnv();
env->PushLocalFrame(10);
// Create a Frame object from the C++ pointer.
// See Proxy classes at http://swig.org/Doc4.0/Java.html#Java_imclass
jclass cls_Frame = env->FindClass("Frame");
jmethodID ctr_Frame = env->GetMethodID(cls_Frame, "<init>", "(JZ)V");
jobject jFrame = env->NewObject(cls_Frame, ctr_Frame, (jlong) frame, (jboolean)false);
jmethodID mid_Frame_delete = env->GetMethodID(cls_Frame, "delete", "(LFrame;)V");
jclass cls_Callbacks = env->GetObjectClass(g_receiver);
jmethodID mid_Callbacks_frameCallback = env->GetMethodID(cls_Callbacks, "frameCallback", "(LFrame;)V");
env->CallVoidMethod(g_receiver, mid_Callbacks_frameCallback, jFrame);
env->CallVoidMethod(jFrame, mid_Frame_delete); // Disconnect the Java Frame object from the C++ FRAME object.
env->PopLocalFrame(nullptr);
}
CALL_CALLBACKS global_callbacks = { my_statusCallback, my_frameCallback };
Finally, you can implement Callbacks#registerCallbacks as follows. This is a bit more tricky because you have to make sure g_receiver is either nullptr or a valid global reference:
JNIEXPORT void Java_Callbacks_registerCallbacks(JNIEnv *env, jclass cls_Callbacks, jobject receiver) {
if (g_receiver) {
env->DeleteGlobalRef(g_receiver);
}
g_receiver = receiver ? env->NewGlobalRef(receiver) : nullptr;
registerCallbacks(global_callbacks);
}
I made some assumptions:
You did not use any package when generating the code. That will affect the JNI method names and any references to classes in signatures.
I am assuming your native code runs on a different thread, so the getEnv function should use the JNI invocation API to attach the current thread as a daemon thread. You can stash the pointer in another global variable.
Since you're on Android, you can only call FindClass from the main thread. You can work around this by creating a global reference to the class in the JNI_Onload method or hack around it by adding a Class<Frame> getFrameClass() method to the Callbacks interface. Or you can take the very long way round by doing the equivalent of g_receiver.getClass().getClassLoader().findClass("Frame").
You did not specify whether the frameCallback needs to free the FRAME object itself. My code assumes it does not and disconnects the Java object so you cannot accidentally use it after the callback ends.
You did not specify whether the native registerCallbacks could be called multiple times, so I assumed it does. You could also call registerCallbacks as part of JNI_Onload.

How exactly is memory allocation taking place and how are Java and C interacting to keep track of the same object?

I have a class containing a member variable of type long and a set of native method declarations. I believe the memory for the variable is being allocated in one of the native methods and the de-allocation is attempted in the finalize() method by calling another native method destroy. I understand that finalize is deprecated but prior to finding an alternative for finalize, I need to understand how the memory allocation is happening and how Java and C maintain sync through JNI and how this particular member variable is tracked. I will attempt to explain the scenario better with code snippets:
JSQ.java
class JSQ {
protected long shdl;
protected JSQ() { log("JSQ constructor"); }
protected JSQ(String stmt, boolean isd)
throws DhSQLException
{
// calls a set of native methods
set_shdl();
setstmt(stmt, shdl);
setd(isd, shdl);
prep(shdl);
}
public void finalize()
{
destroy(shdl);
}
private native void set_shdl() throws DhSQLException;
private native void setstmt(String s, long shdl) throws DhSQLException;
private native void setd(boolean b, long shdl);
private native void prep(long shdl) throws DhSQLException;
private native void destroy(long shdl);
protected void execute() throws DhSQLException
{
parExec(shdl);
}
protected native void parExec(long shdl);
}
JSQL.cxx
#define SQ ((sq_stsm_t *)(shdl))
JNIEXPORT void JNICALL Java_com_project_package_JSQ_set_shdl
(JNIEnv *env, jobject obj_This)
{
jclass cls;
jmethodID mid;
cls = (env)->GetObjectClass (obj_This);
mid = (env)->GetMethodID (hThis,"set_JSQ_shdl","(J)V");
status_t status;
// memory allocation
sq_stsm_t * S = new sq_stsm_t(status);
if(status)
{
if (S) { delete S; }
return;
}
// I understand that we're attempting to call a Java method from a native method.
// But which method is it calling?
// Also, are we converting S from sq_stms_t type to jlong?
(env)->CallVoidMethod (obj_This,mid,(jlong) S);
return;
}
JNIEXPORT void JNICALL Java_com_project_package_JSQ_setstmt
(JNIEnv *env, jobject, jstring jstmt, jlong shdl)
{
status_t status;
// cstmt is obtained using jstmt
// Note: #define SQ ((sq_stsm_t *)(shdl))
status = SQ->SetStmt(cstmt);
return;
}
JNIEXPORT void JNICALL Java_com_project_package_JSQ_destroy
(JNIEnv *, jobject, jlong shdl)
{
delete SQ;
}
The points where I'm confused:
Why long and jlong and the conversion from the user defined type sq_stsm_t to long. I understand that Java is not aware of this user defined type. But why the choice of long?
Why the #define on SQ and how exactly does destroy delete the memory allocated in set_shdl by calling delete on SQ?
Post this, I have to find a replacement for finalize - for which I have concluded that AutoCloseable with try-with-resources is, by far, my best option. Since there are a lot of native calls involved, I'm confused as to how I would go about it. But, that is a separate question to be addressed and I don't want to go into that here. Just mentioning to set some background on the use case.
A Java long variable is guaranteed to be 64 bits wide, so it is enough to contain an address, even on a 64-bit system.
The cast to long is just there to make the C++ type system happy.
As you see, the SQ macro is used to convert this address back to a proper pointer to an sq_stsm_t object.
As for your second question: all the macro does is get rid of some typing work.
The Java part of your program will always pass the long-typed shdl variable, so the SQ macro just provides easy access to the actual sq_stsm_t object.
Finally, new and delete in C++ go together: new allocates memory and calls the constructor, delete calls the destructor and frees the memory again.
Note that "free" does not necessarily mean "give back to the OS", so your process's memory usage can remain the same after this delete operation.
As for the comment in your code: Java_com_project_package_JSQ_set_shdl tries to call the Java method boolean com.project.package.JSQ#set_JSQ_shdl(jlong), although it is not present in the code you pasted. I guess it is a simple setter for the shdl field?
As for your follow-up plans: implementing AutoCloseable is a good idea. You would need to create a native void close() method in your JSQ object which calls delete on the sq_stsm_t object stored in the shdl field and then replaces the shdl field with (jlong)nullptr.

How to pass C++ callback as a click handler of a Button in android?

I am building an android app and I need to create UI elements dynamically from JNI part and assign C++ functions as click handler.
I have defined a java function to create a button and return it.
I have a C++ function that calls the Java function and has jobject of button. Now I want to assign another C++ function as click handler to this object.
Java:
public class MainActivity extends AppCompatActivity {
...
public View control(String text) {
Button bt = new Button(this);
bt.setText(text);
((ConstraintLayout)findViewById(R.id.layout_main)).addView(bt);
return bt;
}
}
C++:
extern "C" JNIEXPORT void JNICALL Java_com_shaidin_ebellum_MainActivity_start(JNIEnv *env, jobject me)
{
jmethodID jControl = env->GetMethodID(env->GetObjectClass(me), "control", "(Ljava/lang/String;)Landroid/view/View;");
jstring jText = env->NewStringUTF("test");
jobject jView = env->CallObjectMethod(me, jControl, jText);
// How to add listener here?
env->DeleteLocalRef(jView_);
env->DeleteLocalRef(jText);
}
In this question
Android UI in C++
JNIpp library
https://github.com/DmitrySkiba/itoa-jnipp
has introduced and it does exactly what I want, but I want to implement it myself because I don't want to add that much code to my project. Also that library has a complicated implementation using nested macros that makes it difficult to learn how it works.
The process of making a Java object do a callback to C++ code is pretty much always the same; make the Java object store some kind of reference to the native object (typically a pointer), and have it pass that pointer back to the C++ code which can call the appropriate function.
A potential implementation could look like this:
MyOnClickListener.java
public class MyOnClickListener implements View.OnClickListener {
private final long nativePeer;
public MyOnClickListener(final long nativePeer) {
this.nativePeer = nativePeer;
}
#Override
public void onClick(View v) {
OnClick(nativePeer);
}
#Override
public void finalize() {
Release(nativePeer);
}
private static native void OnClick(final long peer);
private static native void Release(final long peer);
}
C++
extern "C" JNIEXPORT void Java_com_example_myapp_MyOnClickListener_OnClick(
JNIEnv *env,
jclass clazz,
jlong nativePeer) {
auto f = reinterpret_cast<std::function<void(void)>*>(nativePeer);
(*f)();
}
extern "C" JNIEXPORT void Java_com_example_myapp_MyOnClickListener_Release(
JNIEnv *env,
jclass clazz,
jlong nativePeer) {
auto f = reinterpret_cast<std::function<void(void)>*>(nativePeer);
delete f;
}
...
// Setting the OnClickListener
// We allocate this object with `new` since we need it to remain alive
// until the Java code no longer needs it. It is the responsibility of
// the Java code to ensure that the memory gets freed.
auto callback = new std::function<void(void)>([] {
__android_log_print(ANDROID_LOG_WARN, "MyOnClickListener", "Hello from native onClick!");
});
jclass listener_clazz = env->FindClass("com/example/myapp/MyOnClickListener");
jmethodID new_listener = env->GetMethodID(listener_clazz, "<init>", "(J)V");
jobject listener = env->NewObject(listener_clazz, new_listener, callback);
jmethodID set_onclicklistener = env->GetMethodID(button_clazz, "setOnClickListener", "(Landroid/view/View$OnClickListener;)V");
env->CallVoidMethod(button, set_onclicklistener, listener);
Note 1: In your real code you should of course add proper error handling and not blindly assume that every JNI call succeeds.
Note 2: Rather than relying on finalize to be called you might want to explicitly invoke the Release function when you no longer need the native peer object.
Note 3: I used a pointer to a std::function in my example. You may want to use a pointer to some class of your own. That's really up to you to decide.

Toast not displaying when C function is called

I have a service that can either be started with a launcher or runs on boot. The problem is that when I call a C function with JNI, all the Toast notifications are no longer displayed, whereas if I call only the java method, the Toast notifications display just fine. What causes this and how can I fix it?
Note:
If the C function does something simple like calls back to java and terminates, the Toast notifications will display. My function however, has an infinite loop used to handle interrupts.
ServiceLauncher
public class ServiceLauncher extends AppCompatActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
Toast.makeText(getBaseContext(), "onCreate", Toast.LENGTH_LONG).show();
super.onCreate(savedInstanceState);
startService(new Intent(this, MyService.class));
finish();
}
}
MyService
public class MyService extends Service {
#Override
public IBinder onBind(Intent intent) {
return null;
}
public void onDestroy() {
Toast.makeText(this, "Terminated", Toast.LENGTH_LONG).show();
}
#Override
public int onStartCommand(Intent intent,int flags, int startid) {
Toast.makeText(this, "Running", Toast.LENGTH_LONG).show();
javaMethod(); /*toast displays if this is called*/
cMethodFromJNI(); /*toast does not display if this is called*/
return START_STICKY;
}
}
C code structure
JNIEXPORT jint JNICALL
Java_className_cMethodFromJNI(JNIEnv *env, jclass type) {
jmethodID mid = (*env)->GetStaticMethodID(env, type, "javaMethod", "(I)V");
// open and read some fd (not shown)
for (;;) {
// wait on change in fd and act on it (not shown)
callJavaAgain(env,type,mid)
}
}
//close fd (not shown)
exit(0);
}
EDIT: I've changed the code like so, following the answer below. Seems like the situation is exactly the same as before, perhaps I am missing something.
Revised C code structure
JNIEXPORT jint JNICALL
Java_className_cMethodFromJNI(JNIEnv * env, jclass type) {
/*cache JVM*/
int status = ( * env) - > GetJavaVM(env, & jvm);
if (status != 0) {
LOGD("failed to retrieve *env");
exit(1);
}
attachedThread();
}
void attachedThread() {
/* get a new environment and attach a new thread */
JNIEnv * newEnv;
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_6; // choose your JNI version
args.name = NULL; // if you want to give the java thread a name
args.group = NULL; // you can assign the java thread to a ThreadGroup
( * jvm) - > AttachCurrentThread(jvm, & newEnv, & args);
jclass cls = ( * newEnv) - > FindClass(newEnv, "fully/qualified/class/name");
jmethodID mid = ( * newEnv) - > GetStaticMethodID(newEnv, cls, "callJavaAgain", "(V)V");
// open and read some fd (not shown)
for (;;) {
// wait on change in fd and act on it (not shown)
intermediaryFunction(newEnv, cls, mid);
}
}
//close fd (not shown)
exit(0);
}
void intermediaryFunction(JNIEnv * newEnv, jclass cls, jmethodID mid) {
//do some stuff (not shown)
( * newEnv) - > CallStaticVoidMethod(newEnv, cls, mid);
}
okay so this topic requires some careful attention. Android docs talk about JavaVM and JNIEnv and how to interface with them. What you will need to do is store a global reference to the JavaVM that's accessible from all threads in the C code. The JVM allows a thread to get access to the JNIEnv which can get you access to a class's methods and what not. The next key problem is that you need to stash a jobject 'pointer' to the android object you call back to from the C code (which you probably already know). Make sure you call JNIEnv->DetachThread on the thread that acquired the JNIEnv reference.
so again:
get JavaVM reference and store it in c code
have java call c code to store the jobject reference for your callback
start a thread to do looping, acquire a reference to a JNIEnv on that thread to call to java
call DetachThread before terminating thread execution
another point of trouble is accessing c++ objects from java code, there's a neat trick where you can have c++ code create the java objects, and assign into a private long _cppPointer member on the created Java class, then add this object to some collection on a java object you have already pointer to (like an activity or something)
good luck.
edit: i finally recalled another great source of information about JNI work

Android jni broadcast to java

I'm currently working on (an existing) Android app using native c++ code. I want send a broadcast to another Android Java app listening to a BroadcastReceiver. The receiving app is yet working, but I can't figure out how to broadcast data from JNI code either directly or by sending the values to java code and them broadcasting it from Java.
I hope my explanaion is clear. Can someone help me on how to accomplish this?
Thanks!
The easiest way is probably to do it in Java and write a JNI callback to call that Java function. If you do need to do it in C, you'll need to use JNI to create an Intent, then to set the action and extras of the intent, then use JNI to call the sendBroadcast method of your activity to actually send it. Its a lot of boilerplate JNI code, so I'd try to avoid it.
The easiest way is to implement all logic in a Java method:
public class MyClass {
static {
System.loadLibrary("mylib_jni");
}
public static final String ACTION_TEST = "com.example.action.TEST";
private final LocalBroadCastManager broadcastManager;
public MyClass(Context context) {
broadcastManager = LocalBroadcastManager.getInstance(context);
}
#SuppressWarnings("unused")
private void onSendText(String text) {
final Intent intent = new Intent(ACTION_TEST).putExtra(Intent.EXTRA_TEXT, text);
broadcastManager.sendBroadcast(intent);
}
private native void sendText();
}
and just call that method from JNI:
JNIEXPORT void JNICALL Java_com_example_MyClass_1sendText(JNIEnv *env, jobject instance) {
jclass clazz = (*env)->GetObjectClass(env, instance);
jmethodID method = (*env)->GetMethodID(env, clazz, "onSendText", "(Ljava/lang/String;)V");
jstring text = (*env)->NewString(env, "test message");
(*env)->CallVoidMethod(env, instance, method, text);
}

Categories