期貨 CTP的JAVA接口 JNI實現


用JNI直接實現CTP API

這里記錄一下思路,做個紀念。防止以后忘記了~~

參考了SWIG的一些做法(就是照抄了基本思路),例如director類(SPI往回調用),比如methodID的數組。

但是也有很多不一樣的地方(偷懶的),例如C++的SPI的OnXXX函數回調Java方法時,考慮子類繼承時,SWIG會做很多判斷,本文忽略了很多細節。

還例如SWIG所有的CTP數據結構都使用C++原生的,然后java中通過native的setter和getter來操作,本文的數據結構都是純java對象。

 

編譯連接生成了Windows和linux(Ubuntu)的64位的接口,放在了一個jar中

https://github.com/zhsmtp/jni_ctp

 


 

CTP的頭文件分為3個部分:

1. ThostFtdcUserApiDataType.h,api的數據類型, typedef了一大堆類型,char, char[], int, short, double。 對於大部分char型,又#define了很多字符常量。開頭的枚舉類型比較特殊。還有那個單引號括起來的連續字符也比較特殊。

2. ThostFtdcUserApiStruct.h, api的數據結構,用於api和spi的函數參數。數據結構的成員變量的類型全部來自上面的頭文件

3.  API、SPI (Trader, Market Data)


 

思路:

Java的API類跟C++中一樣,只能通過靜態方法例如CreateFtdcTraderApi方法來創建。

Java的API類,包含一個C++的API的實例的指針。API的每個函數,全部通過native方法。這個native方法通過這個指針調用對應的CTP API。例如:

public native int ReqUserLogin(CThostFtdcReqUserLoginField pReqUserLoginField,int nRequestID);

 

Java中的SPI類可以用new來創建,它的構造函數會調用newNativeSpiInstance這個native方法。newNativeSpiInstance(代碼最長的函數)會干這么幾件事:

  1. 獲得Java的SPI實例的全局引用;
  2. 獲得java線程的JVM指針(因為JNIEnv指針只有當前線程有效,SPI的OnXXX是新線程,這個指針會失效)
  3. 以Java的SPI實例的全局引用和JVM指針作為參數,創建一個C++中的SPI子類

Java中的SPI類中的OnXXX函數,則是通過CTP的SPI實例的OnXXX函數直接調用。例如:

    void OnFrontConnected()
    {
        JWrap jw(this);
        JNIEnv* env = jw.getEnv();
        env->CallVoidMethod(jspi,spi_methodIDs[0]);
    }

 


 

開搞:

1. 為每個ctp api的數據結構創建對應的java類

char[]在java中可以使用String表示。注意ctp中是gb2312編碼,這里可以使用下面的String函數直接轉換(沒測試)

package ctp.apistruct;
public class CThostFtdcBrokerSyncField {
    public String BrokerID = "";     //char[11]    (TThostFtdcBrokerIDType)

    public CThostFtdcBrokerSyncField(){}

    public CThostFtdcBrokerSyncField(byte[] BrokerID){
        try{    this.BrokerID= new String(BrokerID, "gb2312");    }catch(java.io.UnsupportedEncodingException e){}
    }
}

2. 實現每一個java API類的native函數

非原生類型的變量需要調用java的構造函數來創建java對象

JNIEXPORT jint JNICALL Java_ctp_CThostFtdcTraderApi_ReqQryTradingNotice
(JNIEnv * env,jobject obj,jobject pQryTradingNotice,jint nRequestID)
{
    CThostFtdcTraderApi* ptrApi;
    jclass clazzTraderApi = env->FindClass("Lctp/CThostFtdcTraderApi;");
    jfieldID fidTraderApi = env->GetFieldID(clazzTraderApi, "ptrApi", "J");
    ptrApi = (CThostFtdcTraderApi*)env->GetLongField(obj,fidTraderApi);
    jclass clzparam = env->FindClass("Lctp/apistruct/CThostFtdcQryTradingNoticeField;");
    CThostFtdcQryTradingNoticeField QryTradingNotice = { 0 };
    {
        jfieldID fid = env->GetFieldID(clzparam, "BrokerID", "Ljava/lang/String;");
        jstring jstr = (jstring) env->GetObjectField(pQryTradingNotice,fid);
        const char* cstr;
        if(jstr)        {
            cstr = env->GetStringUTFChars(jstr, NULL);
            strcpy(QryTradingNotice.BrokerID, (char *) cstr);
        }
        env->ReleaseStringUTFChars((jstring)jstr, cstr);
    }
    {
        jfieldID fid = env->GetFieldID(clzparam, "InvestorID", "Ljava/lang/String;");
        jstring jstr = (jstring) env->GetObjectField(pQryTradingNotice,fid);
        const char* cstr;
        if(jstr)        {
            cstr = env->GetStringUTFChars(jstr, NULL);
            strcpy(QryTradingNotice.InvestorID, (char *) cstr);
        }
        env->ReleaseStringUTFChars((jstring)jstr, cstr);
    }
    {
        jfieldID fid = env->GetFieldID(clzparam, "InvestUnitID", "Ljava/lang/String;");
        jstring jstr = (jstring) env->GetObjectField(pQryTradingNotice,fid);
        const char* cstr;
        if(jstr)        {
            cstr = env->GetStringUTFChars(jstr, NULL);
            strcpy(QryTradingNotice.InvestUnitID, (char *) cstr);
        }
        env->ReleaseStringUTFChars((jstring)jstr, cstr);
    }

    jint iRtn = ptrApi->ReqQryTradingNotice(&QryTradingNotice, ( int ) nRequestID);
    return iRtn;
}

 3. 在C++中實現一個SPI的實例,讓它的OnXXX函數去調用對應Java SPI類中的OnXXX函數

