JNI系列教程一——入门

1.1 背景

和很多语言类似,java也提供了调用原生代码的功能,这门技术叫做JNI。有了JNI,可以在付出更小的代价的前提下,复用大量已经写好的C/C++库,当然一般用JNI的目的还是由于java在处理计算密集型(比如说非对称运算)的操作时有时会力不从心。
从结构上来看JNI是一个中间层,具体的调用步骤是这个样子的:java->JNI->C/C++。

本文源地址http://blog.whyun.com/posts/jni 转载请注明出处

1.2 准备活动

1.2.1 编写java代码

package com.whyun.jni.chapter1;

/**
 * User: sunny
 * Date: 15-10-28
 * Time: 下午12:29
 */
public class FirstDemo {
    static{
        System.loadLibrary("firstdemo");
    }
    public native int getNum();
    public native String getString();
    public static void main(String []args) {
        FirstDemo demo = new FirstDemo();
        System.out.println("num:"+demo.getNum()+",string:"+demo.getString());
    }
}

代码 1.2.1.1 注意到我们在类FirstDemo中定义了两个成员函数都没有函数体,且都加了关键字native声明,如果函数写成这样,则代表当前函数需要调用底层C/C++代码。注意System.loadLibrary这句话,java中使用这个函数来加载动态库,windows平台下运行此段代码要保证firstdemo.dll存在环境变量%path%中,linux平台下要保证libfirstdemo.so存在环境变量$LD_LIBRARY_PATH中。

其实java在loadLibrary的时候,是读取的系统变量java.library.path来搜寻动态库位置的,你可以用System.getProperty("java.library.path")来输出这个变量的内容。只不过在windows中会把环境变量%path%的内容加入到这个变量中,在linux中会把环境变量$LD_LIBRARY_PATH加入到这个变量中。在我的一台linux上打印java.library.path,会输入如下内容: /usr/jdk1.6.0_45/jre/lib/i386/server:/usr/jdk1.6.0_45/jre/lib/i386: /usr/jdk1.6.0_45/jre/../lib/i386: /home/username/lib::/usr/java/packages/lib/i386 :/lib:/usr/lib 其中/home/username/lib:是从环境变量$LD_LIBRARY_PATH读取的。不推荐将生成的动态库放置到系统目录中,首先是不一定有管理员权限,其次会导致系统库目录下的文件过多,不易管理。

1.2.2 生成头文件

本文用到的项目源码在文后给出,项目的目录结构如下:
项目目录结构
图1.2.2 项目目录结构
其中目录out/production为我们的class文件生成的目录,在命令行下进入该目录,运行如下命令javah com.whyun.jni.chapter1.FirstDemo,运行成功之后则在运行命令行的目录下生成文件com_whyun_jni_chapter1_FirstDemo.h,用文本编辑器打开这个头文件,会显示如下内容:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_whyun_jni_chapter1_FirstDemo */

#ifndef _Included_com_whyun_jni_chapter1_FirstDemo
#define _Included_com_whyun_jni_chapter1_FirstDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_whyun_jni_chapter1_FirstDemo
 * Method:    getNum
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_whyun_jni_chapter1_FirstDemo_getNum
  (JNIEnv *, jobject);

/*
 * Class:     com_whyun_jni_chapter1_FirstDemo
 * Method:    getString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_whyun_jni_chapter1_FirstDemo_getString
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

代码 1.2.1

1.3 编译运行

为了简化在windows和linux下配置编译步骤,我们先在操作系统中配置两个环境变量,在windows上将JAVA_HOME配置为JDK的安装路径,很多情况下这个环境变量在配置JDK编译环境的时候已经配置过,可以通过echo %JAVA_HOME%的输出来判断之前是否已经配置过;同理我们在linux上也配置环境变量JAVA_HOME(通过运行echo $JAVA_HOME来检测是否存在)。同时我们在windows上将目录d:\lib加入环境变量PATH中,在linux上将/opt/lib加入环境变量LD_LIBRARY_PATH中。

1.3.1 windows编译环境配置

windows下需要安装visual studio(简称vs)环境来完成C/C++编译,vs 有professional、ultimate和Express三个版本,前两者收费,我们使用免费的Express就够用了。我电脑上装的是vs express for desktop版本。
打开vs,新建项目,选择Visual C++,然后选择Empty Project,输入项目名firstdemo,点击确定。
接着设置项目属性,右击项目,然后选择properties,在Configuration Properties->General->Project Defaults->Configuration Type中,选择Dynamic Library (.dll)。然后在Configuration Properties->VC++ Directories->General->Include Directories中添加两个路径:$(JAVA_HOME)\include$(JAVA_HOME)\include\win32
最后编写c代码,在vs中新建源代码的时候,默认是cpp后缀,我们这里建一个c后缀的文件,因为我的编写习惯是c语法。

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

/*
 * Class:     com_whyun_jni_chapter1_FirstDemo
 * Method:    getNum
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_whyun_jni_chapter1_FirstDemo_getNum
  (JNIEnv *env, jobject obj) {
      return (jint)1;
}

/*
 * Class:     com_whyun_jni_chapter1_FirstDemo
 * Method:    getString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_whyun_jni_chapter1_FirstDemo_getString
  (JNIEnv *env, jobject ob) {
      jstring jinfo            = (*env)->NewStringUTF(env,"the first demo.");
      return jinfo;
}

代码1.3.1 firstdemo.c
注意我们引用了之前的com_whyun_jni_chapter1_FirstDemo.h,我们把它放到firstdemo.c同一级目录下了。
接着运行编译,产生dll文件,默认情况下会在项目目录Debug文件夹下产生firstdeom.dll,你可以把这个文件夹添加到环境变量PATH中去,也可以写脚本在编译完成之后将dll拷贝到电脑的任何一个PATH路径下,在Configuration Properties->Build Events->Post-Build Event->Command Line中写入如下命令:copy "$(TargetDir)$(TargetName).dll" d:\lib\$(TargetName).dll

1.3.2 linux编译

在linux下使用命令行GCC即可,将com_whyun_jni_chapter1_FirstDemo.hfirstdemo.c放到同一个目录下,然后运行脚本

saveDir=/opt/lib
gcc -g -Wall -I $JAVA_HOME/include/ -I $JAVA_HOME/include/linux -fPIC -shared -o $saveDir/libfirstdemo.so firstdemo.c

1.3.3 运行

运行java代码,最终输出

num:1,string:the first demo.

本文用到的源代码可以从http://git.oschina.net/yunnysunny/jni 获得到