首页 > 其他分享 >JNI学习笔记

JNI学习笔记

时间:2023-10-09 21:34:58浏览次数:39  
标签:int HelloWorld 学习 笔记 extern org JNI example C++

1. 使用Java程序调用C++函数步骤

  1. 创建包含本地方法的Java类:

    package org.example;
    public class HelloWorld {
        static {
            System.loadLibrary("HelloWorld");
        }
    
        public native void print();
    
        public static void main(String[] args) {
            new HelloWorld().print();
        }
    }
    
  2. 使用javac编译生成HelloWorld.class文件:

    javac HelloWorld
    
  3. 使用2生成的HelloWorld.class,通过javah在src/main/java目录下生成C++头文件,详见以下2,3节:

    javah org.example.HelloWorld
    

    此时生成了org_example_HelloWorld.h头文件。

  4. 编写本地方法实现,创建org_example_HelloWorld.cpp,在其中实现HelloWorld.print方法:

    #include <jni.h>
    #include <stdio.h>
    #include <org_example_HelloWorld.h>
    
    JNIEXPORT void JNICALL Java_org_example_HelloWorld_print(JNIEnv *env, jobject obj){
        printf("Hello World!\n");
        return ;
    }
    
  5. 编译C++源码并生成一个本地库,用于Java代码中的System.loadLibrary("HelloWorld");,这里我使用g++/gcc:

    g++ -fno-pie -fPIC -no-pie -shared -I ${JAVA_HOME}/include -I ${JAVA_HOME}/include/linux -I . -o libHelloWorld.so org_example_HelloWorld.cpp
    

    这里 -I后面的是指头文件所在目录,-o指明导出的本地库位置,-shared说明导出动态库。

  6. 在java目录下运行,命令如下:

    java -Djava.library.path=./org/example/ org.example.HelloWorld
    

2. 踩坑

2.1 javah无法生成头文件

需要到src/main/java目录下使用javah,命令是:javah java类的完整名(包名+类名)

例如:javah org.example.HelloWorld

2.2 g++ 报错

报错:/usr/bin/ld: /tmp/ccEtEkwZ.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC

原因:需要在编译命令里加入 -fPIC。

2.3 运行报错

报错:找不到HelloWorld库

这里比较坑,原因有两个,一个是我编译本地库时名字是HelloWorld.so,而正确的名字应该是libHelloWorld.so;第二个问题是我运行的是org.example.HelloWorld,所以library需要设置为libHelloWorld.so所在的目录,即./org/example/(当前目录是src/main/java)

3. javah生成的.h文件解析

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

#ifndef _Included_org_example_HelloWorld
#define _Included_org_example_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     org_example_HelloWorld
 * Method:    print
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_org_example_HelloWorld_print
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

#include:引用头文件

#ifndef:宏命令,后接标识符 意为:如果不包含标识符

#define:宏命令,后接标识符 意为:定义标识符

#ifdef:宏命令,后接标识符 意为:如果包含标识符;此文件中,后接的__cplusplus用于识别编译器,即将当前代码编译的时候,是否将代码作为C++进行编译,如果是,则定义了__cplusplus

