购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.11 使用JNI

在密码学领域搞开发,无论是看密码算法的文献资料,还是调用业界著名的密码函数库、大数库、数学库等,都免不了要和C语言打交道(没办法,谁叫C语言是前辈呢)。为了避免重复编程或在核心运算部分提高性能,对于一些基础的算法,我们完全可以利用现有的C函数库来帮忙。这就涉及在Java中调用C函数的问题。幸亏Java给我们提供了JNI(Java Native Interface,Java原生接口)机制,使得在Java中调用C函数易如反掌。

JNI是Java语言的本地编程接口。在Java程序中,我们可以通过JNI实现一些用Java语言不便实现的功能,具体如下:

1)标准的Java类库没有提供应用程序所需要的功能,通常这些功能是与平台相关的(只能由其他语言编写)。

2)希望使用一些已经有的类库或者应用程序,而它们并不是用Java语言编写的。

3)程序的某些部分对速度要求比较苛刻,选择用汇编或者C语言来实现并在Java语言中调用它们。

4)为了应用的安全性,会将一些复杂的逻辑和算法通过本地代码(C或C++)来实现,本地代码比字节码难以破解。

Java可以通过JNI调用C/C++的库,这对于那些对性能要求比较高的Java程序或者Java无法处理的任务无疑是一个很好的方式。Java中的JNI开发流程主要分为以下6步:

步骤01 编写声明native方法的Java类。

步骤02 将Java源代码编译成Class字节码文件。

步骤03 用javah -jni命令生成.h头文件(javah是JDK自带的一个命令,-jni参数表示将Class中用native声明的函数生成JNI规则的函数)。

步骤04 用本地代码实现.h头文件中的函数。

步骤05 将本地代码编译成动态库(Windows下为*.dll,Linux/UNIX下为*.so,Mac OS X下为*.jnilib)。

步骤06 将动态库复制到java.library.path本地库搜索目录下,并运行Java程序。

下面我们通过实例来说明。

【例2.5】 第一个JNI程序

1)打开Eclipse,设置工作区路径,注意本例用编号作为工作区文件夹名(这是为了给读者演示多样性,所以本例没有用myws作为工作区名称)。新建一个Java工程,工程名是SimpleHello。在工程中新建一个类,在New Java Class对话框中,在Package框中输入包名com.study.jni.demo.simple,在Name框中输入类名SimpleHello,同时勾选public static void main(String []args]选项,如图2-44所示。

056-1

图2-44

这里简单解释一下package(程序包,简称为包),Java提供了程序包机制,用于区别类名的命名空间。程序包机制把功能相似或相关的类或接口组织在同一个包中,以方便类的查找和使用。如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名是不同的,不同包的类名可以相同,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免命名冲突。另外,包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。Java使用包这种机制是为了防止命名冲突,以及对类和接口进行分类,以便更好地组织和维护它们,提高可重用性。

设置好如图2-44所示的各项后,单击Finish按钮以关闭对话框。此时Eclipse会显示出SimpleHello.java的编辑窗口,在SimpleHello.java中输入如下代码:

package com.study.jni.demo.simple;

 public class SimpleHello {
     public static native String sayHello(String name);

     public static void main(String[] args) {
         String name = "Ljy";
         String text = sayHello(name);
         System.out.println("after native, java shows:" + text);
     }

     static {
         System.loadLibrary("hello");  //hello.dll要放在系统路径下,比如c:\windows\
     }
}

在main函数中调用sayHello函数。注意sayHello()方法的声明,它有一个关键字native,表明这个方法是使用Java以外的语言实现的。该方法不包括在本程序业务功能的实现中,因为我们要用C/C++语言来实现它。注意System.loadLibrary("hello")这句代码,它是在静态初始化块中定义的,系统用来载入hello库,就是在后文所述要生成的hello.dll。

2)生成.h文件。打开命令行窗口,然后进入SimpleHello.java所在目录,这里是D:\eclipse-workspace\2.5\SimpleHello\src\com\study\jni\demo\simple\,引用了程序包,那么路径就变长了。输入如下命令:

javac SimpleHello.java -h .

