最近搞了一个调用第三方so库做登录认证的任务,以前对JNI没什么概念,最近学习了 《java核心技术》 本地方法 一章,把自己写的一些例子记录一下。 自己C语言真是渣渣,所以所有的例子都在可以包括基本API的基础上尽可能简单。以下所有例子都是在centos 7中测试的,window不太熟。

调用本地方法

java调用本地方法,首先需要加载包含对应方法的so库(linux),一般使用下面这种方式加载so库。

1 public class Test{
2 static
3 {
4 //so库的名字是libTest.so
5 System.loadLibrary("Test");
6 }
7
8 public static native void hello();
9 }

在static代码块中加载so库,这样就能在这个类被classLoader 加载的时候就被载入。要想正确载入so,必须将so库放在java.library.path 指定的路径中,我们可以通过以下两种方式来指定java.library.path 的值

1. 配置 LD_LIBRARY_PATH 环境变量

2. 通过java的运行参数指定 -Djava.library.path= .....

当我们调用本地方法时,会在加载的so库中去寻找与我们所调用方法对应的本地方法,比如上面定义的hello方法,就应该有一个对应的本地方法为

JNIEXPORT void JNICALL Java_Test_hello(JNIEnv *, jclass)

我们可以使用javah产生这个一个头文件,在其中就包含了这个方法的声明。

我们编写完c文件后,就可以用它生成一个对应的so了

gcc -fPIC -I jdk/include -I jdk/include/linux -shared -o libTest.so Test.c

其中jdk是含有jdk的目录,以我的环境为例,jdk目录为 /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.79-2.5.5.1.el7_1.x86_64/, 配置JAVA_HOME指向这个目录,所以编译命令就是:

gcc -fPIC -I ${JAVA_HOME}/include -I ${JAVA_HOME}/include/linux -shared -o libTest.so Test.c

之所以要使用-I 参数指定这两个目录,是因为在其中包含了c文件需要的两个头文件, <jni.h>和 <jni_md.h>

总结出将一个本地方法链接到java程序中的步骤:

1)在java类中声明一个native方法

2)运行javah 得到一个本地方法需要的头文件

3)使用C实现本地方法

4)使用C代码编译出so文件,并将它放置在java.library.path中

5)使用java调用就可以了

下面的案例中重要的api都用红色标记了。

案例1:

计算两个int的和(传入int参数并返回int类型)

class Calc
{
static{
System.loadLibrary("Calc");
} public static native int add(int a, int b); public static void main(String[] args)
{
System.out.println(add(11,23));
}
}

对应的C代码:

#include <stdio.h>
#include "Calc.h" /* jint 对应着java 的int类型 */
JNIEXPORT jint JNICALL Java_Calc_add(JNIEnv *env, jclass jc, jint a, jint b)
{
jint ret = a + b;
return ret;
}

案例二:给传入的name加上hello前缀再返回(传入String参数并返回String类型)

class Hello
{
static
{
System.loadLibrary("Hello");
} public static native String hello(String name); public static void main(String[] args){
System.out.println(hello("zhangsan"));
}
}

对应的C代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Hello.h" /*拼接字符串 */
char* join(const char *s1, const char *s2)
{
char *result = malloc(strlen(s1)+strlen(s2)+1);//+1 for the zero-terminator
//in real code you would check for errors in malloc here
if (result == NULL) exit (1); strcpy(result, s1);
strcat(result, s2); return result;
} JNIEXPORT jstring JNICALL Java_Hello_hello(JNIEnv* env, jclass cl, jstring name)
{
/* 从java String 获得 C char* */
const char* cname;
cname = (*env)->GetStringUTFChars(env, name, NULL); char* hello_s = join("hello, ", cname); /* 从 C char* 再获得 java String */
jstring ret = (*env)->NewStringUTF(env, hello_s); /* 主动释放内存, 表明不再需要通过 name 来访问 cname*/
(*env)->ReleaseStringUTFChars(env, name, cname); return ret;
}

案例三: 在C代码中调用PrintWriter.print方法(调用java对象的实例方法)

感觉这种调用和反射基本类似。

import java.io.*;

public class Hello
{
static
{
System.loadLibrary("Hello");
} public static native void sayHello(PrintWriter out, String message); public static void main(String[] args)
{
PrintWriter out = new PrintWriter(System.out);
Hello.sayHello(out, "Hello world!\n");
out.flush();
} }

