Linux下Java JNI调用C语言动态链接库

Posted by Jason Blog on May 25, 2017

JNI是Java Native Interface的简写,一般译作Java本地接口.Java可以通过JNI调用C/C++库,这在安卓开发中无疑是非常方便的.因为Android内核是C语言写的,Android又对内核接口进一步封装,这层称为HAL层,一般是C++写的.而Android应用程序是Java写的,Java怎么操作硬件接口?这就是JNI层存在的作用.

在Java代码中通过JNI调用C函数的步骤如下.(参考

  • 编写Java代码
  • 编译Java代码
  • 生成C语言头文件
  • 编写C代码
  • 生成C共享库
  • 运行Java程序

第一步,编写Java代码

要调用C库函数,Java类中的方法必须用native修饰.一般在静态代码块中加载本地代码库,调用System.loadLibrary()函数.

class HelloJNI {
	// native method
	native void printHello();
	native void printString(String str);

	// load library
	static {
		System.loadLibrary("hellojni");//在Linux下指libhellojni.so
	}

	public static void main(String args[]) {
		
		HelloJNI myJNI = new HelloJNI();
		// call native method
		myJNI.printHello();
		myJNI.printString("Hello World from printString fun");
	}
}

这里关于静态代码块,多说几句.

1.它是随着类的加载而执行,只执行一次,并优先于主函数.具体说,静态代码块是又类调用的,类调用时,先执行静态代码块,然后才执行主函数的.

2.静态代码块其实是给类初始化化的,而构造代码块是给对象初始化的.

3.静态代码块中的变量是局部变量,与普通函数中的局部变量性质没有区别.

4. 一个类可以有多个静态代码块.

第二步,编译Java代码

javac HelloJNI.java

这一步很简单,不多做介绍.

第三步,生成C语言头文件

若想创建本地方法的映射C函数,必须先生成函数的原型,函数原型位于C/C++头文件中,Java提供了javah工具,用来生成包含函数原型的头文件.使用方法如下.

javah <包含以native关键字声明的方法的Java类名称>

来看看生成的HelloJNI.h中的内容.

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

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNI
 * Method:    printHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_printHello
  (JNIEnv *, jobject);

/*
 * Class:     HelloJNI
 * Method:    printString
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_HelloJNI_printString
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

观察函数原型名称,可以发现函数名遵循一定的命名规则.JNI支持的函数命名形式为”Java/类名/地方法名”.了解这些命名规则,通过函数命名即可推断出JNI本地函数与哪个Java类的哪个本地方法相对应.比如Java_HelloJNI_printHello()函数原型,通过其名称,可以知道它与HelloJNI类中的printHello()方法对应.至于函数原型的参数,可以参考JNI相关的文档,在此不多做介绍.

JNI提供了一套与Java数据类型相对应的Java本地类型,使得本地语言可以使用Java数据类型,如下表所示.

Java类型 Java本地类型 占用内存大小
byte jbyte 1
short jshort 2
int jint 4
long jlong 8
float jfloat 4
double jdouble 8
char jchar 2
boolean jboolean 1
void void  

第四步,编写C/C++代码

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

/*
 * Class:     HelloJNI
 * Method:    printHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *env, jobject obj)
{
	printf("Hrllo World!\n");
	return ;
}
/*
 * Class:     HelloJNI$
 * Method:    printString
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *env, jobject obj, jstring string)
{
	const char *str = (*env)->GetStringUTFChars(env, string, 0);
	printf("%s!\n", str);

	return ;
}

编译

gcc -I/usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -shared -fPIC -o libhellojni.so hellojin.c # 生产共享库

第五步,运行Java程序

运行

java HelloJNI

发现出错

Exception in thread “main” java.lang.UnsatisfiedLinkError: no hellojni in java.library.path

at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1889)
at java.lang.Runtime.loadLibrary0(Runtime.java:849)
at java.lang.System.loadLibrary(System.java:1088)
at HelloJNI.<clinit>(HelloJNI.java:8)

通知动态链接程序此共享文件的路径

export LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH

再次运行.

Hello World!
Hello World from printString fun!