对于普通的Java Native Interface的用法已经比较常见,网上或书中的例子有如汗牛充栋,但是C/C++作为Java动态库而实现的动态编译技术却是比较少见。虽然并不常见,但在某些情况下,这种动态编译技术确是不可或缺,有了它才能满足这种动态的装配需求。
先从结构说起,如图所示,首先这种动态结构的JNI分为三个部分:作为Java的接口而直接被调用的部分称为caller,这部分会单独被编译为被Java代码直接调用的动态库;程序需要动态编译的代码(称为callee)是不确定的部分,即程序的其它部分编译好发布之时,callee这部分代码还不存在或尚未编译,因此是动态的;callee本身还需要调用基础函数或基础类库(自己编写的、非系统库的部分),这部分成为base。
这种动态装配技术在使用中,显然要分为三个操作步骤:第一步让程序的使用者指定或完成不确定的代码部分(动态装配),第二步根据使用者提供的代码进行编译(当然是让程序去自动编译),第三步运行整个程序,调用动态部分。
Base部分由于是基础函数/基础类库部分,因此代码在最初是确定的,将Base部分编译为静态库更为合理,这个静态库最终将被动态的callee所调用,当Callee部分被编译时再链接这个精态度而形成Callee的动态库,编译过程中会检查相关符号链接。Caller本身在此之前就会被编译为动态库,这部分动态库直接被Java代码所调用,caller动态库在运行时又会去调用callee部分的动态库,从而实现上述所说的动态编译技术。
对于Base部分的Makefile如下:
CC = gcc
AR = ar
CFLAGS = -fPIC -O2 -g -Wall
OBJS = base.o log.o util.o config.o
libsoap.a : $(OBJS)
$(AR) rcs libbase.a $(OBJS)
%.o : %.c
$(CC) -c $(CFLAGS) $< -o $@
......
Caller部分的代码如下:
......
dlerror();
fcn = dlsym(handle, callee);
if ((errmsg = dlerror()) != NULL)
{
fprintf(stderr, "Can't find function (%s): (%s)/n", callee, errmsg);
exit(-1);
}
fcn();
......
另外,使用中需要特别注意环境变量的设置(LD_LIBRARY_PATH),将相关动态库的目录设置在其中,否则编译或运行时就会出现动态库找不到的情况。
为了实现这种动态编译,需要在运行环境必须事先安装好与编译相关的软件,例如编译器(gcc)和makefile。
本文所面向的环境是针对Linux/Unix平台,对于Windows平台,仍然从中可以得到启发,创建于此相类似的过程。