在C/C++中调用Java代码

2023-11-04

JNI就是Java Native Interface, 即可以实现Java调用本地库, 也可以实现C/C++调用Java代码, 从而实现了两种语言的互通, 可以让我们更加灵活的使用.

通过使用JNI可以从一个侧面了解Java内部的一些实现.

本文使用的环境是

64位的win7系统
JDK 1.6.0u30 (32位)
C/C++编译器是 Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8168 for 80x86 (VC 6.0的, 其他版本的也可以编译通过, 测试过vs2010)
本文使用到的一些功能:

创建虚拟机
寻找class对象, 创建对象
调用静态方法和成员方法
获取成员属性, 修改成员属性
C/C++调用Java代码的一般步骤:

编写Java代码, 并编译
编写C/C++代码
配置lib进行编译, 配置PATH添加相应的dll或so并运行
编写Java代码并编译

这段代码非常简单, 有个静态方法和成员方法, 一个public的成员变量

public class Sample2 {
public String name;

public static String sayHello(String name) {
    return "Hello, " + name + "!";
}

public String sayHello() {
    return "Hello, " + name + "!";
}

}
由于没有定义构造函数, 所以会有一个默认的构造函数.

运行下面的命令编译

javac Sample2.java
可以在当前目录下看到Sample2.class文件, 编译成功, 第一步完成了, So easy!

可以查看Sample2类中的签名

javap -s -private Sample2
结果如下

Compiled from “Sample2.java”
public class Sample2 extends java.lang.Object{
public java.lang.String name;
Signature: Ljava/lang/String;
public Sample2();
Signature: ()V
public static java.lang.String sayHello(java.lang.String);
Signature: (Ljava/lang/String;)Ljava/lang/String;
public java.lang.String sayHello();
Signature: ()Ljava/lang/String;
}
关于签名的含义, 请参看使用JNI进行Java与C/C++语言混合编程(1)–在Java中调用C/C++本地库.

编写C/C++代码调用Java代码

先贴代码吧

include

include

include

ifdef _WIN32

define PATH_SEPARATOR ‘;’

else

define PATH_SEPARATOR ‘:’

endif

int main(void)
{
JavaVMOption options[1];
JNIEnv *env;
JavaVM *jvm;
JavaVMInitArgs vm_args;

long status;
jclass cls;
jmethodID mid;
jfieldID fid;
jobject obj;

options[0].optionString = "-Djava.class.path=.";
memset(&vm_args, 0, sizeof(vm_args));
vm_args.version = JNI_VERSION_1_4;
vm_args.nOptions = 1;
vm_args.options = options;

// 启动虚拟机
status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);

if (status != JNI_ERR)
{
    // 先获得class对象
    cls = (*env)->FindClass(env, "Sample2");
    if (cls != 0)
    {
        // 获取方法ID, 通过方法名和签名, 调用静态方法
        mid = (*env)->GetStaticMethodID(env, cls, "sayHello", "(Ljava/lang/String;)Ljava/lang/String;");
        if (mid != 0)
        {
            const char* name = "World";
            jstring arg = (*env)->NewStringUTF(env, name);
            jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);
            const char* str = (*env)->GetStringUTFChars(env, result, 0);
            printf("Result of sayHello: %s\n", str);
            (*env)->ReleaseStringUTFChars(env, result, 0);
        }

        /*** 新建一个对象 ***/
        // 调用默认构造函数
        //obj = (*env)->AllocObjdect(env, cls);

        // 调用指定的构造函数, 构造函数的名字叫做<init>
        mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
        obj = (*env)->NewObject(env, cls, mid);
        if (obj == 0)
        {
            printf("Create object failed!\n");
        }
        /*** 新建一个对象 ***/

        // 获取属性ID, 通过属性名和签名
        fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
        if (fid != 0)
        {
            const char* name = "icejoywoo";
            jstring arg = (*env)->NewStringUTF(env, name);
            (*env)->SetObjectField(env, obj, fid, arg); // 修改属性
        }

        // 调用成员方法
        mid = (*env)->GetMethodID(env, cls, "sayHello", "()Ljava/lang/String;");
        if (mid != 0)
        {
            jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);
            const char* str = (*env)->GetStringUTFChars(env, result, 0);
            printf("Result of sayHello: %s\n", str);
            (*env)->ReleaseStringUTFChars(env, result, 0);
        }
    }

    (*jvm)->DestroyJavaVM(jvm);
    return 0;
}
else
{
    printf("JVM Created failed!\n");
    return -1;
}

}
这段代码大概做了这几件事

