天天看點

将c++靜态庫實作二次封裝供java調用

工作中常常作為c++開發者,常常需要與java開發人員進行對接,或者他們看重了一些很好的c++庫想借用,就需要将這些已有的開發可進行二次封裝給java開發調用

首先需要從官網下載下傳jdk并安裝,例如本人的安裝路徑:C:\software\java\jdk1.8.0_45\

假如我們目前已有一組c++的頭檔案及庫檔案

[1]建立對應的java類及結構

c++頭檔案ReData.h裡有一個結構資料:

///ReData.h

struct MSGItem

{

    MSGItem() : chl_id(0), t_sec(0),t_usec(0),_direct(0),_msg("")

    {

    };

    ~MSGItem()

    MSGItem& operator=(const MSGItem& rhs)

        if(this!=&rhs){

            this->chl_id = rhs.chl_id;

            this->t_sec = rhs.t_sec;

            this->t_usec = rhs.t_usec;

            this->_direct = rhs._direct;

            this->_msg = rhs._msg;

        }

        return *this;

    unsigned int chl_id;

    unsigned int  t_sec;         /* seconds */

    unsigned int  t_usec;        /* and microseconds */

    int _direct;

    std::string _msg;

};

//ReData.h

假如有一個MSGRecive類,并該類引用了MSGItem結構,

//MSGRecive.h

class MSGRecive

public:

    MSGRecive(void);

    ~MSGRecive(void);

.......................................

    //擷取封包資料

    bool getMsg(MSGItem &_item);

    //取對應ip的裝置id

    int GetIDByIP(int _id);

    //批量取對應ip的裝置id

    int GetIDByIPRange(int _startId, int _endId,std::map<int,int> &_idMaps);

.......................................................

現在建立java與c++結構資料對應的結構及類,例如

建立一個java工程,在src\lib_et1100目錄下建立對應的java類

//JMSGItem.java

package lib_et1100;

public class JMSGItem {

    public int chl_id;

    public int  t_sec;         /* seconds */

    public int  t_usec;        /* and microseconds */

    public int _direct;

    public String _msg;

}

//JMSGRecive.java

import java.util.Map;

import lib_et1100.JMSGItem;

public class JMSGRecive {

    static

        System.loadLibrary("jk_dll");

    }

    public JMSGRecive()

        mNativeMSGRecive = init();

........................................................

    public boolean getMsg(JMSGItem _item)

        return getMsg_c(mNativeMSGRecive,_item);

    public int GetIDByIP(int _id)

        return GetIDByIP_c(mNativeMSGRecive,_id);

    public int GetIDByIPRange(int _startId, int _endId, Map _idMaps)

        return GetIDByIPRange_c(mNativeMSGRecive,_startId,_endId,_idMaps);

........................................................................................

    private native int  init();

    private native int GetIDByIP_c(int mMSGRecive,int _id);

    private native boolean getMsg_c(int mMSGRecive,JMSGItem _item);

    private native int GetIDByIPRange_c(int mMSGRecive,int _startId, int _endId, Map _idMaps);

    private int mNativeMSGRecive;

//LibJavaEt.java,主程式類

public class LibJavaEt {

     public static void main(String[] args) {

            JMSGRecive msg = new JMSGRecive();

           ..............................................................

          JMSGItem it = new JMSGItem();

          boolean ret = msg.getMsg(it);

         .....................................................................

     }

//LibJavaEt.java

[2]使用javah指令(javah  類的全路徑)

javah  .classpath -jni

//////////////////.classpath conf///////////////

<?xml version="1.0" encoding="UTF-8"?>

<classpath>

    <classpathentry kind="src" path="src"/>

    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>

    <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/jk_dll">

        <attributes>

            <attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="jk_lib_sample/bin"/>

        </attributes>

    </classpathentry>

