在c/c++中调用Java方法
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的成员变量
|
1
2
3
4
5
6
7
8
9
10
11
|
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: ()Vpublic 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代码
先贴代码吧
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
#include <jni.h>#include <string.h>#include <stdio.h>// 环境变量PATH在windows下和linux下的分割符定义#ifdef _WIN32#define PATH_SEPARATOR ';'#else#define PATH_SEPARATOR ':'#endifint 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
下面就是创建虚拟机
|
1
2
3
4
5
6
7
8
|
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); |
构造函数也是方法, 类似调用方法的方式.
// 调用指定的构造函数, 构造函数的名字叫做<init>mid = (*env)->GetMethodID(env, cls, "<init>", "()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两个地方.
在c/c++中调用Java方法的更多相关文章
- jni中调用java方法获取当前apk的签名文件md5值
相应的java方法: void getsign(Context context) throws Exception { PackageInfo localPackageInfo = context.g ...
- HAL中通过JNI调用java方法【转】
转载请注明本文出处:http://www.cnblogs.com/xl19862005 作者:Xandy 由于工作的需要,最近一直在研究HAL.JNI.Java方法之间互调的问题,并做了如下一些记录和 ...
- Unity调用Android Studio中的Java方法
1. 新建Unity项目: 2. Android Studio中新建EmptyActivity: 3. 新建安卓项目时记住最小版本号: 4. 将左侧项目文件浏览面板切换到Project项下,在本项根节 ...
- Android Studio NDK开发-JNI调用Java方法
相对于NDK来说SDK里面有更多API可以调用,有时候我们在做NDK开发的时候,需要在JNI直接Java中的方法和变量,比如callback,系统信息等.... 如何在JNI中调用Java方法呢?就需 ...
- oracle调用java方法的例子(下面所有代码都是在sql/plus中写)
在Oracle中调用Java程序,注意:java方法必须是static类型的,如果想在JAVA中使用system.out/err输出log. 需要在oracle 中执行"call dbms_ ...
- Oracle数据库中调用Java类开发存储过程、函数的方法
Oracle数据库中调用Java类开发存储过程.函数的方法 时间:2014年12月24日 浏览:5538次 oracle数据库的开发非常灵活,不仅支持最基本的SQL,而且还提供了独有的PL/SQL, ...
- cocos2d-x中使用JNI的调用JAVA方法
用cocos2d-x公布Android项目时.都应该知道要用JAVA与C/C++进行交互时会涉及到JNI的操作(Java Native Interface).JNI是JAVA的一个通用接口.旨在本地化 ...
- PySpark 的背后原理--在Driver端,通过Py4j实现在Python中调用Java的方法.pyspark.executor 端一个Executor上同时运行多少个Task,就会有多少个对应的pyspark.worker进程。
PySpark 的背后原理 Spark主要是由Scala语言开发,为了方便和其他系统集成而不引入scala相关依赖,部分实现使用Java语言开发,例如External Shuffle Service等 ...
- C++调用JAVA方法详解
C++调用JAVA方法详解 博客分类: 本文主要参考http://tech.ccidnet.com/art/1081/20050413/237901_1.html 上的文章. C++ ...
随机推荐
- 【sping揭秘】16、@After(finally) 但是这个实在afterturning之前执行
package cn.cutter.start.bean; import org.apache.commons.logging.Log; import org.apache.commons.loggi ...
- 课程四(Convolutional Neural Networks),第四 周(Special applications: Face recognition & Neural style transfer) —— 1.Practice quentions
[解释] This allows us to learn to predict a person’s identity using a softmax output unit, where the n ...
- Linux学习笔记之七————Linux常用命令之编辑器、服务器
<1>gedit编辑器 gedit是一个Linux环境下的文本编辑器,类似windows下的写字板程序,在不需要特别复杂的编程环境下,作为基本的文本编辑器比较合适. <2> ...
- Android开源系列:仿网易Tab分类排序控件实现
前言 产品:网易新闻那个Tab排序好帅. 开发:哦~ 然后这个东东在几天后就出现了..... (PS:差不多一年没回来写博客了~~~~(>_<)~~~~,顺便把名字从 enjoy风铃 修改 ...
- Android中Enum(枚举)的使用
简介 enum 的全称为 enumeration, 是 JDK 1.5 中引入的新特性,存放在 java.lang 包中. 创建枚举类型要使用 enum 关键字,隐含了所创建的类型都是 java.l ...
- 树莓派2B+安装Debain操作系统
写在前面 本篇文章基于当前树莓派官方最新提供的Debain操作系统进行操作,Linux内核版本 4.14.71.本篇文章内容涵盖SD卡写入官方Debain操作系统,开启SSH连接,修改语言环境,改变当 ...
- eclipse配置ant开发环境,一键部署项目
ANT出现之前,编译和部署Java应用需要使用包括特定平台的脚本.Make文件.不同的IDE以及手工操作等组成的大杂烩.现在,几乎所有的开源Java项目都在使用Ant,许多公司的开发项目也在使用Ant ...
- #13 让代码变得Pythonic
前言 在学习Python的过程中,肯定听说过这么一个词:Pythonic,它的意思是让你的代码很Python! 一.列表生成式 前面有一节专门讲解了Python的列表,其灵活的使用方法一定让你陶醉其中 ...
- 基于Asp.Net Core的简单社区项目源代码开源
2019年3月27号 更新版本 本项目基于 ASP.NET CORE 3.0+EF CORE 3.0开发 使用vs2019 +sqlserver 2017(数据库脚本最低支持sql server 20 ...
- /proc文件系统 - 汇总
0. /proc目录简介 Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构.改变内核设置的机制. proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间 ...