2

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

这是我的代码:

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 位时遇到了类似的问题。

4

2 回答 2

1

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

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

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

233)三个预定义的流stdinstdout、 和stderr在程序启动时是无方向的。

C99 标准将混合窄字符和宽字符函数视为未定义行为。实际上,GNU C 库说“没有发出诊断。应用程序的行为会很奇怪,或者应用程序会崩溃。该fwide函数可以帮助避免这种情况。”(来源

由于 JRE 负责程序启动,它负责stdinstdoutstderr流,因此也负责它们的方向。 你的 JNI 代码是它家的客人,不要去换它的地毯。 在实践中,这意味着您必须处理给定的任何流方向,您可以使用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);
}
于 2014-10-22T04:34:30.130 回答
0

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

一)创建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: 
        *; 
}; 

二)运行命令:

$ 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 

看看它打印什么输出

于 2015-01-29T20:50:55.733 回答