extern "C"{:实现C++和C以及其他语言的混合编程。

3.1 extern "C"{ 说明

举例如下:

  • C++引用C函数:
/* c语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif

/* c语言实现文件:cExample.c */
#include "cExample.h"
int add( int x, int y )
{
    return x + y;
}

// c++实现文件,调用add:cppFile.cpp
extern "C"
{
    #include "cExample.h"
}
int main(int argc, char* argv[])
{
    add(2,3);
    return 0;
}

cExample.h 是C语言的头文件,在C++中使用extern "C"包含,这样C++在链接C库时,采用C的方式进行链接(即寻找_add而不是_add_int_int)。

  • C引用C++函数:
//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif

//C++实现文件 cppExample.cpp
#include "cppExample.h"
int add( int x, int y )
{
    return x + y;
}

/* C实现文件 cFile.c
/* 这样会编译出错:#include "cExample.h" */
extern int add( int x, int y );

int main( int argc, char* argv[] )
{
    add( 2, 3 );   
    return 0;
}

此时,C不能直接使用#include "cppExample.h",因为C不支持extern "C",这里C++文件使用extern "C"目的是让C++编译时生成C形式的符号,将其添加到C++实现库中,以便C能找到。

总结:

  • extern "C" 只是 C++ 的关键字,不是 C 的;

    如果在 C 程序中引入了 extern "C" 会导致编译错误。

  • 被 extern "C" 修饰的目标一般是对一个全局C或者 C++ 函数的声明

    从源码上看 extern "C" 一般对头文件中函数声明进行修饰。 Ccpp 中头文件函数声明的形式都是一样的(因为两者语法基本一样),对应声明的实现却可能由于语言特性而不同了( C 库和 C++ 库里面当然会不同)。

  • extern "C" 这个关键字声明的真实目的,就是实现 C++ 与C及其它语言的混合编程

    一旦被 extern "C" 修饰之后,它便以 C 的方式工作(编译阶段:以C的方式编译,链接阶段:寻找C方式编译生成的符号), C 中引用 C++ 库的函数,或 C++ 中引用 C 库的函数,都可以通过这个方式(即在C++文件中用extern "C" 声明,实现兼容。

3.2 本地方法

生成的本地方法C实现接受两个参数,尽管Java中没有接受任何参数。第一个参数事JNIEnv的接口指针,第二个参数是HelloWorld对象本身,类似C++中的this指针。

4.数据类型映射

4.1 String类型使用和转换

转换

Java中的String在本地方法中变为jstring,而jstring和C++中的字符串char*并不等价,不能替换使用,此时需要使用JNI方法GetStringUTFChars:

const char* model_Dir = env->GetStringUTFChars(modelDir, NULL);

不要忘记检查 GetStringUTFChars 的返回值,这是因为 Java 虚拟机的实现决定内部需要申请内存来容纳 UTF-8 字符串,内存的申请是有可能会失败的。如果内存申请失败,那么 GetStringUTFChars 将会返回 NULL 并且会抛出 OutOfMemoryError 异常。

释放

在使用完之后需要释放本地字符串:

env->ReleaseStringUTFChars(modelDir, model_Dir);
创建

通过调用JNI函数NewStringUTF,可以在本地代码中创建一个新的Java.lang.String 实例。同样,需要检查返回值是否为NULL。

其他

以Unicode格式获取和释放字符串:GetStringChars和ReleaseStringChars

统计字符串长度:GetStringUTFLength/GetStringLength

以 Unicode 格式将字符串的内容复制到预分配的 C 缓冲器到或从预分配的 C 缓冲区中复制:GetStringRegion\SetStringRegion.

标签:int,HelloWorld,学习,笔记,extern,org,JNI,example,C++
From: https://www.cnblogs.com/liu-im/p/17753203.html

相关文章

  • 读书笔记——《软件需求》其二
    通过读《软件需求》,我学习到了很多,下面我拿具体的例子来说明一下:"Well-statedrequirementsarethekeytobuildingsystemsthecustomerswant."明确定义的需求是构建符合客户期望的系统的关键。"Thegoalofrequirementsengineeringistoidentifysystembehaviorstha......
  • 笔记1:环境安装及烧录模式
    1.需要安装ADB工具2.使用RKDevTool.exe 烧录固件 K3568开发板需要进入Loader或Maskrom模式才可执行烧写操作。进入Loader模式的方法:首先按住开发板上的音量+(V+)按键(具体位置请参考按键示意图3.2.3)不松,给开发板上电或复位,此时RKDevTool工具会提示:发现一个LOADER......
  • ST表学习笔记
    ST表学习笔记st表是一种的数据结构。运用倍增思想,可以维护RMQ(区间最值问题),预处理\(O(N\logN)\),查询\(O(1)\)。以求区间最大值为例。预处理用一个二维数组\(f[j][i]\)来存储一定区间内的最大值,其中\(j\)表示区间长度为\(2^{j}\),\(i\)表示区间起点。即\(f[j][......
  • C++系列十:日常学习-范围库Ranges
    目录前言介绍举例:前言不错麽内容参考https://zh.cppreference.com/w/cpp/rangesChatjpt总结注意点:确保你的C++编译器支持C++20标准包含ranges头文件views的操作是惰性的,它们不会立即执行,而是在需要时计算。这意味着你可以构建复杂的管道,而不必担心性能问题。提供......
  • 仅作笔记用:PowerShell 关闭显示器
    使用这个命令可以手动关闭显示器,这样就不需要第三方工具甚至自己写代码了。(Add-Type'[DllImport("user32.dll")]publicstaticexternintSendMessage(inthWnd,inthMsg,intwParam,intlParam);'-Namea-Pas)::SendMessage(-1,0x0112,0xF170,2)也可以写成CMD的形式......
  • openGauss学习笔记-94 openGauss 数据库管理-访问外部数据库-mysql_fdw
    openGauss学习笔记-94openGauss数据库管理-访问外部数据库-mysql_fdwopenGauss的fdw实现的功能是各个openGauss数据库及远程服务器(包括数据库、文件系统)之间的跨库操作。目前支持的远程服务器类型包括Oracle、MySQL(MariaDB)、openGauss(postgres_fdw)、file_fdw、dblink。mysql_f......
  • Rust安装及学习资料
    目录官网包管理Rust程序设计语言通过例子学Rust在线运行安装rustup升级Rust卸载Rust创建项目官网https://www.rust-lang.org/zh-CN/包管理https://crates.io/Rust程序设计语言https://kaisery.github.io/trpl-zh-cn/通过例子学Rusthttps://rustwiki.org/zh-CN/......
  • 整除分块学习笔记
    模型求\(\large\sum^{n}_{i=1}\lfloor{\frac{n}{i}}\rfloor\)假设\(n\)等于10,我们可以列出下表:\(\i\)12345678910\(\frac{10}{i}\)10532211111如果我们的\(n\)更大时,我们可以发现\(\frac{10}{i}\)中有许多重复的地方。我们可以......
  • Qt学习随笔-2、Qt中的对象树、坐标系、信号和槽
       1 对象树  1.1 当创建的对象在堆区的时候,如果指点的父亲是QObject派生下来的类或QObject子类派生下来的类,可以不用管理释放的操作,将对象会放入对象树中。  1.2 一定程度上简化了内存回收机制。       2 Qt中的坐标系  2.1 左上......
  • SQL笔记
    SQL四种常用关系型数据库及其对应SQL语言分别是MySQL(mysql)Oracle(sqlplus)SQLServer(ssms)PostgreSQL(psql)。  SQL基础知识SQL的注释--单行注释/*多行注释这是新的一行结束行*/selectname,salaryfromtablename##运用注释调试sql......