Call back on JNI - java-native-interface

How can I register call back on JNI from android app? My requirement is, I want to make a JNI call from Android application and want to register a call back, so that I can get call back on java application from JNI.
Thanks

First, check out Swig. It wraps C++ in Java and also has a "director" fcility that makes it easier to call C++ methods from Java.
With raw JINI, you cannot do this directly. Although the RegisterNatives() call can be made to bind a native method, it cannot be changed. If you want to call a C function by pointer, you will need to do this in two steps. I am glossing over a lot, because JNI is incredibly verbose and tedious. The basic trick is, wrap the C function pointer in a java long.
First declare a java class:
public class Callback {
public Callback(long cMethodPointer) { this.cMethod = cMethod; }
public void doCallback() { callCMethod(cMethod); }
public static native void callCMethod(long cMethod);
public long cMethod;
}
Run javah on that class file, and you'll get a stub header generated that looks like:
JNIEXPORT void JNICALL Java_Callback_callCMethod
(JNIEnv *, jclass, jlong);
Implement that method in a DLL/so:
typedef void (*FP)();
JNIEXPORT void JNICALL Java_Callback_callCMethod
(JNIEnv *, jclass, jlong pointer) {
((FP)(void*)pointer)();
}
and call System.loadLibrary() on that DLL/so in your Java main() method.
Finally, from your C/C++ code, you will need to create an instance of the Callback object via JNI, and pass the pointer to an actual method to it:
void MyFunction() { ... }
void Register() {
jclass cls = env->FindClass("Callback");
jmethodID mid = env->GetMethodID(cls, "<init>", "(J)V");
jvalue arg;
arg.j = (jlong)(void*)MyFunction;
jobject callback = env->NewObjectA(confCls, mid, &arg);
}
So this gives you a brand new Callback object that points to your C function! But then, how do you do anything with that? Well, you have to pass the Callback object to Java via JNI (step omitted), so that your Java code has the Callback object from which to call your C method:
public class Foo {
public Callback callback;
public void doSomeStuff() {
...
callback.doCallback();
}
}

Related

Is it possible to create a new jobject of a java listener in JNI?

In Android Studio MainActivity, I write something like
int itemA_num = 0;
int itemB_num = 0;
ABCListener mabclistener = new ABCListenter() {
#Override
public void onEventActivated(CustomResult result) {
//do sth secret e.g.
itemA_num ++;
}
}
ABCobject mabcobject = (ABCobject) findviewById(R.id.abcobject1);
mabcobject.setListener(mabcListener);
I don't want people to decompile my APK and modify the code by amending the value or adding something like this:
ABCListener mabclistener = new ABCListenter() {
#Override
public void onEventActivated(CustomResult result) {
//do sth secret e.g.
itemA_num += 10000; //possibly some general name read by those guys and modified as int1 +=10000;
itemB_num += 500; //possibly some general name read by those guys and added this line int2 +=500;
}
}
So I want to use JNI with Cmake. Inside a .cpp file, I want to create the Class Object, findviewById, setListener and create the ABCListener.
I know using the format
jclass cls = (jclass) env->FindClass("abc/def/GHI");
jmethodID mid = (jmethod) env->GetMethodID(cls, "methodname", "(xxxx)yyy");
jobject obj = (jobject) env->CallObjectMethod(cls, mid, params);
However, if I want to write code about ABCListener and make a jobject of it, I don't know how and where to tell the machine I am going to write some code relating to #Override public void onEventActivated(CustomResult result) { ... }. I also want to add some lines of code inside the response in JNI.
I have found a website "similar" to this question but it is from 2011 and about Runnable. https://community.oracle.com/tech/developers/discussion/2298550/overriding-interface-methods-via-jni
I don't know if it still works in 2021.
First, define a new class on the Java side:
class NativeABCListener implements ABCListener {
#Override public native void onEventActivated(CustomResult result);
}
Next, create an instance of NativeABCListener, either in Java or in native code, and attach it to your mabcobject. You know how to do this so I will not repeat it.
On the native side, you simply define a C++ method with the appropriate name:
JNIEXPORT void JNICALL Java_your_package_NativeABCListener_onEventActivated(JNIEnv *env, jobject thiz, jobject result) {
...
}
If you need multiple ABCListeners that do different things, you can choose to create multiple NativeABCListener classes (each with their own corresponding native function), or you can modify NativeABCListener to store a C++ function pointer in a Java long field. In the ..._onEventActivated function you then extract the field from thiz and call it like a regular functino pointer.

Call method String java from ndk with languge c++

i want to call method inside java and retrun a string to ndk but my app will crash when i calling java method .
i checked more stackoverflow site but when im using other codes,it dont work.
help me thanks
inside ndk code :
extern "C"
JNIEXPORT jstring JNICALL
Java_com_hppni_battleword_view_SplashScreen_tkk(JNIEnv *env, jclass type) {
jclass jClass = env->FindClass("com/hppni/battleword/view/SplashScreen");
if (jClass != nullptr) {
jmethodID mid2 = env->GetStaticMethodID(jClass, "encryptThisString",
"(Ljava/lang/String;)Ljava/lang/String;"); // app will crash here
if (mid2 != nullptr) {
env->CallStaticVoidMethod(jClass, mid2, (jstring) "ali"); // app will crash here
}
}
return env->NewStringUTF(getSignature(env));
}
inside java class/Activity :
public static String encryptThisString(String input) {
Log.d("NDK", input);
return input;
}
You can't just cast char * string to jstring. You need to create jstring object using JNI functions like NewStringUTF, for example.

How to track which class the interface object has in JNI