创建虚拟机JVM, 在程序结束的时候销毁虚拟机JVM
寻找class对象
创建class对象的实例
调用方法和修改属性
虚拟的创建

与之相关的有这样几个变量

JavaVMOption options[1];
JNIEnv *env;
JavaVM *jvm;
JavaVMInitArgs vm_args;

JavaVM就是我们需要创建的虚拟机实例

JavaVMOption相当于在命令行里传入的参数

JNIEnv在Java调用C/C++中每个方法都会有的一个参数, 拥有一个JNI的环境

JavaVMInitArgs就是虚拟机创建的初始化参数, 这个参数里面会包含JavaVMOption

下面就是创建虚拟机

options[0].optionString = “-Djava.class.path=.”;
memset(&vm_args, 0, sizeof(vm_args));
vm_args.version = JNI_VERSION_1_4;
vm_args.nOptions = 1;
vm_args.options = options;

// 启动虚拟机
status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
“-Djava.class.path=.”看着眼熟吧, 这个就是传入当前路径, 作为JVM寻找class的用户自定义路径, 我们的Sample2.class就在当前路径(当然也可以不在当前路径, 你可以随便修改).

vm_args.version是Java的版本, 这个应该是为了兼容以前的JDK, 可以使用旧版的JDK, 这个宏定义是在jni.h中, 有以下四种

define JNI_VERSION_1_1 0x00010001

define JNI_VERSION_1_2 0x00010002

define JNI_VERSION_1_4 0x00010004

define JNI_VERSION_1_6 0x00010006

vm_args.nOptions的含义是, 你传入的options有多长, 我们这里就一个, 所以是1.

vm_args.options = options把JavaVMOption传给JavaVMInitArgs里面去.

然后就是启动虚拟机了status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args).

可以通过这个返回值status , 知道虚拟机是否启动成功

define JNI_OK 0 /* success */

define JNI_ERR (-1) /* unknown error */

define JNI_EDETACHED (-2) /* thread detached from the VM */

define JNI_EVERSION (-3) /* JNI version error */

define JNI_ENOMEM (-4) /* not enough memory */

define JNI_EEXIST (-5) /* VM already created */

define JNI_EINVAL (-6) /* invalid arguments */

寻找class对象, 并实例化

JVM在Java中都是自己启动的, 在C/C++中只能自己来启动了, 启动完之后的事情就和在Java中一样了, 不过要使用C/C++的语法.

获取class对象比较简单, FindClass(env, className).

cls = (*env)->FindClass(env, “Sample2”);
在Java中的类名格式是java.lang.String, 但是className的格式有点不同, 不是使用’.’作为分割, 而是’/’, 即java/lang/String.

我们知道Java中构造函数有两种, 一种是默认的没有参数的, 一种是自定义的带有参数的. 对应的在C/C++中, 有两种调用构造函数的方法.

调用默认构造函数

// 调用默认构造函数
obj = (*env)->AllocObjdect(env, cls);
构造函数也是方法, 类似调用方法的方式.

// 调用指定的构造函数, 构造函数的名字叫做
mid = (*env)->GetMethodID(env, cls, “”, “()V”);
obj = (*env)->NewObject(env, cls, mid);
调用方法和修改属性

关于方法和属性是有两个ID与之对应, 这两个ID用来标识方法和属性.

jmethodID mid;
jfieldID fid;
方法分为静态和非静态的, 所以对应的有

mid = (*env)->GetStaticMethodID(env, cls, “sayHello”, “(Ljava/lang/String;)Ljava/lang/String;”);

mid = (*env)->GetMethodID(env, cls, “sayHello”, “()Ljava/lang/String;”);
上面两个方法是同名的, 都叫sayHello, 但是签名不同, 所以可以区分两个方法.

JNI的函数都是有一定规律的, Static就表示是静态, 没有表示非静态.

方法的调用如下

jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);

jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);
我们可以看到静态方法是只需要class对象, 不需要实例的, 而非静态方法需要使用我们之前实例化的对象.

属性也有静态和非静态, 示例中只有非静态的.

获取属性ID

fid = (*env)->GetFieldID(env, cls, “name”, “Ljava/lang/String;”);
修改属性的值

(*env)->SetObjectField(env, obj, fid, arg); // 修改属性
关于jstring的说明

java的String都是使用了unicode, 是双字节的字符, 而C/C++中使用的单字节的字符.

从C转换为java的字符, 使用NewStringUTF方法

jstring arg = (*env)->NewStringUTF(env, name);
从java转换为C的字符, 使用GetStringUTFChars