注意,-h后面有一个空格,然后有一个黑点。选项-h表示需要生成JNI的头文件,h后面有个空格,然后加了黑点,黑点表示在当前目录下生成头文件,如果需要指定目录,可以把点改成文件夹名称(文件夹会自动新建)。该命令执行后,会在同一目录下生成两个文件:SimpleHello.class和com_study_jni_demo_simple_SimpleHello.h,后者就是我们所需的头文件,这个文件不要去修改,后面的VC工程中要用到。

3)编写本地实现代码。现在我们要用C/C++语言实现Java中定义的方法,其实就是新建一个DLL程序(DLL是指动态链接库)。

打开VC 2017,按Ctrl+Shift+N组合键打开“新建项目”对话框,然后在界面左侧选择“Windows桌面”,在右侧选择“Windows桌面向导”,输入工程名为“hello”,并设置工程存放的位置,如图2-45所示。

057-1

图2-45

单击“确定”按钮,随后出现“Windows桌面项目”对话框,选择“应用程序类型”为“动态链接库(.dll)”,并撤选“预编译标头”复选框,如图2-46所示。

058-1

图2-46

单击“确定”按钮,此时一个DLL工程就建立起来了。在VC解决方案中双击hello.cpp,然后在编辑框中输入如下代码:

#include "header.h"
#include "jni.h"
#include "stdio.h"
#include "string.h"
#include "com_study_jni_demo_simple_SimpleHello.h"
JNIEXPORT jstring JNICALL Java_com_study_jni_demo_simple_SimpleHello_ sayHello(JNIEnv *env, jclass cls, jstring j_str)
{
    const char *c_str = NULL;
    char buff[128] = { 0 };
    jboolean isCopy;
    c_str = env->GetStringUTFChars(j_str, &isCopy);   //生成native的char指针
    if (c_str == NULL)
    {
        printf("out of memory.\n");
        return NULL;
    }
     printf("From Java String:addr: %x  string: %s  len:%d  isCopy:%d\n", c_str, c_str, strlen(c_str), isCopy);
    sprintf_s(buff, "hello %s", c_str);
    env->ReleaseStringUTFChars(j_str, c_str);
    return env->NewStringUTF(buff);    //将C语言字符串转化为java字符串
}

这段代码很简单,主要就是把Java传来的参数打印出来。其中jni.h是JDK自带的文件,我们需要为工程添加JDK所在的路径,在VC菜单栏中选择“项目→属性→配置属性→C/C++”,然后在右边“平台”下选择“x64”(因为我们要生成64位的DLL),并在“附加包含目录”框中输入:%JAVA_HOME%\include; %JAVA_HOME%\include\win32,如图2-47所示。

059-1

图2-47

然后单击“确定”按钮关闭对话框,把D:\eclipse-workspace\2.5\SimpleHello\src\com\study\jni\demo\simple\下的com_study_jni_demo_simple_SimpleHello.h复制到VC工程目录下,并在VC中添加该头文件,在VC工具栏上选择解决方案平台为x64,如图2-48所示。

059-2

图2-48

按F7键生成解决方案,此时将在hello\x64\Debug下生成hello.dll,把该文件复制到C:\Windows下。至此,VC本地代码开发工作就完成了。

4)重新回到Eclipse中,按Ctrl+F11组合键来运行工程,随后就可以在下方控制台窗口中看到该程序的输出,如图2-49所示。

059-3

图2-49

我们看到不但打印了VC程序中的语句,也打印了VC函数的返回值(字符串:hello Ljy)。从这个例子可以验证Java向本地函数传参数,并成功获取了返回值。有人可能不喜欢在C:\Windows下添加文件,没关系,我们可以把hello.dll放到Java工程目录下或者任意一个目录下,只要在Java中指定绝对路径即可。比如我们把hello.dll放到D:盘下,则在Java程序中就要写成绝对路径: SahmydpI2vqTruRSOJpPJzjJE+PjW/3yBI/70Wz3j3QGxbZcnNDAlromJCvCeNQB

System.load("d:/hello.dll"); //写绝对路径时,后缀名.dll也要写出来,要调用load函数
点击中间区域
呼出菜单
上一章
目录
下一章
×