16

Is it necessary to convert char* to jbyteArray, then call java String's contructor to generate a jstring? How else can it be done? Please help.

static int testhandler(void *arg, ...)
 {
    int i;
    struct callback *cb = (struct callback *)arg;

    JNIEnv *env = cb->env;
    char *sig = cb->signature;

    jint size = (jint) strlen(sig);
    jint size1;
    va_list arguments;

    jobjectArray return_array;
    jclass obj_class;
    jbyteArray bytes;
    jstring str;

    obj_class = (*env)->FindClass(env, "java/lang/Object");
    return_array = (*env)->NewObjectArray(env, size, obj_class, NULL);

    va_start(arguments, arg);

    for (i = 0; i < size; i++) {
        jclass clazz;
        jmethodID id;
        jobject obj;
        jobject encoding;
        switch (sig[i]) {
            case 'i': {
                clazz = (*env)->FindClass(env, "java/lang/Integer");
                id = (*env)->GetMethodID(env, clazz, "<init>", "(I)V");
                obj = (*env)->NewObject(env, clazz, id, va_arg(arguments, uint32_t));
                (*env)->SetObjectArrayElement(env, return_array, i, obj);
                break;
            }
            case 'l': {
                clazz = (*env)->FindClass(env, "java/lang/Long");
                id = (*env)->GetMethodID(env, clazz, "<init>", "(J)V");
                obj = (*env)->NewObject(env, clazz, id, va_arg(arguments, uint64_t));
                (*env)->SetObjectArrayElement(env, return_array, i, obj);
                break;
            }
            case 's': {
                clazz = (*env)->FindClass(env, "java/lang/String");
                size1 = (jint) strlen(va_arg(arguments, char *));
                id = (*env)->GetMethodID(env, clazz, "<init>", "([BLjava/lang/String;)V");

                encoding = (*env)->NewStringUTF(env, va_arg(arguments, char *)); 
                bytes = (*env)->NewByteArray(env, size1); 
                (*env)->SetByteArrayRegion(env, bytes, 0, size1, (jbyte *)(va_arg(arguments, char *)));

                str = (jstring)(*env)->NewObject(env, clazz, id , bytes);
                obj = (*env)->NewObject(env, clazz, id, str);
                (*env)->SetObjectArrayElement(env, return_array, i, obj);
                break;
            }
            default: {
                printf("unknown signature char '%c'\n", sig[i]);
            }
        }

    }
    va_end(arguments);
    (*env)->CallVoidMethod(env, cb->handler, cb->id, return_array);

    return 0;
}
Sumit Singh
  • 15,743
  • 6
  • 59
  • 89
user2358330
  • 377
  • 2
  • 6
  • 17
  • Firstly, I do not understand, why when `env` has type `JNIEnv*` уоu write `(*env)->SomeFunc`? Does this code compile? Secondly, choose one of following variants: object-oriented: `env->SomeFunc(params)` or C-style: `SomeFunc(env, params)`. – Tsar Ioann Jun 05 '13 at 12:31
  • 3
    @TsarIoann He is using the C style. It is "object oriented" C where you have to use `(*env)->SomeFunc(env, xyz)`. It is totally correct: http://stackoverflow.com/a/936148/1350762 – maba Jun 05 '13 at 12:39
  • 1
    @maba Oh my god. Sorry. The answer given by mkaes is correct. – Tsar Ioann Jun 05 '13 at 12:40
  • @TsarIoann 1. Yes the code compiles without any errors in VisualStudio. For some reason im not able to just use env-> in my program. It allows env only as (*env). 2. I have to use the object oriented style. But when I call a function using env -> somefunc(params), it complains saying not enough arguments for the call. So when I use env -> somefunc(env, params), I get no errors. Im new to using JNI, so I dont know why I get these errors and the reason behind them. – user2358330 Jun 05 '13 at 12:43
  • @maba Do you know if it is possible to pass the va_arg to the SetByteArrayRegion as argument? Am I making a mistake there? – user2358330 Jun 05 '13 at 12:48
  • @user2358330 I don't know why you want to use a byte array. Use mkaes answer and create a string using `NewStringUTF`. – maba Jun 05 '13 at 13:10
  • @maba Like you suggested, I used just the NewStringUTF function as follows `clazz = (*env)->FindClass(env, "java/lang/String"); id = (*env)->GetMethodID(env, clazz, "", "([BLjava/lang/String;)V"); obj = (*env)->NewObject(env, clazz, id, (*env)->NewStringUTF(env, va_arg(arguments, char *))); (*env)->SetObjectArrayElement(env, return_array, i, obj); break;` But I still get the same exception access violation error. – user2358330 Jun 05 '13 at 14:17

