dalf
dalf

Reputation: 602

How to print wide character using JNI

On a 32-bit Ubuntu machine, from JDK 1.7.0, I'm unable to print wide characters.

Here is my code:

JNIFoo.java

public class JNIFoo {    
    public native void nativeFoo();    

    static {
        System.loadLibrary("foo");
    }        

    public void print () {
        nativeFoo();
        System.out.println("The end");
    }
    
    public static void main(String[] args) {
        (new JNIFoo()).print();
        return;
    }
}

foo.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <jni.h>
#include "JNIFoo.h"

JNIEXPORT void JNICALL Java_JNIFoo_nativeFoo (JNIEnv *env, jobject obj)
{
  fwprintf(stdout, L"using fWprintf\n");
  fflush(stdout);
} 

Then I'm executing the following commands:

javac JNIFoo.java
javah -jni JNIFoo
gcc -shared -fpic -o libfoo.so -I/path/to/jdk/include -I/path/to/jdk/include/linux foo.c

Here is the result depending of the JDK used to execute the program:

jdk1.6.0_45/bin/java -Djava.library.path=/path/to/jni_test JNIFoo

using fWprintf

The end

jdk1.7.0/bin/java -Djava.library.path=/path/to/jni_test JNIFoo

The end

jdk1.8.0_25/bin/java -Djava.library.path=/path/to/jni_test JNIFoo

The end

As you can see, with JDK 1.7 and JDK 1.8, the fwprintf has no effect!

So my question is what am I missing to be able to use wide chars using JDK 1.7 (and 1.8) ?

Note: if I call fprintf instead of fwprintf, then there is no problem, everything is print out correctly.

Edit

Based on the comment of James, I created a main.c file:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <wchar.h>
#include "JNIFoo.h"

int main(int argc, char* argv[])
{
  fwprintf(stdout, L"In the main\n");  
  Java_JNIFoo_nativeFoo(NULL, NULL);

  return 0;
}

Then I compile it like that:

gcc -Wall -L/path/to/jni_test -I/path/to/jdk1.8.0_25/include -I/pat/to/jdk1.8.0_25/include/linux main.c -o main -lfoo

And set LD_LIBRARY_PATH

export LD_LIBRARY_PATH=/path/to/jni_test

And it is working correctly:

In the main
using fWprintf

So the problem may not come from C.

Note: it's working correctly on a 64-bit machine. I have similar problem using linux Mint 32-bit.

Upvotes: 2

Views: 585

Answers (2)

Lev
Lev

Reputation: 951

@dalf, The problem is outside JDK. It's in 32 bit version of GLIBC. Please try to reproduce it on your machine:

I) create 3 files:

---foo.c: 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <wchar.h> 

void foo() { 
  fwprintf(stdout, L"using fWprintf\n"); 
  fflush(stdout); 
} 

----main.c: 
#include <stdio.h> 
#include <stdlib.h> 
#include <dlfcn.h> 

int main(int argc, char **argv)  { 
    void *handle; 
    void (*foo)(); 
    char *error; 

   handle = dlopen("libfoo.so", RTLD_LAZY); 
   if (!handle) { 
     fprintf(stderr, "%s\n", dlerror()); 
     exit(EXIT_FAILURE); 
   } 

   dlerror(); 

   *(void **) (&foo) = dlsym(handle, "foo"); 

   if ((error = dlerror()) != NULL)  { 
     fprintf(stderr, "%s\n", error); 
     exit(EXIT_FAILURE); 
   } 

   (*foo)(); 
   dlclose(handle); 
   exit(EXIT_SUCCESS); 
} 

----mapfile: 
SomethingPrivate { 
    local: 
        *; 
}; 

II) run commands:

$ gcc -m32 -shared -fpic -o libfoo.so foo.c 
$ gcc -m32 -Xlinker -version-script=mapfile -o main main.c -ldl 
$ export LD_LIBRARY_PATH="." 
$ ./main 

and see what does it print to output

Upvotes: 0

Adam Rosenfield
Adam Rosenfield

Reputation: 400314

You must not mix printing of narrow and wide characters to the same stream. C99 introduced the concept of stream orientation whereby an I/O stream can be wide-oriented or byte-oriented (prior to C99, wide characters did not exist in the C language standard). From C99 §7.19.2/4–5:

4) Each stream has an orientation. After a stream is associated with an external file, but before any operations are performed on it, the stream is without orientation. Once a wide character input/output function has been applied to a stream without orientation, the stream becomes a wide-oriented stream. Similarly, once a byte input/output function has been applied to a stream without orientation, the stream becomes a byte-oriented stream. Only a call to the freopen function or the fwide function can otherwise alter the orientation of a stream. (A successful call to freopen removes any orientation.)233)

5) Byte input/output functions shall not be applied to a wide-oriented stream and wide character input/output functions shall not be applied to a byte-oriented stream. [...]

233) The three predefined streams stdin, stdout, and stderr are unoriented at program startup.

The C99 standard leaves mixing narrow- and wide-character functions as Undefined Behavior. In practice, the GNU C library says "There are no diagnostics issued. The application behavior will simply be strange or the application will simply crash. The fwide function can help avoiding this. " (source)

Since the JRE is in charge of program startup, it's in charge of the stdin, stdout, and stderr streams and therefore also their orientations. Your JNI code is a guest in its house, don't go changing its carpets. In practice, this means you have to deal with whatever stream orientation you're given, which you can detect with the fwide(3) function. If you want to print wide characters to a byte-oriented stream, too bad. You'll need to work around that by convincing the JRE to use wide-oriented streams, or convert your wide characters to UTF-8, or something else.

For example, this code should work in all cases:

JNIEXPORT void JNICALL Java_JNIFoo_nativeFoo (JNIEnv *env, jobject obj)
{
  if (fwide(stdout, 0) >= 0) {
    // The stream is wide-oriented or unoriented, so it's safe to print wide
    // characters
    fwprintf(stdout, L"using fWprintf\n");
  } else {
    // The stream is narrow oriented.  Convert to UTF-8 (and hopefully the
    // terminal (or wherever stdout is going) can handle that)
    char *utf8_string = convert_to_utf8(L"my wide string");
    printf("%s", utf8_string);
  }
  fflush(stdout);
}

Upvotes: 1

Related Questions