Suppose, we've got a jobject interfaceObj of some interface class. How to track back the actual class of this jobject abstracted by an interface? In the following code IsInstanceOf does not work (always ends up at // error). Possibly, because jobject is an instance of interface class, and is not recognized as an instance of its actual class?
jclass interfaceClass = env->GetObjectClass(interfaceObj);
jclass class1 = env->FindClass("Class1");
jclass class2 = env->FindClass("Class2");
if (env->IsInstanceOf(interfaceObj, class1))
...
else if (env->IsInstanceOf(interfaceObj, class2))
...
else
{
// error
}
The actual class name is reachable through getClass().getName() of jobject, but is there a better way to check interfaceObj is an instance of particular class?

How call a method without any parameter? [duplicate]

I am trying to pass back a string from a Java method called from C++. I am not able to find out what JNI function should I call to access the method and be returned a jstring value.
My code follows:
C++ part
main() {
jclass cls;
jmethodID mid;
jstring rv;
/** ... omitted code ... */
cls = env->FindClass("ClassifierWrapper");
mid = env->GetMethodID(cls, "getString","()Ljava/lang/String");
rv = env->CallStatic<TYPE>Method(cls, mid, 0);
const char *strReturn = env->GetStringUTFChars(env, rv, 0);
env->ReleaseStringUTFChars(rv, strReturn);
}
Java Code
public class ClassifierWrapper {
public String getString() { return "TEST";}
}
The Method Signature (from "javap -s Class")
public java.lang.String getString();
Signature: ()Ljava/lang/String;
You should have
cls = env->FindClass("ClassifierWrapper");
Then you need to invoke the constructor to get a new object:
jmethodID classifierConstructor = env->GetMethodID(cls,"<init>", "()V");
if (classifierConstructor == NULL) {
return NULL; /* exception thrown */
}
jobject classifierObj = env->NewObject( cls, classifierConstructor);
You are getting static method (even though the method name is wrong). But you need to get the instance method since getString() is not static.
jmethodID getStringMethod = env->GetMethodID(cls, "getString", "()Ljava/lang/String;");
Now invoke the method:
rv = env->CallObjectMethod(classifierObj, getStringMethod, 0);
const char *strReturn = env->GetStringUTFChars(env, rv, 0);
The complete working solution is as below:
Java Side
public class ClassifierWrapper {
public ClassifierWrapper(){}
public String getString() { return "TEST";}
}
Native Side
jclass cls;
jmethodID mid;
jstring rv;
cls = jniEnv->FindClass("ClassifierWrapper"); //plase also consider your package name as package\name\classname
jmethodID classifierConstructor = jniEnv->GetMethodID(cls,"<init>", "()V");
if (classifierConstructor == NULL) {
return NULL; /* exception thrown */
}
jobject classifierObj = jniEnv->NewObject( cls, classifierConstructor);
jmethodID getStringMethod = jniEnv->GetMethodID(cls, "getString", "()Ljava/lang/String;");
rv = (jstring)(jniEnv->CallObjectMethod(classifierObj, getStringMethod));
const char *strReturn = jniEnv->GetStringUTFChars( rv, 0);
jniEnv->ReleaseStringUTFChars(rv, strReturn);
The signature ()Ljava/lang/String is wrong, due that a class name into JVM must terminate with ;, then in this case signature must be ()Ljava/lang/String;
The first problem is that ClassifierWrapper.getString() is not static. You will need to make it static or instantiate ClassifierWrapper.
The second problem is that you are using GetMethodId instead of GetStaticMethodId.
To invoke a method that returns an Object (such as a String) you would call CallStaticObjectMethod(). That will return a jobject local reference to the String that the method returned. You can safely cast the jobject to a jstring (see http://java.sun.com/docs/books/jni/html/types.html) and use GetStringUTFChars to retrieve the characters and GetStringUTFLength to get the number of characters.
JNI is very tricky. You need to check the error code for everything (use ExceptionCheck() when there is no error code). If you don't check for errors it will fail silently in most cases and usually not at the point where the actual bug is.
You also need to understand the difference between local and global references (and what methods generate new references) in order to not leak memory and run into the reference limit. For instance, FindClass returns a local reference to a class object, but GetMethodId returns a MethodID.
Good luck

Implementing C++ -to-lua observer pattern?

I have an observer (or "listener") pattern implemented in my code as such:
struct EntityListener
{
public:
virtual void entityModified(Entity& e) = 0;
};
class Entity
{
public:
Entity();
void setListener(EntityListener* listener);
private:
EntityListener* m_listener;
};
Now, this works in C++; the Entity class calls the entityModified() method whenever it needs. Now, I'd like to transfer some of the functionality to Lua, and among those function points is this listener callback. The entities are now created from the Lua scripts. The question is, how do I achieve the listener functionality in Lua?
For example, the Lua script currently does something like this:
function initializeEntity()
-- The entity object is actually created in C++ by the helper
Entity = Helper.createEntity()
-- Here I'd like to hook a Lua function as the Entity's listener
end
One possible solution is to have a LuaListener class in your C++ code that contains a "pointer" to the Lua function, and a Lua-specific setListener function that is called from the Lua script that takes a Lua function as argument, and creates a LuaListener instance and passes that to the actual C++ setListener.
So the Lua code would look something like
function onModified(entity)
-- ...
end
function initializeEntity()
entity = Helper.createEntity()
entity.setListener(onModified)
end
And the C++ code would look something like (pseudoish-code only):
class LuaListener : public EntityListener
{
private:
lua_State* state;
std::string funcName;
public:
void entityModified(Entity& e)
{
// Call function `funcName` in `state`, passing `e` as argument
}
};
class LuaEntity : public Entity
{
public:
void setListenerLua(state, funcName, ...)
{
Entity::setListener(new LuaListener(state, funcName, ...));
}
};