JNI, accessing a string returned from Java in C++

Hi guys,

I've made some progress in my program, the basic function is to extract text from online articles using a Java library called Boilerpipe, but I've got one last hurdle in that I seem to be able to get the the jvm to run the boilerpipe code, but what it returns is a jobject, whereas I appear to need a jstring to actually access the text of the articles in c++.

How do I solve this?

Here is the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include <jni.h>       /* where everything is defined */
#include <iostream>

int main() {

	using namespace std;
	JNIEnv *env1;
	JavaVM *jvm1;
	
	JavaVMInitArgs vm_args1;
	JavaVMOption options1[3];
	options1[0].optionString = "-Djava.library.path=/usr/lib/jvm/java-7-oracle/jre/lib/amd64/server/";
	options1[1].optionString = "-Djava.class.path=/home/josh/plus/console/boilerpipe/boilerpipe-1.2.0.jar";
	options1[2].optionString = "-Dulimit -c unlimited";
	options1[0].extraInfo = NULL;
	options1[1].extraInfo = NULL;
	options1[2].extraInfo = NULL;
	vm_args1.version = JNI_VERSION_1_6;
	vm_args1.nOptions = 3;
	vm_args1.options = options1;
	vm_args1.ignoreUnrecognized = false;


	//Instantiate JVM
	int reAt = JNI_CreateJavaVM(&jvm1, (void**)&env1, &vm_args1);

	if (reAt != JNI_OK) {
        std::cerr << "FAILED: JNI_CreateJavaVM " << reAt << std::endl;
        return -1;
    }
    
    
    //create the url string
    jclass string_cls = env1->FindClass("java/lang/String");
    cout << "url_cls: " << string_cls << std::endl;
    if (string_cls == NULL) {
    
        std::cerr << "FAILED: FindClass" << std::endl;
        return -1;
    }
    jmethodID string_mid = env1->GetMethodID(string_cls, "<init>", "(Ljava/lang/String;)V");
    cout << "url_mid: " << string_mid << std::endl;
    if (string_mid == NULL) {
    
        std::cerr << "FAILED: GetMethodID" << std::endl;
        return -1;
    }
    jobject string = env1->NewObject(string_cls, string_mid, "http://www.theguardian.com/politics/2015/may/08/david-cameron-conservative-government-election-victory/");
    
    
    
    //Convert string to url
    jclass url_cls = env1->FindClass("java/net/URL");
    cout << "url_cls: " << url_cls << std::endl;
    if (url_cls == NULL) {
    
        std::cerr << "FAILED: FindClass" << std::endl;
        return -1;
    }
    jmethodID url_mid = env1->GetMethodID(url_cls, "<init>", "(Ljava/lang/String;)V");
    cout << "url_mid: " << url_mid << std::endl;
    if (url_mid == NULL) {
    
        std::cerr << "FAILED: GetMethodID" << std::endl;
        return -1;
    }
    jobject url = env1->NewObject(url_cls, url_mid, string);
	//cout << "url: " << url << std::endl;


    //get article extractor object
    jclass cls = env1->FindClass("de/l3s/boilerpipe/extractors/ArticleExtractor");
    cout << "cls: " << cls << std::endl;
    if (cls == NULL) {
    
        std::cerr << "FAILED: FindClass" << std::endl;
        return -1;
    }
    jmethodID cons = env1->GetMethodID(cls, "<init>", "()V");
    cout << "mid: " << cons << std::endl;
    if (cons == NULL) {
    
        std::cerr << "FAILED: GetMethodID" << std::endl;
        return -1;
    }
    jobject objExtractor = env1->NewObject(cls, cons, url);
    jmethodID mid = env1->GetMethodID(cls, "getText", "(Ljava/net/URL;)Ljava/lang/String;");
    cout << "mid: " << mid << std::endl;
    if (mid == NULL) {
    
        std::cerr << "FAILED: GetMethodID" << std::endl;
        return -1;
    }


    /////////////PROBLEM IS HERE////////////////
    //causes problem as is, because cannot convert from jobject to jstring
    //but if set to jobject, i can't see how to access the text in it?
    jstring strText = env1->CallObjectMethod(objExtractor, mid, url);


    const char* final = env1->GetStringUTFChars(strText);

    cout << "Text: " << final << std::endl;


    

    //jmethodID mid = env1->GetStaticMethodID(cls, "test", "(I)V");
    //env1->CallStaticVoidMethod(cls, mid, 100);


	
	jvm1->DestroyJavaVM();

	return reAt;
}
Last edited on
According to http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/cpp.html (last apragraph) it should only need a cast.
As naraku9333 said, you just need to cast the returned jobject to a jstring.