C代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Hello.h" /*java 的Object类型对应jobject */
JNIEXPORT void JNICALL Java_Hello_sayHello(JNIEnv* env, jclass jc, jobject out, jstring message)
{
const char* cmessage;
/*从 java String 得到 c char* */
cmessage = (*env)->GetStringUTFChars(env, message, NULL); /* 处理得到的字符串,加上前缀 */
const char* append = "I'm say: ";
char* result = (char*)malloc(strlen(cmessage) + strlen(append) + 1);
strcpy(result, append);
strcat(result, cmessage); /*从 c char* 得到 java String */
jstring jresult = (*env)->NewStringUTF(env, result); /* 主动释放, 不再需要通过message获得cmessage */
(*env)->ReleaseStringUTFChars(env, message, cmessage); /* 下面就是 调用PrintWriter.print(String) */ /* 获得class */
jclass class_PrintWriter = (*env)->GetObjectClass(env, out); /* 获得 method ID , 最后一个参数是 print方法的签名 返回值为void(V), 参数为java.lang.String */
jmethodID id_print = (*env)->GetMethodID(env, class_PrintWriter, "print", "(Ljava/lang/String;)V"); /* 调用方法 */
(*env)->CallVoidMethod(env, out, id_print, jresult);
}

案例四: 在C代码中调用System.getProperty静态方法(调用java静态方法)

public class Test
{
static
{
System.loadLibrary("Test");
} public static native String getClassPath(); public static void main(String[] args)
{
System.out.println(getClassPath());
} }

c代码实现:

#include <stdio.h>
#include "Test.h" JNIEXPORT jstring JNICALL Java_Test_getClassPath(JNIEnv* env, jclass jc)
{
/*获得System的class */
jclass class_System = (*env)->FindClass(env, "java/lang/System"); /*获得 getProperty 方法的 方法id */
jmethodID id_getProperty = (*env)->GetStaticMethodID(env, class_System, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;"); /* 执行 静态方法 */
jobject obj_ret = (*env)->CallStaticObjectMethod(env, class_System, id_getProperty, (*env)->NewStringUTF(env, "version")); return (jstring)obj_ret;
}

这个例子在运行的时候 增加 -Dversion=xxxx 就可以得到version运行参数了。

案例五: 在C中修改Employee的静态和实例属性(修改实例属性和静态属性)

public class Employee
{
static
{
System.loadLibrary("Employee");
}
public static String a = "Good Employee";
private String name;
private double salary; public Employee(String name, double salary)
{
this.name = name;
this.salary = salary;
} public String toString(){
return name + " " + salary;
} public native void raiseSalary(double byPercent); public static native void updateDescription(String description); public static void main(String[] args)
{
Employee e = new Employee("zhangsan", 1000);
System.out.println(e);
e.raiseSalary(0.1);
System.out.println(e); System.out.println("###############################"); System.out.println(e.a);
Employee.updateDescription("Bad Employee");
System.out.println(e.a); }
}

c代码:

#include <stdio.h>
#include "Employee.h" JNIEXPORT void JNICALL Java_Employee_raiseSalary(JNIEnv* env, jobject this_obj, jdouble byPercent)
{
/* get the class */
jclass class_Employee = (*env)->GetObjectClass(env, this_obj); /* get the field Id */
jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, "salary", "D"); //"D" 代表类型double /* get the field value */
jdouble salary = (*env)->GetDoubleField(env, this_obj, id_salary); salary *= 1 + byPercent / 100; /* set the field value */
(*env)->SetDoubleField(env, this_obj, id_salary, salary);
} JNIEXPORT void JNICALL Java_Employee_updateDescription(JNIEnv* env, jclass jc, jstring description)
{
/* get static class field */
/*一定要注意类的签名方式, 前面的L 和最后的;(分号)都不能少,那个分号不是分隔符,是签名的一部分 */
jfieldID desc_id = (*env)->GetStaticFieldID(env, jc, "a", "Ljava/lang/String;"); /* set new static description field */
(*env)->SetStaticObjectField(env, jc, desc_id, description);
}

案例六:访问修改数组

class Test
{
static
{
System.loadLibrary("Test");
} public static native void scaleArray(double[] arr); public static void main(String[] args)
{
double[] arr = {1.1, 2.2};
scaleArray(arr); for(double d : arr){
System.out.println(d);
}
}
}

C代码实现:

