如何使用JNI打印宽字符

时间:2014-10-17 09:54:52

标签: c java-native-interface widechar

32位Ubuntu 计算机上, JDK 1.7.0 ,我无法打印宽字符。

这是我的代码:

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);
} 

然后我执行以下命令:

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

以下是取决于用于执行程序的JDK的结果:

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

  

使用fWprintf

     

结束

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

  

结束

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

  

结束

如您所见,使用JDK 1.7和JDK 1.8,fwprintf无效!

所以我的问题是我能错过使用JDK 1.7(和1.8)来使用宽字符?

注意:如果我调用 fprintf 而不是 fwprintf ,那么没有问题,所有内容都正确打印出来。

修改

根据James的评论,我创建了一个main.c文件:

#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;
}

然后我就这样编译:

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

并设置LD_LIBRARY_PATH

export LD_LIBRARY_PATH=/path/to/jni_test

它运作正常:

In the main
using fWprintf

所以问题可能不是来自C。

注意:它在64位计算机上正常运行。 我使用Linux Mint 32位也有类似的问题。

2 个答案:

答案 0 :(得分:1)

您不得将窄字符和宽字符的打印混合到同一个流中。 C99引入了流方向的概念,其中I / O流可以是面向对象的面向字节的(在C99之前,宽字符)在C语言标准中不存在)。从C99§7.19.2/ 4-5:

  

4)每个流都有方向。在流与外部文件关联之后,但在对其执行任何操作之前,该流没有方向。将宽字符输入/输出功能应用于没有方向的流后,该流将成为面向广泛的流。类似地,一旦将字节输入/输出函数应用于没有方向的流,该流就变成面向字节的流。只有调用freopen函数或fwide函数才能改变流的方向。 (成功调用freopen会删除任何方向。) 233)

     

5)字节输入/输出功能不应用于面向广泛的流,宽字符输入/输出功能不应用于面向字节的流。 [...]

     

233)三个预定义的流stdinstdoutstderr在程序启动时未定向。

C99标准将窄字符和宽字符函数混合为未定义行为。在实践中,GNU C库说“没有发布诊断。应用程序行为只会很奇怪,或者应用程序只会崩溃。fwide函数可以帮助避免这种情况。”(source)< / p>

由于JRE负责程序启动,因此它负责stdinstdoutstderr流,因此也负责它们的方向。 Your JNI code is a guest in its house, don't go changing its carpets.在实践中,这意味着您必须处理您给出的任何流方向,您可以使用fwide(3)功能检测到这种方向。如果要将宽字符打印到面向字节的流,太糟糕了。您需要通过说服JRE使用面向广播的流,或将宽字符转换为UTF-8或其他内容来解决这个问题。

例如,此代码应适用于所有情况:

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);
}

答案 1 :(得分:0)

@dalf, 问题出在JDK之外。它是32位版本的GLIBC。 请尝试在您的机器上重现它:

I)创建3个文件:

---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)运行命令:

$ 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 

并查看它输出的内容