Should be something like this (untested!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    jstring strText = (jstring)env1->CallObjectMethod(objExtractor, mid, url); // now with cast

    if(strTest != 0)
    {
        // somewhere safe to store the final result
        // as just for quick test used char buffer; could use std::string instead
        char final[256];

        const char* temp = env1->GetStringUTFChars(strText);

        strcpy(final, temp);

        env1->ReleaseStringUTFChars(strTest, temp);
        env1->DeleteLocalRef(strTest);

        cout << "Text: " << final << std::endl;
    }


But this code has been run! (On Windows.)

It's using printf as the tutorial I was learning from at the time I wrote it was doing so... (My original code had calls for the other standard types, too.)

And assumes JVM is on the path and the .cls file is in the same directory as the executable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <stdio.h>
#include <string.h>

#include <jni.h>

void test();

int main()
{
    test();

    return 0;
}

void test()
{
    JavaVMOption options[1];
    options[0].optionString = "-Djava.class.path=.";

    JavaVMInitArgs vm_args;
    memset(&vm_args, 0, sizeof(vm_args));
    vm_args.version  = JNI_VERSION_1_2;
    vm_args.nOptions = 1;
    vm_args.options  = options;

    JavaVM* pJVM = NULL;
    JNIEnv* pEnv = NULL;
    long status = JNI_CreateJavaVM(&pJVM, (void**)&pEnv, &vm_args);

    if(status != JNI_ERR)
    {
        jclass cls = pEnv->FindClass("Test");
        if(cls !=0)
        {
            jmethodID mid = pEnv->GetStaticMethodID(cls, "reverseString", "(Ljava/lang/String;)Ljava/lang/String;");
            if(mid != 0)
            {
                const char input[] = "Hello world!";
                char output[256] = "???";

                printf("Input : %s\n", input);

                // Convert the C++ char array to Java String 
                jstring jstr_param = pEnv->NewStringUTF(input);
                jstring jstr_ret   = (jstring)pEnv->CallStaticObjectMethod(cls, mid, jstr_param);
                pEnv->DeleteLocalRef(jstr_param);

                if (jstr_ret != 0) 
                {
                    // Now convert the Java String to C++ char array
                    const char* cstr = pEnv->GetStringUTFChars(jstr_ret, 0);
                    strcpy(output, cstr);
                    pEnv->ReleaseStringUTFChars(jstr_ret, cstr);
                    pEnv->DeleteLocalRef(jstr_ret);
                }

                printf("Output: %s\n", output);
            }
        }
        else
        {
            printf("cls == 0\n");
        }

        pJVM->DestroyJavaVM();
    }
    else
    {
        printf("status = %d\n", status);
    }
}


Where Test.java contains

1
2
3
4
5
6
7
8
public class Test
{
  public static String reverseString(String str)
  {
    StringBuilder dest = new StringBuilder(str);
    return dest.reverse().toString();
  }
}


Andy
Last edited on
Haha, thanks guys, works a treat now! There were some other problems of course but the casting was all I needed to fix this. I'm such a noob! :)
I assume you also freed the Java string and its associated UTF chars as well as adding the cast? (You did say "the casting was all I needed to fix this"...)

Andy
Topic archived. No new replies allowed.