#include <stdio.h>
#include "Test.h" JNIEXPORT void JNICALL Java_Test_scaleArray(JNIEnv* env, jclass jc, jdoubleArray arr)
{
double scaleFactor = 2.0;
/*获得 一个指向 数组的指针 */
double* a = (*env)->GetDoubleArrayElements(env, arr, NULL); int i;
for(i = 0; i< (*env)->GetArrayLength(env, arr); i++)
a[i] = a[i] * scaleFactor; (*env)->ReleaseDoubleArrayElements(env, arr, a, 0);
}

案例七:在C中访问构造函数并构造对象

import java.util.Random;

public class Test
{
static
{
System.loadLibrary("Test");
} public static native int nextInt(); public static void main(String[] args)
{
System.out.println(nextInt());
}
}

在C代码中调用Random类的构造方法构造一个Random实例,然后调用nextInt实例方法。

#include <stdio.h>
#include "Test.h" JNIEXPORT jint JNICALL Java_Test_nextInt(JNIEnv* env, jclass jc)
{
/* 获得 Random 类, 注意表示类的字符串 */
jclass class_Random = (*env)->FindClass(env, "java/util/Random");
/* 获得 Random 构造器 方法id, "<init>"代表构造方法 */
jmethodID id_Random = (*env)->GetMethodID(env, class_Random, "<init>", "()V");
/* 构造一个Random类型的对象 */
jobject obj_random = (*env)->NewObject(env, class_Random, id_Random, NULL); /* 下面调用这个对象的 nextInt 方法 */
jmethodID id_nextInt = (*env)->GetMethodID(env, class_Random, "nextInt", "()I");
jint ret = (*env)->CallIntMethod(env, obj_random, id_nextInt, NULL);
return ret;
}

案例八: 在本地方法中处理异常

public class Test
{
static
{
System.loadLibrary("Test");
} /*
这里的luckyNumber方法纯粹测试目的:
当name为zhangsan时一定会抛出一个IllegalArgumentException异常
当name为lisi时,会调用Random.next(-10)主动抛出一个IllegalArgumentException异常,但是可以使用第二个参数来决定是否要抛出到 jvm
当name为其他值时,无异常
*/
public static native int luckyNumber(String name, boolean nativeHandleException); public static void main(String[] args)
{
System.out.println(Test.luckyNumber("zhangsan", false));
}
}

C代码:

#include <stdio.h>
#include <string.h>
#include "Test.h" JNIEXPORT jint JNICALL Java_Test_luckyNumber(JNIEnv* env, jclass jc, jstring name, jboolean nativeHandleException)
{
const char* cname;
cname = (*env)->GetStringUTFChars(env, name, NULL); /* 当name为zhangsan时我们主动抛出一个异常 */
if(strcmp(cname, "zhangsan") == 0)
{
jclass class_Exception = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
/* 主动抛出异常 */
(*env)->ThrowNew(env, class_Exception, "zhangsan is a bad guy, he can't be given a lucky number");
/* 本地方法抛出异常后并不会主动终止,所以要手动return */
return;
} /* 调用Random.nextInt 产生一个随机幸运数 */
jclass class_Random = (*env)->FindClass(env, "java/util/Random");
jmethodID id_Random = (*env)->GetMethodID(env, class_Random, "<init>", "()V");
jobject obj_random = (*env)->NewObject(env, class_Random, id_Random, NULL); jmethodID id_nextInt = (*env)->GetMethodID(env, class_Random, "nextInt", "(I)I");
jint ret; /* 当name为lisi时,我们使用负数来作为nextInt的参数,从而让他抛出一个异常 */
if(strcmp(cname, "lisi") == 0)
{
ret = (*env)->CallIntMethod(env, obj_random, id_nextInt, (-10) ); /*检查是否有异常挂起 */
jboolean hasException = (*env)->ExceptionCheck(env);
/*当有异常挂起并且要求在native中主动处理异常时,主动clear,这样就不会通知 虚拟机 了*/
if(hasException && nativeHandleException)
{
/* 主动清除挂起的异常 */
(*env)->ExceptionClear(env);
printf("the exception is handled in native function/n");
}else if(hasException){
return;
}
}else{
ret = (*env)->CallIntMethod(env, obj_random, id_nextInt, 10);
} return ret;
}

