Eclipse CDT MinGW生成C++动态链接库及Java JNI的调用例子

时间:2022-02-11 15:47:47

背景:
之前一系统使用的是C++写的类库,然后使用C++封装成一个服务, 当请求该服务的参数是乱码的时候或者是一些特殊字符的时候,会导致服务直接down掉。便想着写一个定时脚本,检测该服务,当服务down掉,变重启该服务。后来随着调用该服务的系统越来越多,部署该服务的机器越来越多,再使用nginx集群,发现定时脚本总忘这忘那。但是本人对C++又不太熟悉,服务的C++封装代码没了 ,也不知道是如何将C++封装成服务的。便想着使用动态类库+JNI+JAVA Serlvet的方式重新封装该服务。于是在Windows下尝试使用Eclipse CDT + MinGW生产C++动态链接库,然后使用Java JNI条用,便有了此文。 至于Linux下生成动态链接库,请参考本人的另一篇博客《Linux下动态链接库的创建和使用》

安装MinGW:
1、下载地址:http://sourceforge.net/projects/mingw/files/
2、 打开exe 文件选择“Next”,进入下一步,选择downloadand install (下载并安装),
3、然后点击“Next”,进入下一步,选择“Current”,
4、然后点击“Next”,进入下一步,选择组件“Full,
5、然后点击“Next”,进入下一步,选择安装位置,默认是“C:\MinGW\”
6、然后点击“Next”进入下载安装过程
7、最后点击“Finish”完成安装。
8、配置环境变量,运行set_distro_paths.bat
9、手动配置环境变量: path 添加 C:\MinGW\bin;
添加 include=C:\MinGW\include 和 Lib=C:\MinGW\lib
10、C:\MinGW\bin下的 mingw32-make改成 make ( eclipse 中的默认设置)

安装Eclispe CDT:
1、下载地址:http://mirrors.neusoft.edu.cn/eclipse/tools/cdt/releases/8.8.1/cdt-8.8.1.zip, 我们下载的是最新版本。
2、解压便可以直接运行,前提是安装了jdk。我们选择安装的是jdk8
3、将D:\ProgramFiles\Java\jdk1.8.0_45\include路径下的jni.h和D:\ProgramFiles\Java\jdk1.8.0_45\include\win32路径下的jni_md.h拷贝到MinGW下的include路径下,否则会出现找不到jni.h 以及不认关键字:JNIEXPORT JNICALL JNIEnv的情况。

致此, C++开发环境已经算是安装完成,下面开始我们的核心内容。

生成动态链接库:
1、打开Eclipse, 新建一个java project,包名为cn.com.xxc, 新建主类JNITest。代码如下:

package cn.com.xxc;

public class JNITest{

public native static String sayHello(String name);

public static void main(String[] args) {
//注意区别System.load()和System.loadLibrary()
System.load("D:\\libJNITest.dll");
GeoAddrSrv.sayHello("Mr XIE");
}
}

2、cmd里进到所在工程目录的src文件夹 , 输入命令:javah cn.com.xxc.JNITest, 会在src文件夹目录下生成cn_com_xxc_JNITest.h头文件
注意:一定要在src文件夹下输入javah,只有这样后面的cn.com.xxc.JNITest(包名 + 类名)路径才能对的上。
3、打开Eclipse CDT, 新建一个C++工程, 如下图:
Eclipse CDT MinGW生成C++动态链接库及Java JNI的调用例子
注意:这个C++工程的名字就是未来生成的dll的名字libXXX.dll。
4、在新建的C++工程,创建src source folder 源码包, 然后把cn_com_xxc_JNITest.h头文件复制到src下,再新建一个cpp文件, 为了统一,cpp文件取名为:cn_com_xxc_JNITest.cpp。
注意:原来生成的.h文件里没有形参,加形参后函数体为:
JNIEXPORT void JNICALL Java_cn_com_xxc_JNITest_sayHello
(JNIEnv *env, jclass jcls, jstring jstr);
cn_com_xxc_JNITest.h文件的内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */  
#include <jni.h>
/* Header for class cn_com_xxc_JNITest */

