Call method String java from ndk with languge c++ - java-native-interface

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.

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.

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

Call back on JNI

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();
}
}

How to catch JNI/Java Exception

I have a JNI layer in my application. In some cases Java throws an exception. How can I get the Java exception in the JNI layer? I have the code something like as follows.
if((*(pConnDA->penv))->ExceptionCheck(pConnDA->penv))
{
(*(pConnDA->penv))->ExceptionDescribe(pConnDA->penv);
(*(pConnDA->penv))->ExceptionClear(pConnDA->penv);
}
Will this block of code catch only JNI exceptions? Where will the exception description be logged in console(stderr)? How do I get this into the buffer, so that I can pass it to my logger module?
if you invoke a Java method from JNI, calling ExceptionCheck afterwards will return JNI_TRUE if an exception was thrown by the Java.
if you're just invoking a JNI function (such as FindClass), ExceptionCheck will tell you if that failed in a way that leaves a pending exception (as FindClass will do on error).
ExceptionDescribe outputs to stderr. there's no convenient way to make it go anywhere else, but ExceptionOccurred gives you a jthrowable if you want to play about with it, or you could just let it go up to Java and handle it there. that's the usual style:
jclass c = env->FindClass("class/does/not/Exist");
if (env->ExceptionCheck()) {
return;
}
// otherwise do something with 'c'...
note that it doesn't matter what value you return; the calling Java code will never see it --- it'll see the pending exception instead.
This is a complement of the Elliott Hughes' answer. My answer provides a step-by-step example explaining how to catch exceptions and how to translate them between C++ and Java words using the JNI layer.
Short answer
See the correct Elliott Hughes' answer.
Reusable example
This answer and snippets are in public domain or in CC0 in order to ease reuse. All the source code here is C++03 backward compatible.
To reuse the above snippet do the following:
Replace mypackage::Exception by your own C++ Exception.
If the corresponding Java exception my.group.mypackage.Exception is not defined, then replace "my/group/mypackage/Exception" by "java/lang/RuntimeException".
Catch exceptions from Java
See also the snippet on coliru.
void rethrow_cpp_exception_as_java_exception()
{
try
{
throw; // This allows to determine the type of the exception
}
catch (const mypackage::Exception& e) {
jclass jc = env->FindClass("my/group/mypackage/Exception");
if(jc) env->ThrowNew (jc, e.what());
/* if null => NoClassDefFoundError already thrown */
}
catch (const std::bad_alloc& e) {
jclass jc = env->FindClass("java/lang/OutOfMemoryError");
if(jc) env->ThrowNew (jc, e.what());
}
catch (const std::ios_base::failure& e) {
jclass jc = env->FindClass("java/io/IOException");
if(jc) env->ThrowNew (jc, e.what());
}
catch (const std::exception& e) {
/* unknown exception (may derive from std::exception) */
jclass jc = env->FindClass("java/lang/Error");
if(jc) env->ThrowNew (jc, e.what());
}
catch (...) {
/* Oops I missed identifying this exception! */
jclass jc = env->FindClass("java/lang/Error");
if(jc) env->ThrowNew (jc, "Unidentified exception => "
"Improve rethrow_cpp_exception_as_java_exception()" );
}
}
I thanks Mooing Duck for contribution on the above C++ code.
Adapt the JNI generated source code
The following file Java_my_group_mypackage_example.cpp use the above rethrow_cpp_exception_as_java_exception() function:
JNIEXPORT jlong JNICALL Java_my_group_mypackage_example_function1
(JNIEnv *env, jobject object, jlong value)
{
try {
/* ... my processing ... */
return jlong(result);
} catch(...) {
rethrow_cpp_exception_as_java_exception();
return 0;
}
}
JNIEXPORT jstring JNICALL Java_my_group_mypackage_example_function2
(JNIEnv *env, jobject object, jlong value)
{
jstring jstr = 0
try {
/* ... my processing ... */
jstr = env->NewStringUTF("my result");
} catch(...) {
rethrow_cpp_exception_as_java_exception();
}
return jstr;
}
JNIEXPORT void JNICALL Java_my_group_mypackage_example_function3
(JNIEnv *env, jobject object, jlong value)
{
try {
/* ... my processing ... */
} catch(...) {
rethrow_cpp_exception_as_java_exception();
}
}
Corresponding Java code
File example.java
package my.group.mypackage;
public class Example {
static {
System.loadLibrary("my-DLL-name");
}
public Example() {
/* ... */
}
private native int function1(int); //declare DLL functions
private native String function2(int); //using the keyword
private native void function3(int); //'native'
public void dosomething(int value) {
int result = function1(value);
String str = function2(value); //call your DLL functions
function3(value); //as any other java function
}
}
Note: "my-DLL-name" refers to the dynamic library produced from the above C/C++ code compiled. It can be my-DLL-name.dll on Windows or my-DLL-name.so on GNU/Linux/Unix.
Steps
Generate example.class from example.java (using javac or maven or your favorite IDE Eclipse/Netbeans/IntelliJ IDEA/...)
Generate C/C++ header file Java_my_group_mypackage_example.h from example.class using javah
In the case, when the Java exception occurs in a nested C++ call, it's not done with a simple return, but you prefer to remove the intermediate stack levels according to the throw mechanism.
I solved this with a
class JavaException : public std::exception
{
};
Then in a nested call, where the exception is raised
void nestedMethod()
{
env->CallVoidMethod(...); // Java call creating Exception
if (env->ExceptionCheck())
{
throw JavaException();
}
}
Finally returning to Java
void JNI_method(JNIenv *env)
{
try
{
doComplexCall(); // --> nestedMethod()
}
catch(const JavaException&)
{
//coming from positive ExceptionCheck()
return;
}
}