主要任務是創建java對象,作為參數去調用對應的Java SPI方法。

    void OnRspUserLogin(CThostFtdcRspUserLoginField * pRspUserLogin,  CThostFtdcRspInfoField * pRspInfo,  int  nRequestID,  bool  bIsLast)
    {
        JWrap jw(this);
        JNIEnv* env = jw.getEnv();
        jobject RspUserLogin;
        if(pRspUserLogin)
        {
            jbyteArray TradingDay = env->NewByteArray(9);
            if(pRspUserLogin->TradingDay)
                env->SetByteArrayRegion(TradingDay , 0, 9, (const jbyte*)pRspUserLogin->TradingDay);
            jbyteArray LoginTime = env->NewByteArray(9);
            if(pRspUserLogin->LoginTime)
                env->SetByteArrayRegion(LoginTime , 0, 9, (const jbyte*)pRspUserLogin->LoginTime);
            jbyteArray BrokerID = env->NewByteArray(11);
            if(pRspUserLogin->BrokerID)
                env->SetByteArrayRegion(BrokerID , 0, 11, (const jbyte*)pRspUserLogin->BrokerID);
            jbyteArray UserID = env->NewByteArray(16);
            if(pRspUserLogin->UserID)
                env->SetByteArrayRegion(UserID , 0, 16, (const jbyte*)pRspUserLogin->UserID);
            jbyteArray SystemName = env->NewByteArray(41);
            if(pRspUserLogin->SystemName)
                env->SetByteArrayRegion(SystemName , 0, 41, (const jbyte*)pRspUserLogin->SystemName);
            jbyteArray MaxOrderRef = env->NewByteArray(13);
            if(pRspUserLogin->MaxOrderRef)
                env->SetByteArrayRegion(MaxOrderRef , 0, 13, (const jbyte*)pRspUserLogin->MaxOrderRef);
            jbyteArray SHFETime = env->NewByteArray(9);
            if(pRspUserLogin->SHFETime)
                env->SetByteArrayRegion(SHFETime , 0, 9, (const jbyte*)pRspUserLogin->SHFETime);
            jbyteArray DCETime = env->NewByteArray(9);
            if(pRspUserLogin->DCETime)
                env->SetByteArrayRegion(DCETime , 0, 9, (const jbyte*)pRspUserLogin->DCETime);
            jbyteArray CZCETime = env->NewByteArray(9);
            if(pRspUserLogin->CZCETime)
                env->SetByteArrayRegion(CZCETime , 0, 9, (const jbyte*)pRspUserLogin->CZCETime);
            jbyteArray FFEXTime = env->NewByteArray(9);
            if(pRspUserLogin->FFEXTime)
                env->SetByteArrayRegion(FFEXTime , 0, 9, (const jbyte*)pRspUserLogin->FFEXTime);
            jbyteArray INETime = env->NewByteArray(9);
            if(pRspUserLogin->INETime)
                env->SetByteArrayRegion(INETime , 0, 9, (const jbyte*)pRspUserLogin->INETime);
            jclass jclazz = env->FindClass("Lctp/apistruct/CThostFtdcRspUserLoginField;");
            RspUserLogin = env->NewObject(jclazz, ctp_struct_methodIDs[2],TradingDay,LoginTime,BrokerID,UserID,SystemName,pRspUserLogin->FrontID,pRspUserLogin->SessionID,MaxOrderRef,SHFETime,DCETime,CZCETime,FFEXTime,INETime);
        }
        jobject RspInfo;
        if(pRspInfo)
        {
            jbyteArray ErrorMsg = env->NewByteArray(81);
            if(pRspInfo->ErrorMsg)
                env->SetByteArrayRegion(ErrorMsg , 0, 81, (const jbyte*)pRspInfo->ErrorMsg);
            jclass jclazz = env->FindClass("Lctp/apistruct/CThostFtdcRspInfoField;");
            RspInfo = env->NewObject(jclazz, ctp_struct_methodIDs[18],pRspInfo->ErrorID,ErrorMsg);
        }
        env->CallVoidMethod(jspi,spi_methodIDs[4],RspUserLogin, RspInfo, nRequestID, bIsLast);
    }    

 

 坑與一些細節

出現segment fault然后導致Java結束,主要原因是查找類的時候,類的名字需要是全名。名字找對了,並且空指針都處理了,基本上就不會出現了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM