windows和linux环境下java调用C++代码-JNI技术
一.前言
最近需要做java端调用c/c++的代码,C++开发遇到的问题就不在提及,主要问题是C++生成windows动态链接库dll和linux系统运行的so文件遇到了问题,网上百度过各种教程, 教程五花八门,还好遇到一篇很好的文章,文章地址放在了后面,其中整篇文章基本是原作者的原文,有些配图或者是自己实际使用说明不一样的地方做了修改
使用工具:
1.JAVA使用的IDE为IntelliJ IDEA 2017.3
2.windows环境下C++使用的IDE为visual studio 2017
3.linux环境下C++使用的编译器为gcc/g++
二.windows环境下java调用C++代码
2.1新建java工程,生成相应头文件
eclipse新建工程名为"jniDemo"的java工程,在包名为com.woniu.Native下新建"NativeCpp.java"类,如下:
package com.woniu.Native;
public class NativeCpp {
public native void fun1();
public native int fun2(int a, int b);
public native void fun3(String url1, String url2);
}
编译生成.class文件
进入工程下的target\classes目录下,执行"javah -jni com.woniu.Native.NativeCpp",运行结果如下:
这里我使用的是 javah -classpath . -jni com.ccx.models.util.TestNative 也可以,com同级目录
此时,会在classes目录下生成"com_woniu_Native_NativeCpp.h"头文件,头文件内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_woniu_Native_NativeCpp */
#ifndef _Included_com_woniu_Native_NativeCpp
#define _Included_com_woniu_Native_NativeCpp
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_woniu_Native_NativeCpp
* Method: fun1
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_woniu_Native_NativeCpp_fun1
(JNIEnv *, jobject);
/*
* Class: com_woniu_Native_NativeCpp
* Method: fun2
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_woniu_Native_NativeCpp_fun2
(JNIEnv *, jobject, jint, jint);
/*
* Class: com_woniu_Native_NativeCpp
* Method: fun3
* Signature: (Ljava/lang/String;Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_woniu_Native_NativeCpp_fun3
(JNIEnv *, jobject, jstring, jstring);
#ifdef __cplusplus
}
#endif
#endif
2.2 c++生成动态库
vs2010新建工程名为"JniDll"的win32控制台应用程序,win32应用程序向导界面选择 "DLL"
创建完成后,把2.1中生成的"com_woniu_Native_NativeCpp.h"头文件放入该工程,并把头文件中的#include <jni.h>改为 "jni.h",
把JDK下include文件夹下的"jni.h"和include下win32文件夹下的"jni_md.h"头文件也一同放入创建的工程中。
这里稍作说明,我用的是vs2017 最新版本更新后创建win32控制台程序已经没有了,可以通过
文件=》新建=》项目=》已安装=》Visual C++ =》Windows桌面 =》windows桌面向导
弹出新窗口,选择动态链接库,勾选空项目,下面是图片的说明
工程目录如下:
编辑JniDll.cpp源码文件,实现头文件中的函数,如下:
#include "stdafx.h"
#include "com_woniu_Native_NativeCpp.h"
#include "stdio.h"
#include "stdlib.h"
JNIEXPORT void JNICALL Java_com_woniu_Native_NativeCpp_fun1
(JNIEnv *, jobject)
{
printf("hello world\n");
}
JNIEXPORT jint JNICALL Java_com_woniu_Native_NativeCpp_fun2
(JNIEnv *, jobject, jint a, jint b)
{
return a + b;
}
char* jstringToChar(JNIEnv* env, jstring jstr) {
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("GB2312");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0) {
rtn = (char*) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
JNIEXPORT void JNICALL Java_com_woniu_Native_NativeCpp_fun3
(JNIEnv *env, jobject, jstring url1, jstring url2)
{
//jstringתchar*
char* pUrl1 = jstringToChar(env, url1);
char* pUrl2 = jstringToChar(env, url2);
printf("url1 = %s\n", pUrl1);
printf("url2 = %s\n", pUrl2);
}
我本机是64位系统,使用的是64位JDK,这里生成的动态库也要生成64位的库,否则调用的时候报如下错误:
更改vs编译生成64位dll,步骤如下:
这里稍作说明 :
Debug: x64 (64位) x86(32位) 调试版本
Release: x64 (64位) x86(32位) 正式发布版本
这两个不同模式一般在C++编译不同版本时候 会有影响,需要保持同一个版本,java调用的时候,保持版本位数一致就行,用debug和release都可以,当然最好还是按照严格来做 测试可用debug模式,正式版本release
编译生成解决方案,这时候会在工程根目录下,生成"x64文件夹",Debug文件夹下会有动态库"JniDll.dll"
2.3 java调用dll
原作者调用:
package com.woniu.jniDemo;
import com.woniu.Native.NativeCpp;
public class App
{
public static void main( String[] args )
{
System.load("D:\\VS2010\\VC\\JniDll\\x64\\Debug\\JniDll.dll");
NativeCpp nativeCpp = new NativeCpp();
nativeCpp.fun1();
System.out.println(nativeCpp.fun2(3, 3));
nativeCpp.fun3("www.baidu.com", "www.haoservice.cn");
}
}
自己实际用法:
public class FileEncryptUtils {
public native void fun1();
public native int fun2(int a, int b);
public native void fun3(String url1, String url2);
static {
System.loadLibrary("CryptoDll");
}
public static void main(String[] args) {
FileEncryptUtils nativeCpp = new FileEncryptUtils ();
nativeCpp.fun1();
System.out.println(nativeCpp.fun2(3, 3));
nativeCpp.fun3("www.baidu.com", "www.haoservice.cn");
}
}
注意:
加载方式:原作者使用绝对路径的方式,这种方式自己使用的时候加载不到
于是采用了相对路径方式,需要将生成的dll文件放置到jdk的bin目录下
注意加载文件的方式是不一样的一个是System.load 一个是System.loadLibrary
运行结果如下:
三.linux(CentOS)环境下java调用C++代码
3.1 编译环境
a.安装gcc和g++
yum install gcc-c++
b.安装jdk
去官网上下载jdk安装包,建议使用rpm安装包,会自动配置环境变量。安装完后如下:
本机的安装目录为:/usr/java/jdk1.8.0_144/,不同版本可能不同。
这点很重要: 这里一定要注意不能安装openjdk,因为openjdk没有include目录,编译时需要用到include目录的头文件。
3.2 制作动态库(so库)
1、创建文件夹"jniso",mkdir jniso。具体位置不做要求
2、把2.1中生成的头文件"com_woniu_Native_NativeCpp.h"拷贝过来,#include "jni.h"改为#include <jni.h>
说明:“” 自定义引用
<> 引用linux环境下的jni.h
这点也很重要,我理解应该是加载了linux系统jdk的文件,而不在使用上面编译时候所拷贝过来的jni.h文件
3、新建jni.cpp源文件,添加如下代码:
#include <jni.h>
#include "com_woniu_Native_NativeCpp.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
JNIEXPORT void JNICALL Java_com_woniu_Native_NativeCpp_fun1 (JNIEnv *, jobject)
{
printf("hello world\n");
}
JNIEXPORT jint JNICALL Java_com_woniu_Native_NativeCpp_fun2
(JNIEnv *, jobject, jint a, jint b)
{
return a + b;
}
char* jstringToChar(JNIEnv* env, jstring jstr) {
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("GB2312");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0) {
rtn = (char*) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
JNIEXPORT void JNICALL Java_com_woniu_Native_NativeCpp_fun3
(JNIEnv *env, jobject, jstring url1, jstring url2)
{
char* pUrl1 = jstringToChar(env, url1);
char* pUrl2 = jstringToChar(env, url2);
printf("url1 = %s\n", pUrl1);
printf("url2 = %s\n", pUrl2);
}
4、编译,生成动态库
这里有一点绝对大坑尤其重要:VS2017 默认编码格式为 这就导致了用下面的命令编译的时候会提示很多错误,并且代码是没有错误的,这个问题网上反应是代码中用了全角那些都不好用,建议vs开发文统一为utf-8或者gbk,编译就会避免这个问题,我的解决方式是用notepad++ 进行转下cpp文件编码就可以,或者记事本打开另存utf-8也可以
g++ -fPIC -c jni.cpp -I /usr/java/jdk1.8.0_144/include/ -I /usr/java/jdk1.8.0_144/include/linux/
g++ -shared jni.o -o jni.so
3.3 java调用jni.so
源作者:
import com.woniu.Native.NativeCpp;
public class App
{
public static void main( String[] args )
{
//windows环境下加载库
//System.load("D:\\VS2010\\VC\\JniDll\\x64\\Debug\\JniDll.dll");
//linux下加载库
System.load("/mnt/hgfs/svn/svn/Demo/jniso/jni.so");
NativeCpp nativeCpp = new NativeCpp();
nativeCpp.fun1();
System.out.println(nativeCpp.fun2(3, 3));
nativeCpp.fun3("www.baidu.com", "www.haoservice.cn");
}
}
自己调用:
public class FileEncryptUtils {
public native void fun1();
public native int fun2(int a, int b);
public native void fun3(String url1, String url2);
static {
System.loadLibrary("CryptoDll");
}
public static void main(String[] args) {
FileEncryptUtils nativeCpp = new FileEncryptUtils ();
nativeCpp.fun1();
System.out.println(nativeCpp.fun2(3, 3));
nativeCpp.fun3("www.baidu.com", "www.haoservice.cn");
}
}
但是需要注意的是:
采用自己的这种方式需要生成lib开头的.so文件
运行结果如下:
参考地址https://blog.csdn.net/woniu211111/article/details/78041868