前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >打通Java和C 之间的传送门,JNI从0 到1的保姆级教程

打通Java和C 之间的传送门,JNI从0 到1的保姆级教程

作者头像
香菜聊游戏
发布2021-07-15 10:03:41
1.7K0
发布2021-07-15 10:03:41
举报
文章被收录于专栏:香菜聊游戏香菜聊游戏
之前我们的游戏服务端战斗和客户端战斗是分开写的,经常会出现 一些莫名其妙的bug,原因是前后端实现的细节不一致,这种问题很难解决,隐蔽性很高,测试的时候也很难测试,只有到了线上才会发现问题,而且处理的周期比较长,为了解决这样的问题,我们的项目出现了前后端战斗统一实现的需求,因为我们的客户端是用unity + xlua 的解决方案,这样客户端在写战斗的时候只要把逻辑和表现进行剥离,将战斗逻辑部分放到服务器进行验证,这样只要在服务端起一个验证服务器执行lua就好了。

因此封装了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%20targetting%20Win64/Personal%20Builds/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 Files\Java\jdk1.8.0_291\include\jni.h
C:\Program Files\Java\jdk1.8.0_291\include\win32\jni_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:\clion\TestJni\cmake-build-debug\libTestJni.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:\clion\TestJni\cmake-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++ 之间的调用主要是函数格式的定义,然后加载动态链接库,直接访问就好了。记住规则就好了,没什么难的。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 香菜聊游戏 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、环境安装
  • 2、走个例子
    • 2.1 新建C程序
      • 2.2 拷贝 jni.h 和 jni_md.h 到目录下
      • 3、技术分析
        • 3.1 函数定义
          • 3.2 JNIEnv 解析
          • 4、这次测试中遇到的问题
            • 1、找不到dll
              • 2、找不到jni.h,jni_md.h
                • 3、打出来dll 无法运行,版本不匹配
                  • 4、修改函数名导致不匹配
                  • 5、Java 和 C 数据类型的对照表
                    • 2、引用类型
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档