java调用c/c++代码_very_on的博客-程序员宅基地

技术标签: jni  

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类

[java]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. public class Sample1 {  
  2.     public native int intMethod(int n);  
  3.     public native boolean booleanMethod(boolean bool);  
  4.     public native String stringMethod(String text);  
  5.     public native int intArrayMethod(int[] intArray);  
  6.        
  7.     public static void main(String[] args) {  
  8.         System.loadLibrary("Sample1");  
  9.         Sample1 sample = new Sample1();  
  10.         int square = sample.intMethod(5);  
  11.         boolean bool = sample.booleanMethod(true);  
  12.         String text = sample.stringMethod("Java");  
  13.         int sum = sample.intArrayMethod(new int[]{ 1,2,3,4,5,8,13});  
  14.            
  15.         System.out.println("intMethod: " + square);  
  16.         System.out.println("booleanMethod: " + bool);  
  17.         System.out.println("stringMethod: " + text);  
  18.         System.out.println("intArrayMethod: " + sum);  
  19.     }  
  20. }  


上面有4个native方法, 分别是4种类型的参数, int, boolean, String, int[].

其中有一句比较重要, 这句话加载了动态类库

 
 
  
  1. 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

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class Sample1 */  
  4.    
  5. #ifndef _Included_Sample1  
  6. #define _Included_Sample1  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11.  * Class:     Sample1 
  12.  * Method:    intMethod 
  13.  * Signature: (I)I 
  14.  */  
  15. JNIEXPORT jint JNICALL Java_Sample1_intMethod  
  16.   (JNIEnv *, jobject, jint);  
  17.    
  18. /* 
  19.  * Class:     Sample1 
  20.  * Method:    booleanMethod 
  21.  * Signature: (Z)Z 
  22.  */  
  23. JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod  
  24.   (JNIEnv *, jobject, jboolean);  
  25.    
  26. /* 
  27.  * Class:     Sample1 
  28.  * Method:    stringMethod 
  29.  * Signature: (Ljava/lang/String;)Ljava/lang/String; 
  30.  */  
  31. JNIEXPORT jstring JNICALL Java_Sample1_stringMethod  
  32.   (JNIEnv *, jobject, jstring);  
  33.    
  34. /* 
  35.  * Class:     Sample1 
  36.  * Method:    intArrayMethod 
  37.  * Signature: ([I)I 
  38.  */  
  39. JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod  
  40.   (JNIEnv *, jobject, jintArray);  
  41.    
  42. #ifdef __cplusplus  
  43. }  
  44. #endif  
  45. #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类型的方法

上面头文件的第一个函数声明

 
 
  
  1. JNIEXPORT jint JNICALL Java_Sample1_intMethod (JNIEnv *, jobject, jint);

注释中的签名是 Signature: (I)I

在每个函数的参数列表中都有JNIEnv *和 jobject两个参数, 这两个参数稍候说明。

实现头文件中的函数

可以使用C语言来实现, 也可以使用C++来实现, 下面先说说C语言的实现。

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. #include "Sample1.h"  
  2. #include <string.h>  
  3.    
  4. JNIEXPORT jint JNICALL Java_Sample1_intMethod  
  5.   (JNIEnv *env, jobject obj, jint num)  
  6. {  
  7.     return num * num;  
  8. }  
  9.    
  10. JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod  
  11.   (JNIEnv *env, jobject obj, jboolean boolean)  
  12. {  
  13.     return !boolean;  
  14. }  
  15.    
  16. JNIEXPORT jstring JNICALL Java_Sample1_stringMethod  
  17.   (JNIEnv *env, jobject obj, jstring string)  
  18. {  
  19.     const char* str = (*env)->GetStringUTFChars(env, string, 0);  
  20.     char cap[128];  
  21.     strcpy(cap, str);  
  22.     (*env)->ReleaseStringUTFChars(env, string, 0);  
  23.     return (*env)->NewStringUTF(env, strupr(cap));  
  24. }  
  25.    
  26. JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod  
  27.   (JNIEnv *env, jobject obj, jintArray array)  
  28. {  
  29.     int i, sum = 0;  
  30.     jsize len = (*env)->GetArrayLength(env, array);  
  31.     jint *body = (*env)->GetIntArrayElements(env, array, 0);  
  32.        
  33.     for (i = 0; i < len; ++i)  
  34.     {  
  35.         sum += body[i];  
  36.     }  
  37.     (*env)->ReleaseIntArrayElements(env, array, body, 0);  
  38.     return sum;  
  39. }  


(*env)->GetStringUTFChars()这个方法, 是用来在Java和C之间转换字符串的, 因为Java本身都使用了双字节的字符, 而C语言本身都是单字节的字符, 所以需要进行转换.

JNIEnv *是每个函数都有的参数, 它包含了很多有用的方法, 使用起来类似Java的反射, 也提供了这样一个编码转换的函数.

GetStringUTFChars()和NewStringUTF(), 第一个是从UTF8转换为C的编码格式, 第二个是根据C的字符串返回一个UTF8字符串.

ReleaseStringUTFChars()是用来释放对象的, 在Java中有虚拟机进行垃圾回收, 但是在C语言中, 这些对象必须手动回收. 否则可能造成内存泄漏.

函数的名字一眼看到就可以猜出功能, jni.h中的大部分函数名都是这样.

如果是C++的话, 这段代码该怎么写?

下面是C++的代码

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. #include "Sample1.h"  
  2. #include <string.h>  
  3.    
  4. JNIEXPORT jint JNICALL Java_Sample1_intMethod  
  5.   (JNIEnv *env, jobject obj, jint num)  
  6. {  
  7.     return num * num;  
  8. }  
  9.    
  10. JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod  
  11.   (JNIEnv *env, jobject obj, jboolean boolean)  
  12. {  
  13.     return !boolean;  
  14. }  
  15.    
  16. JNIEXPORT jstring JNICALL Java_Sample1_stringMethod  
  17.   (JNIEnv *env, jobject obj, jstring string)  
  18. {  
  19.     const char* str = env->GetStringUTFChars(string, 0);  
  20.     char cap[128];  
  21.     strcpy(cap, str);  
  22.     env->ReleaseStringUTFChars(string, 0);  
  23.     return env->NewStringUTF(strupr(cap));  
  24. }  
  25.    
  26. JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod  
  27.   (JNIEnv *env, jobject obj, jintArray array)  
  28. {  
  29.     int i, sum = 0;  
  30.     jsize len = env->GetArrayLength(array);  
  31.     jint *body = env->GetIntArrayElements(array, 0);  
  32.        
  33.     for (i = 0; i < len; ++i)  
  34.     {  
  35.         sum += body[i];  
  36.     }  
  37.     env->ReleaseIntArrayElements(array, body, 0);  
  38.     return sum;  
  39. }  


上述两端代码非常相似, 只有一个不同点

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

运行

>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: 25 
booleanMethod: false 
stringMethod: JAVA 
intArrayMethod: 36

源代码下载: Sample1.zip

运行其中的build&run.bat文件即可, 如有错误请根据实际情况修改其中的一些参数.

DLL工程文件VC6.0和VS2010的: VC6.0&VS2010.zip

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/c_z_w/article/details/52983341

智能推荐

定位技术课程设计-微信小程序校园导游系统_微信小程序wifi信号强度-程序员宅基地

定位技术课程设计课程设计教学目的课程设计要求课程设计题目原题目拓展内容需求分析原理分析微信小程序API定位原理WIFI指纹定位原理路径规划算法调研详细设计总述主页面介绍学校简介页面介绍导引页面概述导引地图景点列表景点详细介绍页面搜索界面导航页面概述导航逻辑驾车导航步行导航公交导航后端服务器设计后端服务器架构服务器部分内容服务器接口设计通用说明generalGET /banner/{id}GET /detail/{id}/locationGET /location/${id}GET /location/lis_微信小程序wifi信号强度

多继承的二义性和虚继承(虚基类)_虚基类二义性-程序员宅基地

