97

I'm trying to get a simple Java method call from C++ while Java calls native method. Here's the Java code:

public class MainActivity extends Activity {
    private static String LIB_NAME = "name";

    static {
        System.loadLibrary(LIB_NAME);
    }

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        TextView tv = (TextView) findViewById(R.id.textview);
        tv.setText(this.getJniString());
    }

    public void messageMe(String text) {
        System.out.println(text);
    }

    public native String getJniString();
}

I'm trying to call messageMe method from native code in the process of getJniString* method call from Java to native.

native.cpp:

#include <string.h>
#include <stdio.h>
#include <jni.h>

jstring Java_the_package_MainActivity_getJniString( JNIEnv* env, jobject obj, jint depth ){

//    JavaVM *vm;
//    JNIEnv *env;
//    JavaVMInitArgs vm_args;
//    vm_args.version = JNI_VERSION_1_2;
//    vm_args.nOptions = 0;
//    vm_args.ignoreUnrecognized = 1;
//
//    // Construct a VM
//    jint res = JNI_CreateJavaVM(&vm, (void **)&env, &vm_args);

    // Construct a String
    jstring jstr = env->NewStringUTF("This string comes from JNI");
    // First get the class that contains the method you need to call
    jclass clazz = env->FindClass("the/package/MainActivity");
    // Get the method that you want to call
    jmethodID messageMe = env->GetMethodID(clazz, "messageMe", "(Ljava/lang/String;)V");
    // Call the method on the object
    jobject result = env->CallObjectMethod(jstr, messageMe);
    // Get a C-style string
    const char* str = env->GetStringUTFChars((jstring) result, NULL);
    printf("%s\n", str);
        // Clean up
    env->ReleaseStringUTFChars(jstr, str);

//    // Shutdown the VM.
//    vm->DestroyJavaVM();

    return env->NewStringUTF("Hello from JNI!");
}

After clean compilation app stops with next message:

ERROR/AndroidRuntime(742): FATAL EXCEPTION: main
        java.lang.NoSuchMethodError: messageMe
        at *.android.t3d.MainActivity.getJniString(Native Method)
        at *.android.t3d.MainActivity.onCreate(MainActivity.java:22)

Apparently it means that method name is wrong, but it looks OK to me.

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
Denys S.
  • 6,358
  • 12
  • 43
  • 65
  • 21
    Please post your solution as an ordinary answer to make both your question and the solution easier to read and thus more useful to the community. You can also collaborate with other people who already answered to complete their answers. – misiu_mp Feb 13 '12 at 13:45
  • @Denys : I followed your coding, but i get this error : java.lang.UnsatisfiedLinkError: getJniString. Can u help me fix this error? – Huy Tower Mar 21 '14 at 09:24
  • @AlexTran, it was a long time ago but judging from the error you probably misspelled or didn't link the `getJniString` method either in java or in c. Make sure to properly link c code to java probably by system import (srsly don't remember all this stuff now :P) – Denys S. Jul 23 '14 at 14:12
  • 1
    How is this calling a java method from c? It's blatantly Java's `onCreate` method calling your native C. – John Oct 07 '14 at 15:37
  • I'm getting base **operand of '->' has non-pointer type 'JNIEnv** when execute with environment(env) variable. Also what about if wanted to do without env* variable, like callback from JNI to Java layer ! Any suggestion! – CoDe Sep 29 '15 at 10:34
  • I've moved the solution posted in the question to a separate answer post as Community Wiki. Should the author decide to post it themselves, I'll delete my answer. – BartoszKP Mar 30 '16 at 16:30

2 Answers2

48

If it's an object method, you need to pass the object to CallObjectMethod:

jobject result = env->CallObjectMethod(obj, messageMe, jstr);

What you were doing was the equivalent of jstr.messageMe().

Since your is a void method, you should call:

env->CallVoidMethod(obj, messageMe, jstr);

If you want to return a result, you need to change your JNI signature (the ()V means a method of void return type) and also the return type in your Java code.

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
Matthew
  • 44,826
  • 10
  • 98
  • 87
  • Please, guide me on how to do that, because of my P.S. :) – Denys S. Mar 04 '11 at 19:09
  • I get the same result with what you're suggesting. – Denys S. Mar 04 '11 at 19:13
  • 1
    there is actually a CallVoidMethod, CallObjectMethod, etc. each one with a different return type. Since your messageMe method is (Ljava/lang/String;)V, you need to use CallVoidMethod. – Matthew Mar 04 '11 at 19:27
  • 2
    note that the error you're getting probably indicates that your Java native method (in your Java code) is probably not of void return type and so isn't being found by GetMethodID – Matthew Mar 04 '11 at 19:30
13

Solution posted by Denys S. in the question post:

I quite messed it up with c to c++ conversion (basically env variable stuff), but I got it working with the following code for C++:

#include <string.h>
#include <stdio.h>
#include <jni.h>

jstring Java_the_package_MainActivity_getJniString( JNIEnv* env, jobject obj){

    jstring jstr = (*env)->NewStringUTF(env, "This comes from jni.");
    jclass clazz = (*env)->FindClass(env, "com/inceptix/android/t3d/MainActivity");
    jmethodID messageMe = (*env)->GetMethodID(env, clazz, "messageMe", "(Ljava/lang/String;)Ljava/lang/String;");
    jobject result = (*env)->CallObjectMethod(env, obj, messageMe, jstr);

    const char* str = (*env)->GetStringUTFChars(env,(jstring) result, NULL); // should be released but what a heck, it's a tutorial :)
    printf("%s\n", str);

    return (*env)->NewStringUTF(env, str);
}

And next code for java methods:

    public class MainActivity extends Activity {
    private static String LIB_NAME = "thelib";

    static {
        System.loadLibrary(LIB_NAME);
    }

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        TextView tv = (TextView) findViewById(R.id.textview);
        tv.setText(this.getJniString());
    }

    // please, let me live even though I used this dark programming technique
    public String messageMe(String text) {
        System.out.println(text);
        return text;
    }

    public native String getJniString();
}
Community
  • 1
  • 1
BartoszKP
  • 34,786
  • 15
  • 102
  • 130