因此封装了lua 的战斗接口,将lua 封装成可以java调用的动态链接库。这样的解决方案使用了JNI的技术。今天来聊下JNI的一些知识点。因为有一段时间没搞C 了,还是得从头开始。
JNI是java native interface的缩写,是用来从java调用C /C代码,也可以从C /C调用Java代码。
1、环境安装
1、下载MinGW压缩包
下载地址:https://sourceforge.net/projects/mingw-w64/files/Toolchains targetting Win64/Personal Builds/mingw-builds/
下载后解压文件 出现 mingw64文件夹。
2、下载clion
因为Visual studio 的安装包实在太大了,懒得下,所以选择了clion.
下载地址:https://www.jetbrains.com/zh-cn/clion/download/#section=windows
注:安装的时候 一步一步next 就可以了了,破解教程可以在网上搜索。
3、配置C 开发环境
这样基本的环境就算完成了,下面开始搞个例子吧。
2、走个例子
2.1 新建C程序
Clion ->file ->新建项目。
下图中填入项目名,并且选择 shared 动态链接库。
注:动态库根据系统的不同会生成同的链接库,win下生成.dll,linux 下生成.so
2.2 拷贝 jni.h 和 jni_md.h 到目录下
文件所在地址:
代码语言:javascript复制C:Program FilesJavajdk1.8.0_291includejni.h
C:Program FilesJavajdk1.8.0_291includewin32jni_md.h
注:在上面两个目录直接找到两个文件,拷贝
(因为你还可能开发其他的工程)到项目根目录就可以了
2.3 输入代码
头文件
代码语言:javascript复制#ifndef TESTJNI_LIBRARY_H
#define TESTJNI_LIBRARY_H
#include "jni.h"
#include "jni_md.h"
JNIEXPORT void JNICALL Java_com_pdool_Main_sayHello(JNIEnv * jobject);
#endif //TESTJNI_LIBRARY_H
.c 文件
代码语言:javascript复制#include "library.h"
#include <stdio.h>
void JNICALL Java_com_pdool_Main_sayHello(JNIEnv * jobject) {
printf("this is C print");
}
2.4 执行构建
生成对应的libxxxx.dll,路径为 D:clionTestJnicmake-build-debuglibTestJni.dll,也就是上图红色的那个目录下
2.4 新建java 项目,可以新建一个Maven 或者普通的Java项目都可以,我这里是选择了普通的项目。
2.5 Java 代码
代码语言:javascript复制package com.pdool;
public class Main {
public static native void sayHello();
static {
System.loadLibrary("libTestJni"); // 加载实现了native函数的动态库,只需要写动态库的名字
}
public static void main(String[] args) {
sayHello();
// String arch = System.getProperty("sun.arch.data.model");
// System.out.println("arch:" arch);
}
}
2.6 配置 library path
library path 是告诉 虚拟机 去哪里查找链接库,在使用System.loadLibrary("libTestJni");的搜索路径
2.7 执行Java代码,看下调用效果
恭喜你,项目跑起来了。下面一起理解下技术细节吧。
3、技术分析
3.1 函数定义
代码语言:javascript复制JNIEXPORT void JNICALL Java_com_pdool_Main_sayHello(JNIEnv * jobject);
extern "C“:
JNI函数声明声明代码是用C 语言写的,所以需要添加extern "C"声明;如果源代码是C语言声明,则不需要添加这个声明
JNIEXPORT:
这个关键字表明这个函数是一个可导出函数。类似Java的public函数,每一个C/C 库都有一个导出函数列表,只有在这个列表里面的函数才可以被外部直接调用。
JNICALL:
说明这个函数是一个JNI函数,用来和普通的C/C 函数进行区别。
Void:
返回值类型
JNI函数名原型
:Java_ JNI方法所在的完整的类名,把类名里面的”.”替换成”_” 真实的JNI方法名,这个方法名要和Java代码里面声明的JNI方法名一样。
env 参数
:是一个执行JNIENV函数表的指针。
3.2 JNIEnv 解析
JNIEnv 概念 : 是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境 ;
调用 Java 函数 : JNIEnv 代表 Java 运行环境, 可以使用 JNIEnv 调用 Java 中的代码;操作 Java 对象 : Java 对象传入 JNI 层就是 Jobject 对象, 需要使用 JNIEnv 来操作这个 Java 对象;
JNIEnv只在当前线程中有效。本地方法不能将JNIEnv从一个线程传递到另一个线程中。相同的 Java 线程中对本地方法多次调用时,传递给该本地方法的JNIEnv是相同的。但是,一个本地方法可被不同的 Java 线程所调用,因此可以接受不同的 JNIEnv。
4、这次测试中遇到的问题
1、找不到dll
配置的参数为 vm option ,不是程序参数
-Djava.library.path=D:clionTestJnicmake-build-debug
路径仅仅只到最后dll 所在的目录
2、找不到jni.h,jni_md.h
拷贝jni.h 到 c工程的目录。
3、打出来dll 无法运行,版本不匹配
因为我使用的MinGW 是64 的版本,但是我jdk 安装的版本是 32 的位的,导致运行报错
可以在控制台 使用java -version
,如果没写64-Bit 就是32的,调用dll 会报错
重新安装64 位的jdk解决。
4、修改函数名导致不匹配
代码语言:javascript复制Exception in thread "main" java.lang.UnsatisfiedLinkError: xxx()V
因为在测试期间,我修改了一次函数的名字,我只在.h 中同步修改了函数的名字,但是.c 中没有同步修改,导致调用报错。
修改函数名一致就可以了。
5、Java 和 C 数据类型的对照表
Java 和C 之间有很多类型不是相同的,下面列举一下数据类型的对照关系,在使用的时候对照就可以了,不用记。
1、基本类型的对应
代码语言:javascript复制# include <inttypes.h> /* C99 */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#else
typedef unsigned char jboolean; /* unsigned 8 bits */
typedef signed char jbyte; /* signed 8 bits */
typedef unsigned short jchar; /* unsigned 16 bits */
typedef short jshort; /* signed 16 bits */
typedef int jint; /* signed 32 bits */
typedef long long jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#endif
2、引用类型
6、总结
java 和C,C 之间的调用主要是函数格式的定义,然后加载动态链接库,直接访问就好了。记住规则就好了,没什么难的。