我们来看Java和C的互操作,这也是一个困然我很久的问题。
C这部分采用之前的calc 复习一下libcalc.a是静态库, libcalc.so是动态库 (so means share objects)
JNI 开发流程 - JNI/NDK 开发指南 - 极客学院Wiki
JNI 开发流程主要分为以下 4 步:
- 编写Java native申明
- 生成jni c header
- 实现jni c
- 在jni c中link linux so
package cn.nonocast;
public class App {
public static void main(String[] args) {
System.out.println("hello world");
System.out.println(calc(1, 2));
}
public static native int calc(int a, int b);
static {
System.loadLibrary("calc");
}
}
gradle run
> Task :run FAILED
Exception in thread "main" java.lang.UnsatisfiedLinkError: no calc in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1122)
at cn.nonocast.App.<clinit>(App.java:12)
FAILURE: Build failed with an exception.
javah -jni -classpath ./build/classes/java/main -d ./jni cn.nonocast.App
很神奇的出现jni/cn_nonocast_App.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class cn_nonocast_App */
#ifndef _Included_cn_nonocast_App
#define _Included_cn_nonocast_App
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: cn_nonocast_App
* Method: calc
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_cn_nonocast_App_calc
(JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
编写对应实现cn_nonocast_App.c
#include <stdio.h>
#include "cn_nonocast_App.h"
// libcalcJNI.jnilib
JNIEXPORT jint JNICALL Java_cn_nonocast_App_calc(JNIEnv* env, jclass, jint a, jint b) {
printf("Hello World!\n");
return 0;
}
先来个mock,然后Makefile
CC=gcc
JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home
libcalc.jnilib : cn_nonocast_App.c
$(CC) -I ${JAVA_HOME}/include -I ${JAVA_HOME}/include/linux -fPIC -shared $^ -o $@
MacOS下的JAVA_HOME设置, 首先通过java_home找到java安装目录,
$ /usr/libexec/java_home
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home
也可以直接在.zshrc中加入export JAVA_HOME=$(/usr/libexec/java_home)
echo $JAVA_HOME
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home
提示找不到jni_md.h, ln一下,
sudo ln -s darwin/jni_md.h jni_md.h
ok, 这时就应该生成libcalcJNI.jnilib
> Task :run FAILED
Exception in thread "main" java.lang.UnsatisfiedLinkError: no calc in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1122)
at cn.nonocast.App.<clinit>(App.java:12)
通过程序来看一下java.library.path
/Users/nonocast/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.
我们优先考虑'.',将jnilib link到build/libs
ln -s ../../jni/libcalc.jnilib .
然后运行,
$ java -jar app.jar
hello world
hello world from jni
0
#include <stdio.h>
#include "cn_nonocast_App.h"
int add(int, int);
// libcalcJNI.jnilib
JNIEXPORT jint JNICALL Java_cn_nonocast_App_calc(JNIEnv* env, jclass c, jint a, jint b) {
return add(a, b);
}
其实这步和nodejs非常类似,过渡一下,可以参考之前的app.cc
#include <iostream>
extern "C" {
#include "calc.h"
}
int main() {
std::cout << "1+2=" << add(2,3) << std::endl;
return 0;
}
这里这需要解决一个link问题, 我们把libcalc.so放在~/lib下,
$(CC) -shared -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -L~/lib -lcalc $^ -o $@
运行
$ java -jar app.jar
hello world
3
这样就完成了java -> jni -> so 的过程。
漏了gradle, 只需要指定run的java.library.path就ok了
run {
systemProperty "java.library.path", "$projectDir/jni"
}