纱夜
本帖最后由 阴阳师元素祭祀 于 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编写例子的代码)

  1. package com.github.euonmyoji.testjava;


  2. import java.nio.file.Paths;

  3. /**
  4. * [url=home.php?mod=space&uid=1231151]@author[/url] yinyangshi
  5. */
  6. public class TestJava {
  7.     private static String staticField = "ljyys";

  8.     static {
  9.         System.load(Paths.get("libTestJni.dll").toAbsolutePath().toString());
  10.     }

  11.     private String field = "?";

  12.     public TestJava() {
  13.         System.out.println("cons with no args");
  14.     }

  15.     public TestJava(String s) {
  16.         System.out.println("cons with s: " + s);
  17.     }

  18.     /**
  19.      * 底层爆破
  20.      */
  21.     private static native void nativeCrash();

  22.     /**
  23.      * 静态底层方法
  24.      * @param arg 假装有参数
  25.      */
  26.     private static native void staticNativeMethod(String arg);

  27.     public static void main(String[] args) throws Exception {
  28.         staticNativeMethod("hello");
  29.         TestJava testJava = new TestJava();
  30.         testJava.printField();
  31.         testJava.field = "¿";
  32.         testJava.printField();
  33.         nativeCrash();
  34.     }

  35.     /**
  36.      * 使用底层打印field
  37.      */
  38.     private native void printField();
  39. }
复制代码

如何编写一个native方法(Cpp)
将定义好native方法的类进行编译得到相关编译输出的class
之后使用JDK带的工具javah 获得相应的h文件
用法:


例如:
  1. javah com.github.euonmyoji.testjava.TestJava
复制代码
得到输出文件


并通过JDK自带工具 javap获得自己可能需要调用的类的方法签名输出:


同时将JDK文件夹下的include文件夹
添加到cpp开发环境中去(不做教程.yys)

在cpp与java大部分交互操作的时候
我们都会使用到
JNIEnv
对于JNIEnv里面的函数,命名已经能让人清晰的知道意义是什么。

编写静态native方法调用java构造方法并输出传进来的参数
1. 输出参数先将获得的arg变成cpp里的字符串,最后调用iostream里面的cout进行输出使用jni中相关函数
  1. GetStringUTFChars(..)
复制代码
后编写以下代码:
  1.      jboolean copy = false;
  2.     ::std::cout << env->GetStringUTFChars(arg, &copy) << ::std::endl;
复制代码
{输出在结尾折叠内容}
2. 构造自己的类的对象
  I.获得自己的构造方法
  1. GetMethodID(...)
复制代码

    构造方法在底层中的名字为<init>,且为非静态方法,其签名参考javap

  1.    jmethodID con =  env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
复制代码
II. 构造自己对象所需参数的对象,例子所需参数为jstring

  1.     jstring s = env->NewStringUTF("¿");
复制代码

  III. 调用NewObject
    cls为该对象所属的class
    对应c/cpp类型为jclass
    (由于上下文是静态native方法,该类的jclass会作为参数传进底层,不需要自己寻找)
  1.     jobject test_java = env->NewObject(cls, con, s);
复制代码


在尝试获得Method返回空指针的时候,本身jvm会自己创建异常,并随之抛出。
最后的完整代码:

  1. JNIEXPORT void JNICALL Java_com_github_euonmyoji_testjava_TestJava_staticNativeMethod
  2.         (JNIEnv *env, jclass cls, jstring arg) {
  3.     jboolean copy = false;
  4.     ::std::cout << env->GetStringUTFChars(arg, &copy) << ::std::endl;
  5.     jstring s = env->NewStringUTF("¿");
  6.     if (!s) {
  7.         return;
  8.     }
  9.     jmethodID con = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
  10.     if (!con) {
  11.         return;
  12.     }
  13.     jobject test_java = env->NewObject(cls, con, s);
  14.     Java_com_github_euonmyoji_testjava_TestJava_printField(env, test_java);
  15. }

复制代码
编写native方法输出对象中field

1. 首先获得要输出的fieldID, 签名参考javap的结果
    并根据jobject获得相关的jclass
  1.     jfieldID jfieldId = env->GetFieldID(env->GetObjectClass(obj), "field", "Ljava/lang/String;");
