天天看点

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

背景:

之前一系统使用的是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, );
    char* rtn = (char*) malloc(length *  + );
    int size = ;
    size = WideCharToMultiByte( CP_ACP, , (LPCWSTR) jcstr, length, rtn,
            (length *  + ), NULL, NULL);
    if (size <= )
        return NULL;
    (env)->ReleaseStringChars(jstr, jcstr);
    rtn[size] = ;
    return rtn;
}

// 由于jvm和c++对中文的编码不一样,因此需要转码。gb2312转换成utf8/16
jstring charTojstring(JNIEnv* env, const char* str) {
    jstring rtn = ;
    int slen = strlen(str);
    unsigned short * buffer = ;
    if (slen == )
        rtn = (env)->NewStringUTF(str);
    else {
        int length = MultiByteToWideChar( CP_ACP, , (LPCSTR) str, slen, NULL, );
        buffer = (unsigned short *) malloc(length *  + );
        if (MultiByteToWideChar( CP_ACP, , (LPCSTR) str, slen, (LPWSTR) buffer, length) > )
            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++动态链接库完美结束。