    <classpathentry kind="output" path="bin"/>

</classpath>

///////////////////////////////////////////////

.classpath配置java源src及輸出bin目錄,

然後生成本地方法的C++頭檔案,例如lib_et1100_JMSGRecive.h到bin目錄,并在bin下會生成一組對應的java的.class檔案,

例如lib_et1100/JMSGItem.class, lib_et1100/JMSGRecive.class,

在lib_et1100_JMSGRecive.h檔案中,lib_et1100_JMSGRecive對應lib_et1100/JMSGItem.class的輸出

javah .classpath

//lib_et1100_JMSGRecive.h

#include <jni.h>

#ifdef __cplusplus

extern "C" {

#endif

..................................................................................................

/*

 * Class:     lib_et1100_JMSGRecive

 * Method:    init

 * Signature: ()I

 */

JNIEXPORT jint JNICALL Java_lib_1et1100_JMSGRecive_init

  (JNIEnv *, jobject);

 * Method:    getMsg_c

 * Signature: (ILlib_et1100/JMSGItem;)Z

JNIEXPORT jboolean JNICALL Java_lib_1et1100_JMSGRecive_getMsg_1c

  (JNIEnv *, jobject, jint, jobject);

 * Method:    GetIPByID_c

 * Signature: (II)I

JNIEXPORT jint JNICALL Java_lib_1et1100_JMSGRecive_GetIDByIP_1c

  (JNIEnv *, jobject, jint obj, jint _id);

 * Method:    GetIDByIPRange_c

 * Signature: (IIILjava/util/Map;)I

JNIEXPORT jint JNICALL Java_lib_1et1100_JMSGRecive_GetIDByIPRange_1c

  (JNIEnv *env, jobject, jint obj, jint startId, jint endId, jobject rmap);

........................................................................

[3]需要建立一個c++項目将這些庫封裝成java調用的動态庫,

我們建立一個c++項目并設定jdk的頭及庫調用

C:\software\java\jdk1.8.0_45\include

C:\software\java\jdk1.8.0_45\include\win

C:\software\java\jdk1.8.0_45\lib

同時将需要java調用的c++的頭及庫檔案加入項目

并将javah指令生成的頭檔案加入到項目中,

[4]c++資料結構轉換Java資料結構

我們在StructDataTran.h将c++結構資料進行轉換

//StructDataTran.h

#include <jni.h>//JNI是Java Native Interface的縮寫,中文為JAVA本地調用。使用JNI可以很友善的用我們的Java程式調用C/C++程式。

#include <map>

#include "ReData.h"

void cp_msgitem(JNIEnv *env,MSGItem_item,jobject ritem)

    jclass ritem_c = env->GetObjectClass(ritem);

    if(NULL!=ritem_c)

        //擷取相關資料;

        jfieldID _direct = env->GetFieldID(ritem_c,"_direct","I");

        jfieldID chl_id = env->GetFieldID(ritem_c,"chl_id","I");

        jfieldID t_sec = env->GetFieldID(ritem_c,"t_sec","I");

        jfieldID t_usec = env->GetFieldID(ritem_c,"t_usec","I");

        jfieldID _msg = env->GetFieldID(ritem_c,"_msg","Ljava/lang/String;");

        //設定相關資料

        env->SetIntField(ritem,_direct,_item._direct);

        env->SetIntField(ritem,chl_id,_item.chl_id);

        env->SetIntField(ritem,t_sec,_item.t_sec);

        env->SetIntField(ritem,t_usec,_item.t_usec);

        jstring str = env->NewStringUTF(_item._msg.c_str());

        env->SetObjectField(ritem,_msg,str);

//還需要轉換标準庫的一些結構及容器類

void cp_map(JNIEnv *env,std::map<int,int> _idMaps,jobject rmap)

    if(!_idMaps.empty()){    

        jclass ritem_c = env->GetObjectClass(rmap);

        //jclass ritem_c = env->FindClass("java/util/HashMap");

        if(NULL!=ritem_c)

        {

            jmethodID HashMap_put = env->GetMethodID(ritem_c, "put",

                "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

            if (HashMap_put == NULL)

                printf("method ID jput not valid\n\n");

            else

            {

                for(std::map<int,int>::iterator it=_idMaps.begin();it!=_idMaps.end();it++)

                {

                    char key[64]={0};

                    itoa(it->first,key,10);

                    char val[64]={0};

                    itoa(it->second,val,10);

                    env->CallVoidMethod(rmap,HashMap_put,env->NewStringUTF(key),env->NewStringUTF(val));

                }

            }

[5]實作javah指令生成的頭檔案的源代碼

///lib_et1100_JMSGRecive.cpp

#include "lib_et1100_JMSGRecive.h"

#include "MSGRecive.h"

#include "StructDataTran.h"

  (JNIEnv *, jobject)

    MSGRecive* p = new MSGRecive();

    return (jint)p;

  (JNIEnv *, jobject, jint, jobject)

    MSGRecive* p = (MSGRecive*)obj;

    MSGItem _item;

    jboolean ret = p->getMsg(_item);

    if(!ret){

        printf("getMsg error!\n");

    }else{

        cp_msgitem(env,_item,ritem);

    return ret;

  (JNIEnv *, jobject, jint obj, jint _id)

    return p->GetIDByIP(_id);

  (JNIEnv *env, jobject, jint obj, jint startId, jint endId, jobject rmap)

    std::map<int,int> _idMaps;

    jint ret = p->GetIDByIPRange(startId,endId,_idMaps);

    cp_map(env,_idMaps,rmap);

//最終項目生成一個dll檔案,例如jk_dll.dll檔案,将動态庫輸出到java工程的bin裡,

[6]java調用

現在在java工程裡,這些java的類及結構資料調用類似

注意JNI為JAVA本地調用,會喪失平台可移植性,工程結構如下

java工程:

jk_et1100_interface

    bin

        lib_et1100

            JMSGItem.class

            JMSGRecive.class

            LibJavaEt.class

        jk_dll.dll

        lib_et1100_JMSGRecive.h

    src

        lib_et1100

            JMSGItem.java

            JMSGRecive.java

            LibJavaEt.java

    .classpath

    .project

jk_dll庫項目:

jk_dll

    jk_et1100_interface

        bin

            lib_et1100_JMSGRecive.h

    c++lib

        ReData.h

        MSGRecive.h

        c++Lib.lib

    src

        lib_et1100_JMSGRecive.cpp

        StructDataTran.h

        StructDataTran.cpp

梳理一下,首先知道 c++lib的頭檔案及庫需要調用,然後在java工程/src/lib_et1100建立對應的java類

通過javah指令生成java工程下,bin下的c++頭檔案及/bin/lib_et110的.class檔案,

然後建立庫工程jk_dll,将c++lib的頭檔案及庫以及javah指令輸出的頭檔案加載到項目,并實作javah指令輸出的頭檔案的源代碼,

jk_dll項目生成動态庫到java工程bin下,供java項目使用