java 本地方法(JNI)的更多相关文章

  1. java本地方法如何调用其他程序函数,方法详解2

    Java调用本地方法(JNI浅谈) (2006-11-27 14:55:36) 转载▼   分类: Java类文章                本人在项目开发实践中的总结和体会     前段时间公司 ...

  2. Android Java访问本地方法(JNI)

    当功能需要本地代码实现的时候,Java 代码就需要调用本地代码. 在调用本地代码时,首先要保证本地代码被加载到 Java 执行环境中并与 Java 代码连接在一起,这样 Java 代码在调用本地方法时 ...

  3. java本地方法如何调用其他程序函数,方法详解

    JNI是Java Native Interface的缩写,中文为JAVA本地调用.从Java 1.1 开始,Java Native Interface (JNI)标准成为java平台的一部分,它允许J ...

  4. java高级用法之:无所不能的java,本地方法调用实况

    目录 简介 JDK的本地方法 自定义native方法 总结 简介 相信每个程序员都有一个成为C++大师的梦想,毕竟C++程序员处于程序员鄙视链的顶端,他可以俯视任何其他语言的程序员. 但事实情况是,无 ...

  5. JAVA本地方法详解,什么是JAVA本地方法?

    一. 什么是Native Method   简单地讲,一个Native Method就是一个java调用非java代码的接口.一个Native Method是这样一个java的方法:该方法的实现由非j ...

  6. java本地方法

    一. 什么是Native Method   简单地讲,一个Native Method就是一个java调用非java代码的接口.一个Native Method是这样一个java的方法:该方法的实现由非j ...

  7. WebView js 调用Java本地方法

    webView = (WebView) this.findViewById(R.id.webview); WebSettings webSettings = webView.getSettings() ...

  8. java native:Java本地方法调用(jni方式)

    https://www.cnblogs.com/zh1164/p/6283831.html

  9. Java本地方法(native方法)的实现

    Java不是完美的,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展Java程序的功能. 可以将 ...

随机推荐

  1. IT职业:2021年掌握的10项关键技能

    原文:https://enterprisersproject.com/article/2020/12/it-careers-10-critical-skills-master-2021 科技行业似乎在 ...

  2. 自定义 demo 集合

    各种写着玩的自定义控件demo 有时网上看到一些比较有意思的开源项目,有时间的话就会自己也撸一个出来,但是一般只关注实现样式.动画等,不会太去细致完整地完成,俗称占个坑~ 持续更新中... githu ...

  3. [leetcode]82. Remove Duplicates from Sorted List

    第一题:遍历链表,遇到重复节点就连接到下一个. public ListNode deleteDuplicates(ListNode head) { if (head==null||head.next= ...

  4. windows和Linux的文件路径

    (1)windows的文件路径格式"E:\Python\workplace\codes"单反斜杠的方式,但是在很多编程语言中会不认识"\"字符,可能会把它识别成 ...

  5. Android开发用到的几种常用设计模式浅谈(一):组合模式

    1:应用场景 Android中对组合模式的应用,可谓是泛滥成粥,随处可见,那就是View和ViewGroup类的使用.在android UI设计,几乎所有的widget和布局类都依靠这两个类.组合模式 ...

  6. Android多线程消息处理机制

    (1)主线程和ANR 主线程:UI线程,界面的修改只能在主线程中,其它线程对界面进行修改会造成异常.这样就解决了多线程竞争UI资源的问题. 一旦主线程的代码阻塞,界面将无法响应,这种行为就是Appli ...

  7. 并发编程之JMM&Volatile(一)

    并发 很多程序员应该对并发一词并不陌生,并发如同一把双刃剑,如果使用得当,可以帮助我们更好的压榨硬件的性能,反之,也会产生一些难以排查的问题.这里,先简单介绍下并发的几个基本概念. 进程与线程 进程: ...

  8. IDEA:配置Tomcat并运行应用

    1.File->ProjectStructre->Artifacts 如下界面 3.下一步:如图所示 4.选择相应的Module就行 5.第一次运行程序时最好选择运行的配置,否则可能运行的 ...

  9. 机器学习笔记·adaboost

    一.算法简介 Adaboost算法是一种集成算法,所谓集成算法就是将多个弱的分类器组合在一起变成一个强的分类器.弱分类器通常是指分类效果比随机分类稍微好一点的分类器.就像我们在做一个重要决定的时候,通 ...

  10. Python作业---内置数据类型

    实验2 内置数据类型 实验性质:验证性 一.实验目的 1.掌握内置函数.列表.切片.元组的基本操作: 2.掌握字典.集合和列表表达式的基本操作. 二.实验预备知识 1.掌握Python内置函数的基/本 ...