Set value from JNI in Java : JNI java.lang.NoSuchFieldError - java

I'm trying to set a variable's value (variable in java) from JNI.
I'm using GetFieldID and SetIntField to do the same.
Following is my code.
main.c
JNIEXPORT void JNICALL Java_com_example_hello_MainActivity_samplefunc
(JNIEnv *env, jobject obj, jobject x)
{
jclass class = (*env)->GetObjectClass(env, x);
jfieldID fid = (*env)->GetFieldID(env, myclass,"a","I");
(*env)->SetIntField(env, obj ,fid, 10);
return;
}
MainActivity.java
package com.example.hello;
public class MainActivity extends ActionBarActivity
{
int a = -1;
/* Declaration of Native function & Load JNI Library*/
public static native void samplefunc(Class x);
static {
System.loadLibrary("hellojni");
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Class x = this.getClass();
Log.d("JNI", "Before : Value of Port: " + a);
samplefunc(x);
Log.d("JNI", "After : Value of Port: " + a);
return;
}
}
The expected Logcat output is:
D/JNI Before : Value of Port: -1
D/JNI After : Value of Port: 10
But i get the following error:
D/JNI (12607): Before : Value of Port: -1
W/dalvikvm(12607): JNI WARNING: JNI function SetIntField called with exception pending
W/dalvikvm(12607): in Lcom/example/hello/MainActivity;.samplefunc:(Ljava/lang/Class;)V (SetIntField)
W/dalvikvm(12607): Pending exception is:
I/dalvikvm(12607): java.lang.NoSuchFieldError: no field with name='a' signature='I' in class Ljava/lang/Class;
I/dalvikvm(12607): at com.example.hello.MainActivity.samplefunc(Native Method)
I guess this is a little basic but I'm new to JNI.
Any help on this would be greatly appreciated.
I have already seen this:
JNI: NoSuchFieldError however it doesn't explain how a value of any variable is set.

I/dalvikvm(12607): java.lang.NoSuchFieldError: no field with name='a' signature='I' in class Ljava/lang/Class;
This line tells you that it is looking for a field 'a' with signature 'I' in class Class and not in your class MainActivity. The problem is the following:
// 'x' is of type Class, since you called samplefunc(Class x)
// Therefore 'class' is set to Class and not MainActivity
jclass class = (*env)->GetObjectClass(env, x);
// Here you try to access the field 'a' from class Class and not from your MainActivity.
// Hence your error.
jfieldID fid = (*env)->GetFieldID(env, class,"a","I");
Edit:
An easy solution would be to change the samplefunc to
samplefunc(MainActivity x)
Then you can use the same code and you get the correct class as suggested by Rolf ツ

Related

java.lang.ClassNotFoundException: on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system/lib64]]

Currently libraries(.so) are built using visual studio and placed on to jniLibs directory.
As shown below,
Getting below exception while finding the class in JNI call (here it is TWMainActivity but true with any other class),
2021-07-23 08:10:43.992 13617-1658/com.tally.twandroidconsolesimulator A/onsolesimulato: java_vm_ext.cc:578] JNI DETECTED ERROR IN APPLICATION: JNI GetMethodID called with pending exception java.lang.ClassNotFoundException: Didn't find class "com.tally.twandroidconsolesimulator.TWMainActivity" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system/lib64]]
java_vm_ext.cc:578] at java.lang.Class dalvik.system.BaseDexClassLoader.findClass(java.lang.String) (BaseDexClassLoader.java:207)
java_vm_ext.cc:578] at java.lang.Class java.lang.ClassLoader.loadClass(java.lang.String, boolean) (ClassLoader.java:379)
java_vm_ext.cc:578] at java.lang.Class java.lang.ClassLoader.loadClass(java.lang.String) (ClassLoader.java:312)
java_vm_ext.cc:578]
java_vm_ext.cc:578] in call to GetMethodID
While via global reference to the object passed via JNI call (java to C++) able to get the jclass.
//this is working
jclass cls = env->GetObjectClass (sMainActivityGlobalRef);
//this is not working throwing mentioned exceptions
// jclass cls = env->FindClass ("com/tally/twandroidconsolesimulator/TWMainActivity");
Note: the same code is working with Andrdoid Studio Native project without having jniLibs
Basic code flow as follows,
public class TWMainActivity extends AppCompatActivity {
static {
System.loadLibrary("TWXPControlsGalleryApp_arm64-v8a");
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InitializeMain();
}
public native void InitializeMain();
}
//Native code
static void
NativeThread(JavaVM * pJavaVm, jint pJVersion, jobject pObjGblRfrnc){
JNIEnv * env;
auto get_env_result = vm->GetEnv ((void **) &env, pJVersion);
if (get_env_result == JNI_EDETACHED) {
if (vm->AttachCurrentThread (env, nullptr) != JNI_OK) {
return;
}
}
jclass cls = env->FindClass ("com/tally/twandroidconsolesimulator/TWMainActivity");
//... rest of the code follows here
}
extern "C" JNIEXPORT void JNICALL
Java_com_tally_twandroidconsolesimulator_TWMainActivity_InitializeMain (JNIEnv * pEnv, jobject pObj)
{
JavaVM * sJavaVm;
jint jv_version = pEnv->GetVersion ();
pEnv->GetJavaVM (&sJavaVm);
jobject ref = pEnv->NewGlobalRef (pObj);
std::thread thread1 (NativeThread, sJavaVm, jv_version, ref );
thread1.detach ();
}
have referred this, this, this,this but didn't help to rectify the problem.
Environment:
classpath "com.android.tools.build:gradle:4.2.1"
I appreciate any hint.
FindClass will not be able to find any of your app-specific classes if invoked on a purely native thread, due to the wrong classloader being used. See this section in the Android developer documentation for more information.
There are various ways in which you could solve this, but the easiest is probably to resolve all classes in JNI_OnLoad, create global references to them, and save those global references somewhere where the rest of your code can access them.

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.

