本帖最后由 阴阳师元素祭祀 于 2021-2-11 18:58 编辑
杂事:
rust教程在本主题最最下面 因为是后来更新的
rust部分使用的java代码与上文一致
本教程建立在拥有cpp与Java基础之上
如有需要可前往
https://www.mcbbs.net/thread-688163-1-1.html 与 {搜索引擎} 进行学习
本教程如果有任何错漏,欢迎在评论指出。
教程相关环境
Windows10 64位操作系统 (Microsoft Windows [版本 10.0.19041.450])
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)
{以及下方故意的错误日志的输出所显示的环境}
什么是native方法
由单词字面意和jdk里面代码可以得知是用来让java可以调用本地的代码
比如 LWJGL
使用native方法调用相关的底层,然后调用系统里面相关的函数
如何编写一个native方法(Java)
在相关方法声明时加入关键字 native即可
例如以下Java代码(同时也用作之后的cpp编写例子的代码)
复制代码
如何编写一个native方法(Cpp)
将定义好native方法的类进行编译得到相关编译输出的class
之后使用JDK带的工具javah 获得相应的h文件
用法:
例如:
复制代码得到输出文件
并通过JDK自带工具 javap获得自己可能需要调用的类的方法签名输出:
同时将JDK文件夹下的include文件夹
添加到cpp开发环境中去(不做教程.yys)
在cpp与java大部分交互操作的时候
我们都会使用到
JNIEnv
对于JNIEnv里面的函数,命名已经能让人清晰的知道意义是什么。
编写静态native方法调用java构造方法并输出传进来的参数
1. 输出参数先将获得的arg变成cpp里的字符串,最后调用iostream里面的cout进行输出使用jni中相关函数
复制代码后编写以下代码:
复制代码{输出在结尾折叠内容}
2. 构造自己的类的对象
I.获得自己的构造方法
复制代码
构造方法在底层中的名字为<init>,且为非静态方法,其签名参考javap
复制代码 II. 构造自己对象所需参数的对象,例子所需参数为jstring
复制代码
III. 调用NewObject
cls为该对象所属的class
对应c/cpp类型为jclass
(由于上下文是静态native方法,该类的jclass会作为参数传进底层,不需要自己寻找)
复制代码
在尝试获得Method返回空指针的时候,本身jvm会自己创建异常,并随之抛出。
最后的完整代码:复制代码编写native方法输出对象中field
1. 首先获得要输出的fieldID, 签名参考javap的结果
并根据jobject获得相关的jclass
复制代码2. 通过fieldid和jobject获得相应的值,并输出
复制代码
编写native方法试试Crash
复制代码(只是普通的代码)(crash只是为了认识一下相关的native报错(?))
最后附上完整的代码+输出:
CPP
Java:
输出:
以及输出的错误文件:
如何加载本地(代码或者说库(?))
复制代码例子:
复制代码(若运行环境下存在libTestJni.dll)如果为其他系统请自行修改路径
若资源打包在jar(例如lwjgl)
可以将文件临时解压到临时目录(参考lwjgl)
之后从硬盘加载
杂项(不可靠知识)1. 所有的jobject的创建与使用,最后资源释放还是由jvm进行管理,所以自行保存的引用或者指针可能会被释放(可以学lwjgl的某nanovg做法,提醒java开发者使其不要被GC)。
课后练习(其实是忘记水了):
1. 如何输出刚才java类里面的private static String staticField = "ljyys"
2. 为什么没有任何地方使用到windows.h 却依旧被include了?
废话:
宅魂!我语法高亮呢(振声)
我在code里面的 "& c o p y" 被转义成 © 莉(振声)
Rust [2018]篇
rust教程在本主题最最下面 因为是后来更新的
rust部分使用的java代码与上文一致
使用第三方库:https://crates.io/crates/jni同时该库已有详细文档
--
Q: 为什么选择rust?
A:比c cpp高到不知道哪里去了 爽。A: rust的jni库本身封装了一些比较方便用的函数 例如不需要自行获取检查methodid/fieldid即可获取相关值
A: 编译方便
搭建rust项目环境
1. 按照普通lib库方法新建项目
2. 添加依赖, 也就是jni库
3. 按照rust部分文档更改输出 参考https://doc.rust-lang.org/stable ... ty/rust-with-c.html
最后项目配置文件应可能为
编写相关函数
利用最上文提到的相关jdk工具
生成相关header文件后
即可开始写相关native函数了
注:依赖的jni的JniEnv不要不小心依赖到sys下
大概函数签名具体规则:
1. 函数名与生成的header一致
2. 参数顺序一致
3. 如需返回相关值 则正常返回
此时 代码可能如下
复制代码其中no_mangle是为了关掉一些警告 具体真实用处参考rust相关文档
第三方JNI库对部分操作封装
获取field与object与method的时候可以直接调用
env.get_xxxx(..., name, sig..., args...);
如已有相关id
可调用env.get_xxx_unchecked(....);
从函数签名的参数不同应能发现
编译
cargo build
在target/debug下 即可找到相关输出
如自行修改了其他设置 可能在其他位置
rust lib.rs全文代码:
rust运行效果日志:复制代码
杂事:
rust教程在本主题最最下面 因为是后来更新的
rust部分使用的java代码与上文一致
本教程建立在拥有cpp与Java基础之上
如有需要可前往
https://www.mcbbs.net/thread-688163-1-1.html 与 {搜索引擎} 进行学习
本教程如果有任何错漏,欢迎在评论指出。
教程相关环境
Windows10 64位操作系统 (Microsoft Windows [版本 10.0.19041.450])
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)
{以及下方故意的错误日志的输出所显示的环境}
什么是native方法
由单词字面意和jdk里面代码可以得知是用来让java可以调用本地的代码
比如 LWJGL
使用native方法调用相关的底层,然后调用系统里面相关的函数
如何编写一个native方法(Java)
在相关方法声明时加入关键字 native即可
例如以下Java代码(同时也用作之后的cpp编写例子的代码)
- package com.github.euonmyoji.testjava;
- import java.nio.file.Paths;
- /**
- * [url=home.php?mod=space&uid=1231151]@author[/url] yinyangshi
- */
- public class TestJava {
- private static String staticField = "ljyys";
- static {
- System.load(Paths.get("libTestJni.dll").toAbsolutePath().toString());
- }
- private String field = "?";
- public TestJava() {
- System.out.println("cons with no args");
- }
- public TestJava(String s) {
- System.out.println("cons with s: " + s);
- }
- /**
- * 底层爆破
- */
- private static native void nativeCrash();
- /**
- * 静态底层方法
- * @param arg 假装有参数
- */
- private static native void staticNativeMethod(String arg);
- public static void main(String[] args) throws Exception {
- staticNativeMethod("hello");
- TestJava testJava = new TestJava();
- testJava.printField();
- testJava.field = "¿";
- testJava.printField();
- nativeCrash();
- }
- /**
- * 使用底层打印field
- */
- private native void printField();
- }
如何编写一个native方法(Cpp)
将定义好native方法的类进行编译得到相关编译输出的class
之后使用JDK带的工具javah 获得相应的h文件
用法:
例如:
- javah com.github.euonmyoji.testjava.TestJava
并通过JDK自带工具 javap获得自己可能需要调用的类的方法签名输出:
同时将JDK文件夹下的include文件夹
添加到cpp开发环境中去(不做教程.yys)
在cpp与java大部分交互操作的时候
我们都会使用到
JNIEnv
对于JNIEnv里面的函数,命名已经能让人清晰的知道意义是什么。
编写静态native方法调用java构造方法并输出传进来的参数
1. 输出参数先将获得的arg变成cpp里的字符串,最后调用iostream里面的cout进行输出使用jni中相关函数
- GetStringUTFChars(..)
- jboolean copy = false;
- ::std::cout << env->GetStringUTFChars(arg, ©) << ::std::endl;
2. 构造自己的类的对象
I.获得自己的构造方法
- GetMethodID(...)
构造方法在底层中的名字为<init>,且为非静态方法,其签名参考javap
- jmethodID con = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
- jstring s = env->NewStringUTF("¿");
III. 调用NewObject
cls为该对象所属的class
对应c/cpp类型为jclass
(由于上下文是静态native方法,该类的jclass会作为参数传进底层,不需要自己寻找)
- jobject test_java = env->NewObject(cls, con, s);
在尝试获得Method返回空指针的时候,本身jvm会自己创建异常,并随之抛出。
最后的完整代码:
- JNIEXPORT void JNICALL Java_com_github_euonmyoji_testjava_TestJava_staticNativeMethod
- (JNIEnv *env, jclass cls, jstring arg) {
- jboolean copy = false;
- ::std::cout << env->GetStringUTFChars(arg, ©) << ::std::endl;
- jstring s = env->NewStringUTF("¿");
- if (!s) {
- return;
- }
- jmethodID con = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
- if (!con) {
- return;
- }
- jobject test_java = env->NewObject(cls, con, s);
- Java_com_github_euonmyoji_testjava_TestJava_printField(env, test_java);
- }
1. 首先获得要输出的fieldID, 签名参考javap的结果
并根据jobject获得相关的jclass
- jfieldID jfieldId = env->GetFieldID(env->GetObjectClass(obj), "field", "Ljava/lang/String;");
- jobject str_obj = env->GetObjectField(obj, jfieldId);
- jboolean copy = false;
- ::std::cout << env->GetStringUTFChars((jstring) str_obj, ©) << ::std::endl;
编写native方法试试Crash
- ::std::cout << "native crash ..." << ::std::endl;
- int* p = nullptr;
- *p = 9961;
最后附上完整的代码+输出:
CPP
Java:
输出:
以及输出的错误文件:
如何加载本地(代码或者说库(?))
- /**
- * Loads the native library specified by the filename argument. The filename
- * argument must be an absolute path name.
- *
- * If the filename argument, when stripped of any platform-specific library
- * prefix, path, and file extension, indicates a library whose name is,
- * for example, L, and a native library called L is statically linked
- * with the VM, then the JNI_OnLoad_L function exported by the library
- * is invoked rather than attempting to load a dynamic library.
- * A filename matching the argument does not have to exist in the
- * file system.
- * See the JNI Specification for more details.
- *
- * Otherwise, the filename argument is mapped to a native library image in
- * an implementation-dependent manner.
- *
- * <p>
- * The call <code>System.load(name)</code> is effectively equivalent
- * to the call:
- * <blockquote><pre>
- * Runtime.getRuntime().load(name)
- * </pre></blockquote>
- *
- * @param filename the file to load.
- * @exception SecurityException if a security manager exists and its
- * <code>checkLink</code> method doesn't allow
- * loading of the specified dynamic library
- * @exception UnsatisfiedLinkError if either the filename is not an
- * absolute path name, the native library is not statically
- * linked with the VM, or the library cannot be mapped to
- * a native library image by the host system.
- * @exception NullPointerException if <code>filename</code> is
- * <code>null</code>
- * @see java.lang.Runtime#load(java.lang.String)
- * @see java.lang.SecurityManager#checkLink(java.lang.String)
- */
- @CallerSensitive
- public static void load(String filename) {
- Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);
- }
- System.load(Paths.get("libTestJni.dll").toAbsolutePath().toString());
若资源打包在jar(例如lwjgl)
可以将文件临时解压到临时目录(参考lwjgl)
之后从硬盘加载
杂项(不可靠知识)1. 所有的jobject的创建与使用,最后资源释放还是由jvm进行管理,所以自行保存的引用或者指针可能会被释放(可以学lwjgl的某nanovg做法,提醒java开发者使其不要被GC)。
课后练习(其实是忘记水了):
1. 如何输出刚才java类里面的private static String staticField = "ljyys"
2. 为什么没有任何地方使用到windows.h 却依旧被include了?
废话:
宅魂!我语法高亮呢(振声)
我在code里面的 "& c o p y" 被转义成 © 莉(振声)

