JNI 初步

前面简单的学习了一些 python 的 C API,这里看看 Java 的做法。从某个角度,python 的 interpreter 动态加载 dynamic lib,对应的 Java 应该是 JVM 完成的,但是 JVM 仍然要求使用一个 Java class 对此 dynamic lib 进行封装,而对应的方法如果声明为 native 就会调用对应的 C/C++ 的 wrapper,而这些 wrapper 将会把这部分调用提交到对应的 C/C++ 实现,之后将结果返回的时候需要将 ownership 转交给 JVM。这部分内容可以参考 The Java Native Interface: Programmer’s Guide and Specification

如果只是传递一些 low level 的 Java 对象,这就比较简单,下面的例子展示了如何传递一个 java.lang.String。

class Prompt {
  private native String getLine (String prompt) ;

  public static void main (String[] args) {
    Prompt p = new Prompt ();
    String input = p.getLine ("input: ") ;
    System.out.println ("received: " + input) ;
  }

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

注意我们的 native code 部分必须先编译 java 获得 class 之后才能生成头文件(具体关系可以参看后面的 Makefile)。生成 .h 后就可以填空实现需要的函数:

#include "Prompt.h"
#include <stdio.h>

JNIEXPORT jstring JNICALL
Java_Prompt_getLine (JNIEnv* env, jobject o, jstring s) {
  char buf [1024] ;
  const jbyte* str = (*env) -> GetStringUTFChars (env, s, NULL) ;
  if (str == NULL)
    return NULL ;
  printf ("%s", str) ;
  (*env) -> ReleaseStringUTFChars (env, s, str) ;
  scanf ("%s", buf) ;
  return (*env) -> NewStringUTF (env, buf) ;
}

需要说明的是,这里的函数定义是从对应的 .h 中 copy 出来的,传递过来的 Java 基本类型都是 j 开头的,比如 jobject 对应 java.lang.Object 等等,我们必须通过 env 获得的其具体的表达形式而不能直接使用,我们返回的对象也必须通过 env 产生。对应一些调用的函数可以参考 The Java Native Interface: Programmer’s Guide and Specification。这里传入的是通过函数参数,我们也可以访问某个对象的成员,这就是通过 env 和传入的 jobject。

为了编译以上程序,我们设计了下面的 Makefile。

CPPFLAGS=-I$(JAVA_HOME)/include
CLS = HelloWorld.class Prompt.class
DYLIB = $(CLS:%.class=lib%.dylib)
GHEAD = $(CLS:%.class=%.h)

all: $(CLS) $(DYLIB)

.PHONY: clean

clean:
	$(RM) $(CLS) $(DYLIB) $(GHEAD) *.o *~

%.class: %.java
	javac $^

%.h: %.class
	javah -jni `basename $^ .class`

lib%.dylib: %.h %.o
	$(LINK.c) -shared $(filter %.o, $^) -o $@

这里为了避免将生成的 .h 文件保留下来,特意通过 implicit rule 去产生中间的 .h 文件,使用完后就会被自动的删掉。

如果我们有大量的函数需要从 native code 移植到 Java 里面,以上办法(one-to-one)就显得比较麻烦。比较推荐的是使用 stub。这个方式的策略是依赖两个重要的类型(实现见这里),CFunction 和 CMalloc,他们将常见的调用都封装好了,我们只需要在提供的类里面初始化他们,然后将对应的函数调用通过 CFunction(使用 Java code)实现即可。

如果需要把复杂的数据结构传递,我们需要所谓的 peer class,本质上它就是通过 CMalloc 管理内存,这时我们可以将 native code 管理的对象放在这里面。anyway 这些方法都提供了一些思路而不是一个 generic 的 solution。特别如果是 C++ 的对象,个人觉得可能 CMalloc 也不是那么的强大,更好地可能是使用类似 boost.python 的方式能够比较自动的把需要 export 的方法让用户简洁的声明出来。

最后小说一句的是记得设置 java.library.path 否则 JVM 找不到动态链接库,而为了让你的动态链接库能找到别的动态链接库,需要 RPATH 或者 LD_LIBRARY_PATH 这类技术,前面讲过了,不再赘述。

哎,跨语言简直就是梦魇 -,-b

——————
And Lot went out, and spoke to his sons in law, which married his daughters, and said, Up, get you out of this place; for the LORD will destroy this city. But he seemed as one that mocked to his sons in law.

Advertisements
JNI 初步

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s