0x00 Java部分

首先有一段Java代码,在main函数中引用了会包含native调用的演示函数。至于使用native的具体场景,相信你已经从其他地方了解,此处不在赘述。

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。
package dxcyber409;

public class Test {
    
    static {
        System.load("D:/test.dll");
    }

    static class Cls {
        private native String f(int i, String s);
        public void test() {
            String s = f(10, "asd");
            System.out.println("Your value:" + s);
        }
    }

    public static void main(String[] args) throws Exception {
        Cls cls = new Cls();
        cls.test();
    }

}

这段代码有明显的平台倾向,你可以看出笔者用的是Windows平台,从而加载的是DLL动态链接库。如果你正在使用Unix派系的系统,那么动态链接库的后缀应该是*.so。又或者你不想硬编码路径和后缀名,那么可以使用System.loadLibrary函数。

首先静态代码块和静态类Cls会由JVM进行最优先的加载(执行),随后的main方法能够顺利执行。当然这段代码是不能直接运行的,让我们修复缺失的动态链接库部分。

0x01 JNI部分

从Java到本地代码的调用过程可以这样来描述:Java -> JNI Bridge -> Native Code。由此可知我们需要自己编写代码,生成动态运行库。

为了与JNI Bridge能够兼容接入,我们还需要一套标准的声明文件,对于C++这种声明文件就是.h头文件。Java SDK套件下的javah命令就提供了这种自动生成操作的支持。

编写一个Java JNI的DEMO 随笔 第1张

图1.javah用法帮助

javah命令支持从已经编译好的class文件中提取出需要实现的native函数接口,然后生成JNI Bridge标准的C++风格.h头文件。

编写一个Java JNI的DEMO 随笔 第2张

图2.Java代码编译后的目录

编译Java代码后可以得到class文件,可以在资源管理器中查看一下编译后的目录(图2)。按照Java代码的结构,和编译后的路径编写javah构建语句。

D:\RTEws\Java\jdk1.8.0_121\bin>javah -d "E:\Workspace\NetBeans\DXCyber409\src\main\java\dxcyber409\jni" -classpath "E:\Workspace\NetBeans\DXCyber409\target\classes" -jni dxcyber409.Test$Cls

在src/.../jni目录下得到dxcyber409_Test_Cls.h文件,有了这个标准声明就可以放心编写C++实现了。

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

#ifndef _Included_dxcyber409_Test_Cls
#define _Included_dxcyber409_Test_Cls
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     dxcyber409_Test_Cls
 * Method:    f
 * Signature: (ILjava/lang/String;)D
 */
JNIEXPORT jstring JNICALL Java_dxcyber409_Test_00024Cls_f
  (JNIEnv *, jobject, jint, jstring);

#ifdef __cplusplus
}
#endif
#endif

 

编写一个Java JNI的DEMO 随笔 第3张

图3.创建Visual Studio项目

此时当然需要创建一个Visual Studio的动态链接库项目,如图3。

此外,细心的你会发现dxcyber409_Test_Cls.h包含了jni.h文件,要想通过编译得把这个文件及其依赖一同包括到项目中(图4)。简单的做法就是把Java SDK套装include目录下的所有.h头文件(由于笔者是在win平台,也包括win32目录下的.h文件),复制一份放到项目源码目录下,并在VS项目中包含这些文件(图5)。

编写一个Java JNI的DEMO 随笔 第4张

图4.Java SDK套装include目录结构

编写一个Java JNI的DEMO 随笔 第5张

图5.完成所有.h头文件复制的项目源码目录

在dxcyber409_Test_Cls.h文件中,由于头文件是我们自己在源码目录提供的,而不是使用标准库头文件,因此注意将include <jni> 修改为include "jni.h"。

随后就是实现该头文件,创建一个dxcyber409_Test_Cls.cpp文件后编写一些简单的代码。

#include "stdafx.h"
#include "dxcyber409_Test_Cls.h"

JNIEXPORT jstring JNICALL Java_dxcyber409_Test_00024Cls_f
(JNIEnv *env, jobject obj, jint a1, jstring a2)
{
    return a2;  // 抛弃第一个int参数,直接返回第二个String参数
}

随后直接编译生成即可,找到生成目录的DLL,移动到D:\test.dll路径,DEMO运行成功。

编写一个Java JNI的DEMO 随笔 第6张

图6.DEMO运行结果

 

PS.如果出现x86架构和x64架构不兼容的提示,在VS中切换架构重新编译即可。

java.lang.UnsatisfiedLinkError: E:\Workspace\C++\JavaNative\Debug\JavaNative.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
    at java.lang.ClassLoader$NativeLibrary.load(Native Method)
    at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1941)
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1824)
    at java.lang.Runtime.load0(Runtime.java:809)
    at java.lang.System.load(System.java:1086)
    at dxcyber409.Test.<clinit>(Test.java:6)
Exception in thread "main" 

编写一个Java JNI的DEMO 随笔 第7张

 

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