const char* str = (*env)->GetStringUTFChars(env, result, 0);
编译和运行

编译需要头文件, 头文件在这两个目录中%JAVA_HOME%\include和%JAVA_HOME%\include\win32, 第一个是与平台无关的, 第二个是与平台有关的, 由于笔者的系统是windows, 所以是win32.

编译的时候还要一个lib文件, 是对虚拟机的支持, 保证编译通过.

cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 Sample2.c %JAVA_HOME%\lib\jvm.lib
我们可以看到在当前目录下Sample2.exe, 运行的时候需要jvm.dll(不要将其复制到当前目录下, 这样不可以运行, 会导致jvm创建失败)

set PATH=%JAVA_HOME%\jre\bin\client\;%PATH%
Sample2
jvm.dll在%JAVA_HOME%\jre\bin\client\目录下, 所以我把这个目录加入到PATH中, 然后就可以运行

Result of sayHello: Hello, World!
Result of sayHello: Hello, icejoywoo!

关于C++的说明

本示例的C++版本, 请自行下载后面的源代码来查看, 区别不是很大.

主要是JNIEnv和JavaVM两个对象, 在C中是结构体, 是函数指针的集合, 在C++中结构体拥有类的能力, 使用起来更为简便, 与Java之间的差异更小一些.

结语

本文介绍了一个简单的例子, 分析了其中的一些代码, 笔者希望通过这篇文章让大家对JNI的了解更加深入一些.

水平有限, 错漏在所难免, 欢迎指正!

源代码下载: c调用java.zip

使用方法: 参照里面的build&run.bat, 使用了%JAVA_HOME%环境变量.

注意:

动态链接库和JDK都有32位和64位的区别, 使用64位系统的朋友, 要注意这个问题, 可能导致运行或编译错误.
还要注意区分C和C++代码, 在JNI中两种代码有一定的区别, 主要是env和jvm两个地方.
参考文献:

public0821, C++调用JAVA方法详解, http://public0821.iteye.com/blog/423941
Scott Stricker, 用 JNI 进行 Java 编程, http://www.ibm.com/developerworks/cn/education/java/j-jni/section3.html
JDK 6u30 docs, Java Native Interface Specification

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在C/C++中调用Java代码 的相关文章

  • 类特定的新删除运算符是否必须声明为静态

    标准中是否要求类特定的 new new delete 和 delete 是静态的 我可以让它们成为非静态成员运算符吗 为什么需要它们是静态的 它们被隐式声明为静态 即使您没有键入 static
  • 找不到 assimp-vc140-mt.dll ASSIMP

    我已经从以下位置下载了 Assimp 项目http assimp sourceforge net main downloads html http assimp sourceforge net main downloads html Ass
  • 如何在 QTabWidget Qt 中展开选项卡

    我有一个QTabWidget像这个 但我想展开选项卡以 填充 整个小部件宽度 如下所示 我怎样才能做到这一点 我在用Qt 5 3 2 and Qt 创建者 3 2 1 Update 我尝试使用setExpanding功能 ui gt myT
  • 单例模式和 std::unique_ptr

    std unique ptr唯一地控制它指向的对象 因此不使用引用计数 单例确保利用引用计数只能创建一个对象 那么会std unique ptr与单例执行相同 单例确保只有一个实例属于一种类型 A unique ptr确保只有一个智能指针到
  • 为什么 Google 建议将库复制到您的树中?

    谷歌的Play 服务 API 的使用说明 http developer android com google play services setup html 例如 说 将 extras google google play service
  • 如何在服务器端按钮点击时关闭当前标签页?

    我尝试在确认后关闭当前选项卡 因此我将以下代码放在确认按钮的末尾 但选项卡没有关闭 string jScript ClientScript RegisterClientScriptBlock this GetType keyClientBl
  • Android TextureView 和硬件加速

    我正在尝试实现上所示的示例这一页 http developer android com reference android view TextureView html 我已经在运行 android 4 及以上版本的三种不同设备上进行了尝试
  • 如何通过 JsonConvert.DeserializeObject 在动态 JSON 中使用 null 条件运算符

    我正在使用 Newtonsoft 反序列化已知的 JSON 对象并从中检索一些值 如果存在 关键在于对象结构可能会不断变化 因此我使用动态来遍历结构并检索值 由于对象结构不断变化 我使用 null 条件运算符来遍历 JSON 代码看起来像这
  • ASP.NET MailMessage.BodyEncoding 和 MailMessage.SubjectEncoding 默认值

    很简单的问题 但我在 MSDN 上找不到答案 查找 ASP NET 将用于的默认值 MailMessage BodyEncoding and MailMessage SubjectEncoding 如果你不在代码中设置它们 Thanks F
  • 在 EnvDTE 中调试时捕获 VS 局部变量

    是否可以使用 EnvDTE 进行 vsix Visual Studio 扩展来捕获本地和调试窗口使用的调试数据 或者可以通过其他方法吗 我想创建一个自定义的本地窗口 我们可以修改它以根据需要显示一些较重的内容 而无需为高级用户牺牲原始的本地
  • IEnumerable.Except 不起作用,那么我该怎么办?

    我有一个 linq to sql 数据库 非常简单 我们有 3 个表 项目和用户 有一个名为 User Projects 的连接表将它们连接在一起 我已经有了一个获得的工作方法IEnumberable
  • 使用restsharp序列化对象并将其传递给WebApi而不是序列化列表

    我有一个看起来像的视图模型 public class StoreItemViewModel public Guid ItemId get set public List
  • 新任务中使用的依赖注入服务

    我在需要时使用依赖项注入来访问我的服务 但我现在想要创建一个并发任务 但这会由于依赖项注入对象及其生命周期而导致问题 我读过这篇文章 标题 防止多线程 Link http mehdi me ambient dbcontext in ef6
  • Android View Canvas onDraw 未执行

    我目前正在开发一个自定义视图 它在画布上绘制一些图块 这些图块是从多个文件加载的 并将在需要时加载 它们将由 AsyncTask 加载 如果它们已经加载 它们只会被绘制在画布上 这工作正常 如果加载了这些图片 AsyncTask 就会触发v
  • 在简单注入器中解析具有自定义参数的类

    我正在使用以下命令创建 WPF MVVM 应用程序简易注射器作为 DI 容器 现在 当我尝试从简单注入器解析视图时遇到一些问题 因为我需要在构造时将参数传递到构造函数中 而不是在将视图注册到容器时 因此这不是适用的 简单注入器将值传递到构造
  • 将 char[][] 转换为 char** 会导致段错误吗?

    好吧 我的 C 有点生疏了 但我想我应该用 C 来做我的下一个 小 项目 这样我就可以对其进行抛光 并且我已经有不到 20 行的段错误了 这是我的完整代码 define ROWS 4 define COLS 4 char main map
  • C++0x中disable_if在哪里?

    Boost 两者都有enable if and disable if 但 C 0x 似乎缺少后者 为什么它被排除在外 C 0x 中是否有元编程工具允许我构建disable if按照enable if 哦 我刚刚注意到std enable i
  • 使我的 COM 程序集调用异步

    我刚刚 赢得 了在当前工作中维护用 C 编码的遗留库的特权 这个dll 公开使用 Uniface 构建的大型遗留系统的方法 除了调用 COM 对象之外别无选择 充当此遗留系统与另一个系统的 API 之间的链接 在某些情况下 使用 WinFo
  • xsi:type 属性搞乱了 C# XML 反序列化

    我使用 XSD exe 根据 XML 架构 xsd 文件 自动生成 C 对象 我正在反序列化 OpenCover 输出 但其中一个部分类未正确生成 这是导致异常的行
  • Keystore getEntry 在 Android 9 上返回 NULL

    c我已对存储在 Android 密钥库中的登录密码进行了加密和解密 在 Android 9 上 我观察到应用程序在尝试解密密码时崩溃 我无法重现它 但拥有 Pixel 3 的用户是崩溃的设备之一 下面是我如何从密钥库解密密码 private

