java 本地方法(JNI)

最近搞了一个调用第三方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;
}
时间: 2024-10-02 23:48:27

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

JNI(Java本地方法调用)

windows:中C/C++是.dll表示调用的动态链接库,而linux是.so表示动态链接库 JNI(Java Native Interface)的书写步骤 ·编写带有native声明的方法的java类      ·使用javac命令编译所编写的java类      ·使用javah ?jni java类名生成扩展名为h的头文件      ·使用C/C++(或者其他编程想语言)实现本地方法      ·将C/C++编写的文件生成动态连接库 JNI(Java Native Interface)调

WebView js 调用Java本地方法

webView = (WebView) this.findViewById(R.id.webview); WebSettings webSettings = webView.getSettings(); webSettings.setJavaScriptEnabled(true); webView.addJavascriptInterface(new Object(){ /** * 这个注解,是4.0一下,别人可以随便调用本地方法.缺少安全性, * 4.0以上,为了防止别人调用其他的方法.加上了

Java虚拟机栈和本地方法栈

Java虚拟机栈的特征 线程私有 后进先出(LIFO)栈 存储栈帧,支持Java方法的调用.执行和退出 可能出现OutOfMemoryError异常和StackOverflowError异常 Java本地方法栈的特征 线程私有 后进先出(LIFO)栈 作用是支撑Native方法的调用.执行和退出 可能出现OutOfMemoryError异常和StackOverflowError异常 有一些虚拟机(如HotSpot)将Java虚拟机栈和本地方法栈合并实现 栈帧的概念和特征 Java虚拟机栈中存储的

JVM 运行时数据区:程序计数器、Java 虚拟机栈和本地方法栈,方法区和堆

Java 虚拟机可以看作一台抽象的计算机,如同真实的计算机,它也有自己的指令集和运行时内存区域. Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存(运行时内存区域)划分为若干个不同的数据区域. 如下图所示: 一.程序计数器 Program Counter Register 定义:程序计数器是当前线程所执行字节码的行号指示器. 原因:Java 中的多线程是线程间轮流切换并需要 CPU 给予时间片的方式实现的.在任何一个确定的时刻,都只有一个线程在执行指令.为了线程间轮流切换后能够快

Java clone方法(下)

1.最终调用的是一个JNI方法,即java本地方法,加快速度 2.使用clone方法,分为浅复制.深复制,这里直接使用网上抄来的案例来说明吧: 说明: 1)为什么我们在派生类中覆盖Object的clone()方法时,一定要调用super.clone()呢?在运行时刻,Object中的clone()识别你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中. 2)继承自java.lang.Object.clone()方法是浅层复制.一下代码可以

浅谈Java的本地方法实现JNI

最近做了一个数据结构课上迷宫求解的问题,要求在栈结构上最好能带一个图形界面,将找到的迷宫路径展示出来.原本打算连图形界面一起用C语言写,但是C语言的图形界面如果不调用操作系统的图形库真的很难看,所以选择了Java写图形界面,C语言写算法的这样一个混合编程的方案.这里不对Java图形界面的具体实现和C的算法做解释,只介绍Java和C的“连接部分”. 使用本地方法的前提 虽然现在Java的执行效率的确得到了很大的提升,但是一旦遇到大量计算操作时,还是不如C语言执行快.这种情况下,我们可以考虑将关键的

java native interface JNI 调用Java方法

在上一篇文章中介绍了JNI,以及java调用JNI,这篇讲一下 JNI调用java方法. 通过使用合适的JNI函数,你可以创建Java对象,get.set 静态(static)和 实例(instance)的域,调用静态(static)和实例(instance)函数.JNI通过ID识别域和方法,一个域或方法的ID是任何处理域和方法的函数的必须参数. 下表列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数.每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对

JNI/NDK开发指南(二)——JVM查找java native方法的规则

通过第一篇文章,大家明白了调用native方法之前,首先要调用System.loadLibrary接口加载一个实现了native方法的动态库才能正常访问,否则就会抛出java.lang.UnsatisfiedLinkError异常,找不到XX方法的提示.现在我们想想,在Java中调用某个native方法时,JVM是通过什么方式,能正确的找到动态库中C/C++实现的那个native函数呢? JVM查找native方法有两种方式: 1> 按照JNI规范的命名规则 2> 调用JNI提供的Regist

使用JNA替代JNI调用本地方法

JNA全称是Java Native Access,是Sun推出的一种调用本地方法技术,比起它的同门师兄JNI,JNA大大简化了调用本地方法的过程,使用也比较方便, JNA是在JNI的基础上完善的,用青出于蓝而胜于蓝来形容一点不为过,下面看一下JNI的调用过程: 使用JNI你得完成上面这些步骤,比较麻烦,而是用JNA就省事多了,基本上不需要脱离Java环境就可以完成.     JNA项目主页是https://jna.dev.java.net/, 目前最新的版本是3.2.4 .下载时记得将自带的Ex