Android Native Activity - Call Java method

I've been trying for a while to call a java method from c++, I am able to do it successfully with an Activity class but with a NativeActivity, it crashes when calling CallVoidMethod. GetMethodID is able to find it, it returns an address. env & vm objects are valid & are populated from android_main(). Is it possible that it simply won't work with a native activity class?
Cpp: (edited)
void SendNotification() {
JavaVM* lJavaVM = main_activity->vm;
JNIEnv* lJNIEnv = main_activity->env;
JavaVMAttachArgs lJavaVMAttachArgs;
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
lJavaVMAttachArgs.name = "NativeThread";
lJavaVMAttachArgs.group = NULL;
jint lResult = lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
if (lResult == JNI_ERR)
return;
jobject lNativeActivity = main_activity->clazz;
jclass ClassNativeActivity = lJNIEnv->GetObjectClass(main_activity->clazz);
jmethodID _method = lJNIEnv->GetMethodID(ClassNativeActivity, "SendNotification", "()V");
lJNIEnv->CallVoidMethod(lNativeActivity, _method);
lJavaVM->DetachCurrentThread();
}
Java:
package com.thor.kalen;
import android.app.AlertDialog;
import android.app.NativeActivity;
import android.os.Bundle;
public class MainActivity extends NativeActivity
{
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
} // End of public void onCreate(Bundle savedInstanceState)
public void SendNotification() {
new AlertDialog.Builder(this).setTitle("Alert").setMessage("").setNeutralButton("Close", null).show();
}
}
com.thor.kalen.MainActivity.SendNotification() Java method should be called for a jobject of class com.thor.kalen.MainActivity, not a jclass of this object: it's not a static method:
main_activity->env->CallVoidMethod(main_activity.clazz, _method)
Note the comment in native_activity.h:
/**
* The NativeActivity object handle.
*
* IMPORTANT NOTE: This member is mis-named. It should really be named
* 'activity' instead of 'clazz', since it's a reference to the
* NativeActivity instance created by the system for you.
*
* We unfortunately cannot change this without breaking NDK
* source-compatibility.
*/
jobject clazz;
Also, you can only show() an AlertDialog from the Main (UI) thread. Your C++ code suggests that you do you from a background thread.
If this code executes on the main thread, then
main_activity->vm->DetachCurrentThread()
should be removed. AttachThread() can be removed, too, but it's a NOP when called on a thread that is already attached.

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

c++ Plugin -> JNI --> Java Classpath