随机推荐

  • NLP中embedding

    NLP中embedding https mp weixin qq com s 5ttCIFPVA0 7O67DvI6HKA https www zhihu com question 510987022 一 获取目标对象表征 如果有人问我 你
  • tp5.0 api 接口设计语言包切换功能

    tp5 0 lang 使用 header 传参 语言包没有调用 一 设置语言切换配置 参考文档 https www kancloud cn manual thinkphp5 118132 找到你config设置文件 进行设置 二 设置对应语
  • 网站防御cdn和高防服务器,高防IP和高防CDN哪个防护更好?

    现在网络攻击越来越多 而且有越来越凶猛的趋势 为了保障服务器的安全性和稳定性 不少企业和站长都选了高防服务器 虽然防御提升了 但是攻击依旧是无法避免的 如果使用了高防服务器之后 还不能很好的防御 又不想更换防御更高的高防服务器 不妨可以添加
  • 解决ARM-Compiler ‘Default Compiler Version 5‘ which is not available

    新版MDK不再自带ARM Compiler version 5编译器 如果编辑之前的工程会提示 ARM Compiler Default Compiler Version 5 which is not available 错误 需要手动安装
  • JAR包在CenOS启动成功却打不开项目

    开始看防火墙 service iptables status 提示 Redirecting to bin systemctl status iptables service Unit iptables service could not b
  • RJ45网口灯的含义及当前问题描述

    当前使用的网口是10M 100M的 上有两个灯 绿色和黄色灯 绿色灯状态表示的是网口的连接状态 如果绿色灯常亮表明的是网口处于正常连接状态 黄色灯闪烁代表数据传输 常见的异常 两个灯都不亮 网口未连接成功 两个灯都亮 且常亮 但是数据不通
  • docker入门,这一篇就够了。

    Docker入门 这一篇就够了 Docker容器虚拟化平台 前言 接触docker很长时间了 但是工作中也没有用到 所以总是学了忘 忘了学 不过这次 我打算跟大家分享一下我的学习历程 也算是我的独特的复习笔记 我会在这一篇中讲清楚docke
  • Unity播放视频(一) VideoPlayer的使用

    1 在UI上添加脚本 添加UITexture脚本 用于显示视频 添加VideoPlayer 2 上代码 VideoPlayer m Video UITexture m Texture void Start m Video started O
  • 利用html设置嵌套式表格,表格嵌套 HTML

    嵌套表格就是在一个大的表格中 再嵌进去一个或几个小的表格 即插入到表格单元格中的表格 如果用一个表格布局页面 并希望用另一个表格组织信息 则可以插入一个嵌套表格 表格的嵌套一方面是为使页面的外观更为漂亮 利用表格嵌套来编辑出复杂而精美的效果
  • 3个步骤就让一个web服务器建立起来(web服务器简单写法)

    第一步 新建一个js文件 文件名自行设置 不建议使用中文和数字 内容如下 1 引入http模块 const http require http 2 创建服务 const server http createServer function r
  • 使用百度地图api功能显示位置

  • ctfshow--web入门(web1-web20)

    web1 查看源代码 发现flag web2 查看源代码 通过在url头部添加 view source 或者f12 web3 flag在响应包里 web4 进入robots txt 发现后台遗留文件 web5 考点phps文件泄露 直接访问
  • TypeScript之类与抽象类

    前言 记录一下typeScript中的类 TS中的类与ECMAScript中的类还是有很大区别的 ES6开始引入了类的概念 通过class 关键字定义类 在constructor中定义实例属性等 比起 ES6中的类 在TS中 多了一些类型定
  • 图像噪声

    噪声在理论上可以定义为 不可预测 只能用概率统计方法来描述的随机误差0 因此 可以将图像噪声看成是多维随机过程 描述噪声完全可以借用随机过程及其概率密度函数 数字图像的噪声主要来源于图像的获取和传输过程 图像传感器的工作情况受各种因素影响
  • Vue教程(五):样式绑定——class和style

    1 样式代码准备 样式提前准备
  • org.hibernate.exception.GenericJDBCException: Cannot open connection

    导入一个基于Hibernate的项目后debug 结果出现这个问题 出现这个错误的原因及解决方法 1 数据库没启动 这时你可以简单检验一下 可以拿一个你肯定没问题的项目运行一下 看数据库里表的数据 我用的这个 也可以可以打开数据库客户端 如
  • python 用户信息管理系统【各个函数剖析 + 完整代码 零基础适用篇】

    这个用户管理系统小白也能轻松掌握 只用了函数 用户信息只有两个 姓名 账号 和密码 采用字典存放数据 字典的键即为姓名 值为密码 功能分为两大部分 第一部分为用户的登录和注册 第二部分为管理员对信息的增删查改操作 以下为文章目录 文章目录
  • 【leetcode系列】python单链表查找中间节点

    python单链表查找中间节点 使用快慢指针法 coding UTF 8 class Node def init self data next self data data self next next n1 Node n1 None n2
  • 【满分】【华为OD机试真题2023 JAVA&JS】红黑图

    华为OD机试真题 2023年度机试题库全覆盖 刷题指南点这里 红黑图 知识点枚举 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 众所周知红黑树是一种平衡树 它最突出的特性就是不能有两个相邻的红色节点 那我们定义一个红黑图
  • 在C/C++中调用Java代码

    JNI就是Java Native Interface 即可以实现Java调用本地库 也可以实现C C 调用Java代码 从而实现了两种语言的互通 可以让我们更加灵活的使用 通过使用JNI可以从一个侧面了解Java内部的一些实现 本文使用的环