在Java中调用C/C++本地库
JNI是Java Native Interface的英文缩写, 中文翻译为本地调用, 自从Java 1.1开始就成为了Java标准的一部分.
C/C++是系统级的编程语言, 可以用来开发任何和系统相关的程序和类库, 但是Java本身编写底层的应用比较难实现, 使用JNI可以调用现有的本地库, 极大地灵活了Java的开发.
C/C++的效率是目前最好的语言, 可以使用C/C++来实现一些实时性非常高的部分. C/C++和Java本身都是非常流行的编程语言, 一些大型软件中经常使用语言之间的混合编程.
鉴于目前网络上JNI的文章不是特别多, 我将自己的一些总结写在这里. 如有错漏, 欢迎指正!
Java调用C/C++大概有这样几个步骤
- 编写带有native方法的Java类, 使用javac工具编译Java类
- 使用javah来生成与native方法对应的头文件
- 实现相应的头文件, 并编译为动态链接库(windows下是.dll, linux下是.so)
下面就完整的介绍一个简单的Java调用C/C++的例子, 这个例子是来自http://www.ibm.com/developerworks/cn/education/java/j-jni/index.html, 不过其中有一些错误, 这个文章是非常不错的JNI学习资料, 但是非常古老.
编写Java类
我们来编写一个Sample1的java类
public class Sample1 {
public native int intMethod(int n);
public native boolean booleanMethod(boolean bool);
public native String stringMethod(String text);
public native int intArrayMethod(int[] intArray);
public static void main(String[] args) {
System.loadLibrary("Sample1");//没有后缀名
Sample1 sample = new Sample1();
int square = sample.intMethod(5);
boolean bool = sample.booleanMethod(true);
String text = sample.stringMethod("Java");
int sum = sample.intArrayMethod(new int[]{1,2,3,4,5,8,13});
System.out.println("intMethod: " + square);
System.out.println("booleanMethod: " + bool);
System.out.println("stringMethod: " + text);
System.out.println("intArrayMethod: " + sum);
}
}
Java Sample1
上面有4个native方法, 分别是4种类型的参数, int, boolean, String, int[].
其中有一句比较重要, 这句话加载了动态类库
System.loadLibrary("Sample1");
在windows下加载的就是Sample1.dll, 在linux下加载的就是Sample1.so.
本文使用的windowws, 所以后面使用Sample1.dll来表示Sample1动态链接库.
注意: 不可以在代码中写上后缀dll或so. 还要保证Sample1.dll在path路径中. 这个Sample1.dll是我们后面需要编译出来的东西.
4个native方法就是我们需要用C来实现的方法.
编译Sample1.java, 使用命令行(windows是cmd, linux下一般是bash)
>javac Sample1.java
可以看到Sample1.class文件
使用javah生成头文件
在命令行中运行
>javah Sample1
可以在目录下看到一个新文件Sample1.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Sample1 */ #ifndef _Included_Sample1
#define _Included_Sample1
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Sample1
* Method: intMethod
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_Sample1_intMethod
(JNIEnv *, jobject, jint); /*
* Class: Sample1
* Method: booleanMethod
* Signature: (Z)Z
*/
JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
(JNIEnv *, jobject, jboolean); /*
* Class: Sample1
* Method: stringMethod
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
(JNIEnv *, jobject, jstring); /*
* Class: Sample1
* Method: intArrayMethod
* Signature: ([I)I
*/
JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
(JNIEnv *, jobject, jintArray); #ifdef __cplusplus
}
#endif
#endif
我们可以看到其中有四个函数声明, Java_完整类名_方法名, 完整类名包括了包名, 例如demo.Sample1是完整类名, 对应的这里就是demo_Sample1.
在注释中我们可以看到这样一个东西 Signature, 这个是方法的签名. 关于Signature, 下面通过一个表格来说明.
| java类型 | Signature | 备注 |
| boolean | Z | |
| byte | B | |
| char | C | |
| short | S | |
| int | I | |
| long | L | |
| float | F | |
| double | D | |
| void | V | |
| object | L用/分割的完整类名 | 例如: Ljava/lang/String表示String类型 |
| Array | [签名 | 例如: [I表示int数组, [Ljava/lang/String表示String数组 |
| Method | (参数签名)返回类型签名 | 例如: ([I)I表示参数类型为int数组, 返回int类型的方法 |
上面头文件的第一个函数声明
JNIEXPORT jint JNICALL Java_Sample1_intMethod (JNIEnv *, jobject, jint);
注释中的签名是 Signature: (I)I
在每个函数的参数列表中都有JNIEnv *和 jobject两个参数, 这两个参数稍候说明.
实现头文件中的函数
可以使用C语言来实现, 也可以使用C++来实现, 下面先说说C语言的实现.
#include "Sample1.h"
#include <string.h> JNIEXPORT jint JNICALL Java_Sample1_intMethod
(JNIEnv *env, jobject obj, jint num)
{
return num * num;
} JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
(JNIEnv *env, jobject obj, jboolean boolean)
{
return !boolean;
} JNIEXPORT jstring JNICALL Java_Sample1_stringMethod (JNIEnv *env, jobject obj, jstring string)
{
const char* str = (*env)->GetStringUTFChars(env, string, );
char cap[];
strcpy(cap, str);
(*env)->ReleaseStringUTFChars(env, string, );
return (*env)->NewStringUTF(env, strupr(cap));
} JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
(JNIEnv *env, jobject obj, jintArray array)
{
int i, sum = ;
jsize len = (*env)->GetArrayLength(env, array);
jint *body = (*env)->GetIntArrayElements(env, array, ); for (i = ; i < len; ++i)
{
sum += body[i];
}
(*env)->ReleaseIntArrayElements(env, array, body, );
return sum;
}
(*env)->GetStringUTFChars()这个方法, 是用来在Java和C之间转换字符串的, 因为Java本身都使用了双字节的字符, 而C语言本身都是单字节的字符, 所以需要进行转换.
JNIEnv *是每个函数都有的参数, 它包含了很多有用的方法, 使用起来类似Java的反射, 也提供了这样一个编码转换的函数.
GetStringUTFChars()和NewStringUTF(), 第一个是从UTF8转换为C的编码格式, 第二个是根据C的字符串返回一个UTF8字符串.
ReleaseStringUTFChars()是用来释放对象的, 在Java中有虚拟机进行垃圾回收, 但是在C语言中, 这些对象必须手动回收. 否则可能造成内存泄漏.
函数的名字一眼看到就可以猜出功能, jni.h中的大部分函数名都是这样.
如果是C++的话, 这段代码该怎么写?
下面是C++的代码
#include "Sample1.h"
#include <string.h> JNIEXPORT jint JNICALL Java_Sample1_intMethod
(JNIEnv *env, jobject obj, jint num)
{
return num * num;
} JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
(JNIEnv *env, jobject obj, jboolean boolean)
{
return !boolean;
} JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
(JNIEnv *env, jobject obj, jstring string)
{
const char* str = env->GetStringUTFChars(string, );
char cap[];
strcpy(cap, str);
env->ReleaseStringUTFChars(string, );
return env->NewStringUTF(_strupr(cap));
} JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
(JNIEnv *env, jobject obj, jintArray array)
{
int i, sum = ;
jsize len = env->GetArrayLength(array);
jint *body = env->GetIntArrayElements(array, ); for (i = ; i < len; ++i)
{
sum += body[i];
}
env->ReleaseIntArrayElements(array, body, );
return sum;
}
C++实现
上述两端代码非常相似, 只有一个不同点
C代码: (*env)->GetStringUTFChars(env, string, 0);
C++代码: env->GetStringUTFChars(string, 0);
C语言中使用的是结构体的函数指针, 而在C++中使用的还是struct, 我们知道struct在C++中和class的功能是几乎一样的, struct也可以用来定义类, 所以env在C++中是个类对象的指针.
编译和运行
方法一、使用的是微软编译器, 编译C语言版的dll
>cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 -LD Sample1.c -FeSample1.dll
编译C++版本的dll
>cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 -LD Sample1.cpp -FeSample1.dll
方法二、用vs新建dll项目
1.新建dll空项目:文件-》新建项目-》Win32控制台应用程序-》应用程序类型为DLL-》附加选项为空项目-》完成
2.项目-》属性-》c/c++ -》常规-》附加包含目录是%JAVA_HOME%\include和%JAVA_HOME%\include\win32对应的路径。
3.有的版本的vs可能需要在预处理器中加入_CRT_SECURE_NO_WARNINGS选项。
运行
>java Sample1
注意: 64位版本的JDK可能会在运行时报错:
java.lang.UnsatisfiedLinkError: ...Sample1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
如果您有这样的错误, 请使用32位的JDK来重新运行.
运行结果如下:
intMethod: 25booleanMethod: falsestringMethod: JAVAintArrayMethod: 36在Java中调用C/C++本地库的更多相关文章
- Java语言与C语言混合编程(2)--在Java中调用C语言本地库
在上一篇文章中介绍了Java语言中的native关键字,以及Java语言调用C语言的编译生成本地动态链接库(DLL)实现加法运算的小例子,本文通过一个更加详细的例子,深入讲解Java语言调用C语言的函 ...
- Java中调用c/c++语言出现Exception in thread "main" java.lang.UnsatisfiedLinkError: Test.testPrint(Ljava/lang/String;)V...错误
错误: Exception in thread "main" java.lang.UnsatisfiedLinkError: Test.testPrint(Ljava/lang/S ...
- java中调用dll文件的两种方法
一中是用JNA方法,另外是用JNative方法,两种都是转载来的, JNA地址:http://blog.csdn.net/shendl/article/details/3589676 JNativ ...
- java中调用js脚本
JDK1.6加入了对Script(JSR223)的支持.这是一个脚本框架,提供了让脚本语言来访问Java内部的方法.你可以在运行的时候找到脚本引擎,然后调用这个引擎去执行脚本.这个脚本API允许你为脚 ...
- autoit 处理文件上传弹出框,并在JAVA中调用
Java 代码 //定义exe 文件存放的绝对路径 File file2 = new File("."); String command = file2.getCanonical ...
- 如何在Java中调用Python代码
有时候,我们会碰到这样的问题:与A同学合作写代码,A同学只会写Python,而不会Java, 而你只会写Java并不擅长Python,并且发现难以用Java来重写对方的代码,这时,就不得不想方设法“调 ...
- 在Java中调用Python
写在前面 在微服务架构大行其道的今天,对于将程序进行嵌套调用的做法其实并不可取,甚至显得有些愚蠢.当然,之所以要面对这个问题,或许是因为一些历史原因,或者仅仅是为了简单.恰好我在项目中就遇到了这个问题 ...
- JAVA中调用LevelDB用于Linux和Window环境下快速存储KV结构
一.简介 JAVA中调用LevelDB用于Linux和Window环境下快速存储KV结构 二.依赖 <!-- https://mvnrepository.com/artifact/org.fus ...
- java中调用kettle转换文件
java中调用kettle转换文件 通过命令行也能够调用,然后java中调用命令行代码也能够.这样没有和java代码逻辑无缝集成.本文说明kettle5.1中假设通过其它API和java代码无缝集成: ...
随机推荐
- HoloLens开发手记-配置开发环境 Install the tools
随着Build 2016开发者大会的结束,HoloLens开发包也正式开放下载.Hololens没有独立的SDK,开发特性被集成到最新的Visual Studio Update 2中.如果你没有Hol ...
- iOS开发总结——项目目录结构
1.前言 清晰的项目目录结构有利于项目的开发,同时也是软件架构的一部分,所以,项目开发之初搭建项目的目录结构很重要.刚转iOS时,自己并不知道如何搭建App的项目目录,在参与开发两个应用后,结合Web ...
- 实例分析JAVA CLASS的文件结构
今天把之前在Evernote中的笔记重新整理了一下,发上来供对java class 文件结构的有兴趣的同学参考一下. 学习Java的朋友应该都知道Java从刚开始的时候就打着平台无关性的旗号,说“一次 ...
- Django使用Channels实现WebSocket--上篇
WebSocket - 开启通往新世界的大门 WebSocket是什么? WebSocket是一种在单个TCP连接上进行全双工通讯的协议.WebSocket允许服务端主动向客户端推送数据.在WebSo ...
- mysql表分区实战
一,什么是数据库分区以mysql为例,mysql数据库中的数据是以文件的形势存在磁盘上的,默认放在/mysql/data下面 (可以通过my.cnf中的datadir来查看),一张表主要对应着三个文件 ...
- PostgreSQL 数据类型
数值类型 数值类型由两个字节,4字节和8字节的整数,4字节和8字节的浮点数和可选精度的小数.下表列出了可用的类型. www.yiibai.com Name Storage Size Descripti ...
- [Golang] GoConvey测试框架使用指南
GoConvey 是一款针对Golang的测试框架,可以管理和运行测试用例,同时提供了丰富的断言函数,并支持很多 Web 界面特性. GoConvey 网站 : http://smartystreet ...
- JavaScript跨域解决方式
平时工作中经常被JavaScript跨域问题所困扰,其实有很多种解决方式,下面给大家介绍常用的几种: 1.jsonp解决跨域问题 客户端代码: <!DOCTYPE html> <ht ...
- 力软开发力软框架力软敏捷开发learun
版本:6.1.6.2 演示地址:www.fishcmonkey.com 联系QQ:6539471
- 探秘小程序(8):scroll-view组件
按照官方文档的例子已经多scroll-view进行了说明,但是案例中没有样式 到时会出现一些问题,比如说,自己写的scroll-x并不能横向滚动,今天就来说一下这个问题: DEMO如下: html: ...