一般来说,在派生类中对基类成员的访问是应该是唯一的。但是,由于在多继承的情况下,可能出现基类中某个成员的访问不唯一的情况,这称为对基类成员访问的二义性。在多继承的情况下,通常有两种可能出现的二义性。 1 派生类的多个基类中调用其同名成员时可能出现二义性class A{public: void f(){}};class B{public: void f() {} v_虚基类二义性

vue-cli建立项目,总是存在eslint-loader的错误???-程序员宅基地

vue-cli建立项目 ,然后查看项目时候,总是常出现解决方案1.在vue-cli建立的项目根目录中新建一个文件里面的内容结果

java编写格式_JAVA语言编程格式规范-程序员宅基地

JAVA语言编程格式规范目录1.前言..................................................................................................................................................22.试用范围................................_java编程头文件怎么写

用s函数实现Lugre摩擦模型_lugre摩擦模型simulink仿真-程序员宅基地

目录1. Lugre摩擦模型2. Lugre摩擦模型的状态空间方程3. Lugre摩擦模型的simulink建模4. Lugre摩擦模型的s函数建模5. simulink模型和s函数模型对比6. Lugre摩擦模型的s函数源代码1. Lugre摩擦模型 摩擦模型主要分为动态和静态摩擦模型。Lugre摩擦模型能够很好的反应出摩擦的静态和动态效应。Lugre摩擦模型: ① ②..._lugre摩擦模型simulink仿真

offboard Control-程序员宅基地

1.使用mavlink based软件 没有ROS2.使用无线连接,没有ROS3.Onboard computer + ROS + WiFi link (recommended)Control Methods 有下面两种方式1. Offboard Control with Custom Code参考 https://pixhawk.org/dev/direct-..._pixhawk direct-uart-offboard-control

随便推点

二分查找与二分答案_二分查找和二分答案什么差别-程序员宅基地

二分查找的demo二分查找是有demo的以下内容来自于B站up主的讲解二分搜索与二分答案#include<iostream>using namespace std;int binary_search(int S, int E){ int ans = -1; while (S <= E) { int middle = (S + E) / 2; if (check(middle)) { ans = middle; E = middle - 1; _二分查找和二分答案什么差别

流行编程语言的详细对比(5)--异常处理_主流语言的异常处理-程序员宅基地

异常处理Javatry{ System.out.println("Access element three :" + a[3]);}catch(ArrayIndexOutOfBoundsException e){ System.out.println("Exception thrown :" + e);}finally{ a[0] = 6; System.out.println("First element value: " +a[0])_主流语言的异常处理

JEE360-基于Spring Boot的插件式权限系统-程序员宅基地

JEE360-基于Spring Boot的插件式权限系统原文地址:http://blog.csdn.net/tanyi307767340/article/details/79003121项目合作技术分享请联系qq:307767340作者:360汇云 权限插件系统的使用方式,在spring boot项目中添加maven依赖 com.b_jee360

RMAN备份与恢复—恢复归档日志、控制文件、参数文件-程序员宅基地

恢复归档日志(会恢复到归档日志所在目录,有初始化参数log_archive_dest_1决定。)[sql] view plaincopyprint?RMAN> list backup of archivelog all; 备份集列表 =================== BS 关键字 大小 设备类型占用时间 完成时间 ------

HibernateTemplate 的用途-程序员宅基地

2010-12-16 11:01:14| 分类: 默认分类|字号 订阅Hibernate 对Dao层的处理实例 有模糊查询和 分页package infoweb.dao; import java.util.List; import java.util.Iterator; import infoweb.pojo.Info

Java + Redis3.0.2集群的应用(一)——Redis集群环境的搭建-程序员宅基地

最近公司项目中要求使用Redis集群,用来加快数据访问速度,减少数据IO压力,所以研究了一下,做下记录,也方便后来的朋友以供参考。本节主要介绍Redis集群环境如何搭建:测试环境情况如下: 操作系统:hyper-v虚拟机下centos6.5,磁盘空间50G,内存2G; Redis版本:redis-3.0.2, 官方下载地址:http://download.redis.io/releases/r