Reputation: 602
On a 32-bit Ubuntu machine, from JDK 1.7.0, I'm unable to print wide characters.
Here is my code:
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;
}
}
#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.
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
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
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 thefwide
function can otherwise alter the orientation of a stream. (A successful call tofreopen
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
, andstderr
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