复制代码
2. 通过fieldid和jobject获得相应的值,并输出
  1.     jobject str_obj = env->GetObjectField(obj, jfieldId);
  2.     jboolean copy = false;
  3.     ::std::cout << env->GetStringUTFChars((jstring) str_obj, ©) << ::std::endl;
复制代码


编写native方法试试Crash

  1.     ::std::cout << "native crash ..." << ::std::endl;
  2.     int* p = nullptr;
  3.     *p = 9961;
复制代码
(只是普通的代码)(crash只是为了认识一下相关的native报错(?))




最后附上完整的代码+输出:
CPP

Java:

输出:

以及输出的错误文件:

如何加载本地(代码或者说库(?))
  1.     /**
  2.      * Loads the native library specified by the filename argument.  The filename
  3.      * argument must be an absolute path name.
  4.      *
  5.      * If the filename argument, when stripped of any platform-specific library
  6.      * prefix, path, and file extension, indicates a library whose name is,
  7.      * for example, L, and a native library called L is statically linked
  8.      * with the VM, then the JNI_OnLoad_L function exported by the library
  9.      * is invoked rather than attempting to load a dynamic library.
  10.      * A filename matching the argument does not have to exist in the
  11.      * file system.
  12.      * See the JNI Specification for more details.
  13.      *
  14.      * Otherwise, the filename argument is mapped to a native library image in
  15.      * an implementation-dependent manner.
  16.      *
  17.      * <p>
  18.      * The call <code>System.load(name)</code> is effectively equivalent
  19.      * to the call:
  20.      * <blockquote><pre>
  21.      * Runtime.getRuntime().load(name)
  22.      * </pre></blockquote>
  23.      *
  24.      * @param      filename   the file to load.
  25.      * @exception  SecurityException  if a security manager exists and its
  26.      *             <code>checkLink</code> method doesn't allow
  27.      *             loading of the specified dynamic library
  28.      * @exception  UnsatisfiedLinkError  if either the filename is not an
  29.      *             absolute path name, the native library is not statically
  30.      *             linked with the VM, or the library cannot be mapped to
  31.      *             a native library image by the host system.
  32.      * @exception  NullPointerException if <code>filename</code> is
  33.      *             <code>null</code>
  34.      * @see        java.lang.Runtime#load(java.lang.String)
  35.      * @see        java.lang.SecurityManager#checkLink(java.lang.String)
  36.      */
  37.     @CallerSensitive
  38.     public static void load(String filename) {
  39.         Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);
  40.     }
复制代码
例子:
  1.         System.load(Paths.get("libTestJni.dll").toAbsolutePath().toString());
复制代码
(若运行环境下存在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

最后项目配置文件应可能为
[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. 如需返回相关值 则正常返回


此时 代码可能如下
  1. use jni::JNIEnv;
  2. use jni::objects::{JClass, JObject, JString, JValue};

  3. #[no_mangle]
  4. pub extern "system" fn Java_com_github_euonmyoji_testjava_TestJava_nativeCrash(_env: JNIEnv, _class: JClass) {}

  5. #[no_mangle]
  6. pub extern "system" fn Java_com_github_euonmyoji_testjava_TestJava_staticNativeMethod(env: JNIEnv, class: JClass, s: JString) {
  7. }

  8. #[no_mangle]
  9. pub extern "system" fn Java_com_github_euonmyoji_testjava_TestJava_printField(env: JNIEnv, obj: JObject) {}

复制代码
其中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运行效果日志:
  1. print string in rust! hello
  2. cons with s: ¿
  3. ?
  4. cons with no args
  5. ?
  6. ¿
  7. thread '<unnamed>' panicked at 'crash in rust..? or just mean panic. and 中文信息(', test_jni\src\lib.rs:7:5
  8. note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
  9. fatal runtime error: failed to initiate panic, error 5
复制代码








1018827075
111111111111111111111111

成成辉
咿呀咿呀哟

LilDank
必须要学习Java吗

洞穴夜莺
我想问这个方法对于mingw和MSVC都一样吗?
听说这俩编译器的ABI不太一样

第一页 上一页 下一页 最后一页