#ifndef _Included_cn_com_xxc_JNITest
#define _Included_cn_com_xxc_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: cn_com_xxc_JNITest
* Method: sayHello
* Signature: (Ljava/lang/String;)V
*/

JNIEXPORT jstring JNICALL Java_cn_com_xxc_JNITest_sayHello
(JNIEnv *env, jclass jcls, jstring jstr);

#ifdef __cplusplus
}
#endif
#endif

cn_com_xxc_JNITest.cpp文件的内容如下:

#include <iostream>
#include <string>
#include "cn_com_xxc_JNILib.h"

using namespace std;

// 由于jvm和c++对中文的编码不一样,因此需要转码。 utf8/16转换成gb2312
char* jstringToChar(JNIEnv *env, jstring jstr) {
int length = (env)->GetStringLength(jstr);
const jchar* jcstr = (env)->GetStringChars(jstr, 0);
char* rtn = (char*) malloc(length * 2 + 1);
int size = 0;
size = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) jcstr, length, rtn,
(length * 2 + 1), NULL, NULL);
if (size <= 0)
return NULL;
(env)->ReleaseStringChars(jstr, jcstr);
rtn[size] = 0;
return rtn;
}

// 由于jvm和c++对中文的编码不一样,因此需要转码。gb2312转换成utf8/16
jstring charTojstring(JNIEnv* env, const char* str) {
jstring rtn = 0;
int slen = strlen(str);
unsigned short * buffer = 0;
if (slen == 0)
rtn = (env)->NewStringUTF(str);
else {
int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR) str, slen, NULL, 0);
buffer = (unsigned short *) malloc(length * 2 + 1);
if (MultiByteToWideChar( CP_ACP, 0, (LPCSTR) str, slen, (LPWSTR) buffer, length) > 0)
rtn = (env)->NewString((jchar*) buffer, length);
// 释放内存
free(buffer);
}
return rtn;
}

JNIEXPORT jstring JNICALL Java_cn_com_xxc_JNITest_sayHello
(JNIEnv *env, jclass jcls, jstring jstr)(
JNIEnv *env, jclass jcla, jstring jstr) {

// jstring 转 char*
char *charData = jstringToChar(env, jstr);
// char* 转 string
std::string strTemp = charData;
//
strTemp = "Hello " + strTemp;
// result.c_str() : string 转 char*
return charTojstring(env, result.c_str());
}

5、[关键一步]选中工程,按alt+enter,在Build—-Settings—-Tool Settings—–MinGW C++ Linker目录栏下的Miscellaneous选项下,在linker flags处填入:-Wl,–add-stdcall-alias
6、然后点击编译,在Debug目录下生成libMyJNILib.dll,libXXX.dll名字可以发现XXX就是我们起的C++的工程名字.
7、生成dll完毕后,C++的就告一段落了。在java工程里新建一个文件夹libs,该文件夹路径跟src在同一级目录。将生成的dll拷贝到libs文件夹。
8、[关键一步]如果使用的是System.loadLibrary()函数里写入参数:libMyJNILib, 选中工程,依次点击run—run configurations–JNITest在点击Arguments,在Vm arguments处填入如下:-Djava.library.path=”${workspace_loc}\JNITest\libs;${env_var:PATH}”
注意:上面这句话一点都不能错,其中JNITest是java的工程的名字。两头的引号不要少,另外里面是\,因为这是windows下。
9、如果使用的是 System.load()函数里写入参数:libMyJNILib的绝对路径,就不需要再配置 run configurations。
10、JNITest 加上 System.out.println(System.getProperty(“java.library.path”));打印path的所有路径

至此,Eclipse CDT + MinGW生成C++动态链接库 和 Java JNI的调用C++动态链接库完美结束。