i am trying to create a Plugin for an Application. The Plugin needs to be written in c++. I want to use the Plugin on Windows and on Mac, so it would be great to write the Plugin in Java.
My Problem, there is an other Plugin using Java. Since they are using the same main Application there is already an running JavaVM.
JavaVM *jvm = NULL;
jsize jvm_count = 0;
jint res=0;
res = JNI_GetCreatedJavaVMs (&jvm, 1, &jvm_count);
My Problem:
How can i change / modify the Classpath of the existing JavaVM? Or how can i create a new / second JavaVM?
I've tryed to load my jar file via JNI:
/* URL CLASS */
jclass URLcls;
URLcls = env->FindClass("java/net/URL");
/* URL CLASS CONSTRUCTOR*/
jmethodID URLclsMid;
URLclsMid = env->GetMethodID(URLcls, "<init>","(Ljava/lang/String;)V");
/* URL OBJECT */
jobject URLobj;
jstr = env->NewStringUTF("file:/path/to/test/file/test.jar");
URLobj = env->NewObject(URLcls, URLclsMid, jstr);
/* URL Array */
jobjectArray URLArray;
URLArray = env->NewObjectArray(1, URLcls, URLobj);
/*Thread Class*/
jclass ThreadCLS;
ThreadCLS = env->FindClass("java/lang/Thread");
/*Static Method currentThread*/
jmethodID ThreadCLS_currentThread;
ThreadCLS_currentThread = env->GetStaticMethodID(ThreadCLS, "currentThread","()Ljava/lang/Thread;");
/*get current Thread Object*/
jobject currentThread;
currentThread = env->CallStaticObjectMethod(ThreadCLS, ThreadCLS_currentThread);
/* getContextClassLoader method */
jmethodID currentThread_getContextClassLoader;
currentThread_getContextClassLoader = env->GetMethodID(ThreadCLS, "getContextClassLoader","()Ljava/lang/ClassLoader;");
/* ClassLoader Class */
jclass ClassLoaderCLS;
ClassLoaderCLS = env->FindClass("java/lang/ClassLoader");
/* get ClassLoader Object */
jobject classLoader = env->CallObjectMethod(currentThread, currentThread_getContextClassLoader);
/* URLClassLoader Class */
jclass URLClassLoaderCLS;
URLClassLoaderCLS = env->FindClass("java/net/URLClassLoader");
/* Static Method newInstance */
jmethodID URLClassLoaderCLS_newInstance;
URLClassLoaderCLS_newInstance = env->GetStaticMethodID(URLClassLoaderCLS, "newInstance","([Ljava/net/URL;Ljava/lang/ClassLoader;)Ljava/net/URLClassLoader;");
/* get new URLClassLoader Instance */
jobject myURLClassLoaderInstance;
myURLClassLoaderInstance = env->CallStaticObjectMethod(URLClassLoaderCLS, URLClassLoaderCLS_newInstance, URLArray, classLoader);
/* get setContextClassLoader Method */
jmethodID currentThread_setContextClassLoader;
currentThread_setContextClassLoader = env->GetMethodID(ThreadCLS, "setContextClassLoader","(Ljava/lang/ClassLoader;)V");
/* trying to set the ClassLoader from the current Thread */
env->CallVoidMethod(currentThread, currentThread_setContextClassLoader, myURLClassLoaderInstance);
/* get loadClass Method */
jmethodID loadClass;
loadClass = env->GetMethodID(URLClassLoaderCLS, "loadClass","(Ljava/lang/String;)Ljava/lang/Class;");
/* get a Class from my test.jar*/
jstring methodString = env->NewStringUTF("de.domain.sayHello");
jclass myClass = (jclass)env->CallObjectMethod(myURLClassLoaderInstance, loadClass, methodString);
/*working till here*/
jmethodID myClassMethod;
myClassMethod = env->GetMethodID(myClass, "doIt","()Ljava/lang/String;");
if (myClassMethod == NULL) {
// Method of Class "de.domain.sayHello" not found
}
What can i do to change the Classpath? or to load my jar File?
Thanks
Lisa
I've solved my Problem. I did not manage to set or influence the Classpath in a way so that i can use:
jmethodID myClassMethod;
myClassMethod = env->GetMethodID(myClass, "doIt","()Ljava/lang/String;");
if (myClassMethod == NULL) {
// Method of Class "de.domain.sayHello" not found
}
Instead of this i've created an Object of my Class using Java Reflection.
Find my Java Class: (after "/working till here/" from the code of my question)
/* Class CLASS */
jclass Classcls = env->FindClass("java/lang/Class");
/* String CLASS */
jclass StringCls = env->FindClass("java/lang/String");
jstring methodString = env->NewStringUTF("de.domain.sayHello");
jclass myJavaClass = (jclass)env->CallObjectMethod(MyURLClassLoaderInstance, URLClassLoaderCLS_loadClass, methodString);
Get an Object from my Java Class:
/* CLASS newInstance Method*/
jmethodID ClassNewInstanceMid = env->GetMethodID(Classcls, "newInstance","()Ljava/lang/Object;");
jobject myObject = env->CallObjectMethod(myJavaClass, ClassNewInstanceMid);
with this i got an object from my Java Class. (The Default Constructor was called)
With the jobject i was able to call a method of my object via java reflection. I passed a Sring with the Application path to my Java Method.
On the Java side i loaded the Application:
import java.io.File;
public class sayHello {
public static native void sayHi(String message, int times);
public sayHello()
{
System.out.println("object created :) ");
}
public void doIt(String test)
{
File myfile = new File(test);
if(myfile.exists())
System.load(myfile.getAbsolutePath());
else
System.out.println("Something went wrong :( ");
sayHi("C++ Funktion Call out of Java",5);
}
}
In my c++ Plugin i implemented this function:
JNIEXPORT void JNICALL Java_de_domain_sayHello_sayHi
(JNIEnv *localEnv, jclass clazz, jstring message, jint times)
{
int myT = times;
for(int i=0;i<myT;i++)
fprintf(stderr,"%s\n", localEnv->GetStringUTFChars(message, NULL));
}
An Interessting thing:
Inside the JNICALL Method (Java_de_domain_sayHello_sayHi) i am able to find my Call via JNI:
jclass myCLS = env->FindClass("de/domain/sayHello");
if(myCLS != NULL){
//class found
}
Inside this function it dosen't matter whether i use the JNIEnv "localEnv" from the function or "env" (global variable)
Thanks
Lisa

Categories