Rust [2018]篇
rust教程在本主题最最下面 因为是后来更新的
rust部分使用的java代码与上文一致
使用第三方库:https://crates.io/crates/jni同时该库已有详细文档
--
Q: 为什么选择rust?
A:
A: 编译方便
搭建rust项目环境
1. 按照普通lib库方法新建项目
2. 添加依赖, 也就是jni库
3. 按照rust部分文档更改输出 参考https://doc.rust-lang.org/stable ... ty/rust-with-c.html
最后项目配置文件应可能为
[package]
name = "YOURNAME"
version = "VERSION"
authors = ["AUTHOR"]
edition = "2018"
[lib]
name = "test_jni"
crate-type = ["cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
jni = "0.19.0"
编写相关函数
利用最上文提到的相关jdk工具
生成相关header文件后
即可开始写相关native函数了
注:依赖的jni的JniEnv不要不小心依赖到sys下
大概函数签名具体规则:
1. 函数名与生成的header一致
2. 参数顺序一致
3. 如需返回相关值 则正常返回
此时 代码可能如下
- use jni::JNIEnv;
- use jni::objects::{JClass, JObject, JString, JValue};
- #[no_mangle]
- pub extern "system" fn Java_com_github_euonmyoji_testjava_TestJava_nativeCrash(_env: JNIEnv, _class: JClass) {}
- #[no_mangle]
- pub extern "system" fn Java_com_github_euonmyoji_testjava_TestJava_staticNativeMethod(env: JNIEnv, class: JClass, s: JString) {
- }
- #[no_mangle]
- pub extern "system" fn Java_com_github_euonmyoji_testjava_TestJava_printField(env: JNIEnv, obj: JObject) {}
第三方JNI库对部分操作封装
获取field与object与method的时候可以直接调用
env.get_xxxx(..., name, sig..., args...);
如已有相关id
可调用env.get_xxx_unchecked(....);
从函数签名的参数不同应能发现
编译
cargo build
在target/debug下 即可找到相关输出
如自行修改了其他设置 可能在其他位置
rust lib.rs全文代码:
rust运行效果日志:
- print string in rust! hello
- cons with s: ¿
- ?
- cons with no args
- ?
- ¿
- thread '<unnamed>' panicked at 'crash in rust..? or just mean panic. and 中文信息(', test_jni\src\lib.rs:7:5
- note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
- fatal runtime error: failed to initiate panic, error 5
111111111111111111111111
咿呀咿呀哟
必须要学习Java吗
我想问这个方法对于mingw和MSVC都一样吗?
听说这俩编译器的ABI不太一样
听说这俩编译器的ABI不太一样
本帖最后由 阴阳师元素祭祀 于 2020-10-5 17:11 编辑
只试过mingw编译情况
msvc不知道
我甚至不知道什么是ABI
看了下JNIEXPORT和JNICALL定义
复制代码
往后面翻就太多了
一些规范 希望jdk那边给出的header是规范了的(?)
所以我只管写实现(跑)
我觉得应该一样
洞穴夜莺 发表于 2020-10-5 16:57
我想问这个方法对于mingw和MSVC都一样吗?
听说这俩编译器的ABI不太一样
只试过mingw编译情况
msvc不知道
看了下JNIEXPORT和JNICALL定义
- #define JNIEXPORT __declspec(dllexport)
- #define JNIIMPORT __declspec(dllimport)
- #define JNICALL __stdcall
一些规范 希望jdk那边给出的header是规范了的(?)
所以我只管写实现(跑)
我觉得应该一样
真羡慕技术大佬