3 Answers3

43

You could just check JNI api documentation. E.g. Here.
You will find:

jstring NewStringUTF(JNIEnv *env, const char *bytes);

So all you have to do it something like this:

char *buf = (char*)malloc(10);
strcpy(buf, "123456789"); // with the null terminator the string adds up to 10 bytes
jstring jstrBuf = (*env)->NewStringUTF(env, buf);
jesosk
  • 313
  • 1
  • 8
mkaes
  • 13,781
  • 10
  • 52
  • 72
  • 1
    Is it necessary to `free(buf)` at some point or does `NewStringUTF` make the JVM responsible for that memory? – zmb Jun 07 '13 at 23:35
  • @zmd: Yes you must free the memory yourself. And depending on the context you might also need to call `DeleteLocalRef()` on your string object. – mkaes Jun 08 '13 at 13:04
  • typo, ``(*env)->NewStringUTF(env, buf);`` should be ``env->NewStringUTF(env, buf);``. – Hank Nov 29 '13 at 08:58
  • 2
    @Yuanhang: No it is not a typo. Either you use the c syntax like I did or the c++ syntax with `env->NewStringUTF(buf)`. It depends on how you want to compile your native code. – mkaes Nov 29 '13 at 09:23
  • it's not going to work in C++.... G++ won't compile it, author tagged C++ and not C! – user924 Aug 09 '18 at 09:37
  • @user924: Yes it will work. Just see my comment about the choice of syntax. – mkaes Aug 09 '18 at 09:49
10

It depends on the nature of your char * string. NewStringUTF copies from a 0-terminated, modified UTF-8 encoding of Unicode characters. NewString copies from a counted, UTF-16 encoding of Unicode characters. If you don't have either of those, you have to perform a conversion.

A lot of code using NewStringUTF is written with the assumption that the string is NUL-terminated ASCII. If that assumption is correct then it will work because the modified UTF-8 encoding and the ASCII encoding would produce the same byte sequence. Such code should be clearly commented to document the restriction on the string data.

The conversion method you allude to—calling Java functions (e.g. String(byte[] bytes, Charset charset)) —is a good one. One alternative (in Windows, I see you are using Visual Studio) is MultiByteToWideChar. Another alternative is iconv.

Bottom line: You have to know what character set and encoding your string uses and then convert it to a UTF-16 encoded Unicode for use as a Java String.

Tom Blodget
  • 20,260
  • 3
  • 39
  • 72
4

it is better to return byte[] to java rather than jstring due to the difference between UTF string in java and c string.it's much easier to deal encoding problem in java

in java:

byte[] otherString = nativeMethod("javastring".getBytes("UTF-8"))

in c++:

jbyte* javaStringByte = env->GetByteArrayElements(javaStringByteArray, NULL);
jsize javaStringlen = env->GetArrayLength(javaStringByteArray);
std::vector<char> vjavaString;
vjavaString.assign(javaStringByte , javaStringByte + tokenlen);
std::string cString(vjavaString.begin(), vjavaString.end());
//
// do your stuff ..
//
jsize otherLen = otherCString.size();
jbyteArray otherJavaString = env->NewByteArray(otherLen);
env->SetByteArrayRegion(otherJavaString , 0, otherLen , (jbyte*) &otherJavaString [0]);

env->ReleaseByteArrayElements(javaStringByteArray, javaStringByte , JNI_ABORT);
return otherJavaString ;

in java again:

new String(otherString);
zyanlu
  • 181
  • 1
  • 3