CTP Java API Linux x64 编译(SWIG 封装 C++ 动态库)

前言

网上有不少教程讲到 CTP API 的编译,但是我按照多数教程照着做都不太成功,只有一份是成功了。在此把过程记录下来,希望可以帮到更多的人。

运行环境

CTP 服务端主要由 Java 编写,部署在 Linux 系统,本地开发调试也是 Linux 系统。所以 API 的编译也在 Linux。

过程

1. 下载 CTP API

在上期的官网下载,然后解压。我们只需要 Linux x64 的,工作目录的文件如下:

error.dtd
error.xml
ThostFtdcMdApi.h
ThostFtdcTraderApi.h
ThostFtdcUserApiDataType.h
ThostFtdcUserApiStruct.h
thostmduserapi_se.so
thosttraderapi_se.so

2. 安装 SWIG

SWIG 是一个能将 C/C++ 接口转换为其他语言的工具,支持 Python, Java, C#, Go 等多种编程语言。

我这里直接安装系统源提供的版本:sudo apt install swig,我装的是 3.0.12 版本。

3. 使用 SWIG 得到 Java 包

一、接口文件 thosttraderapi.i

在工作目录创建文件 thosttraderapi.i,内容如下:

%module(directors="1") thosttradeapi 
%{ 
#include "ThostFtdcTraderApi.h"
#include "iconv.h"
%}

%typemap(out) char[ANY], char[] {
    if ($1) {
        iconv_t cd = iconv_open("utf-8", "gb2312");
        if (cd != reinterpret_cast<iconv_t>(-1)) {
            char buf[4096] = {};
            char **in = &$1;
            char *out = buf;
            size_t inlen = strlen($1), outlen = 4096;

            if (iconv(cd, in, &inlen, &out, &outlen) != static_cast<size_t>(-1))
                $result = JCALL1(NewStringUTF, jenv, (const char *)buf);
            iconv_close(cd);
        }
    }
}

%feature("director") CThostFtdcTraderSpi; 
%ignore THOST_FTDC_VTC_BankBankToFuture;
%ignore THOST_FTDC_VTC_BankFutureToBank;
%ignore THOST_FTDC_VTC_FutureBankToFuture;
%ignore THOST_FTDC_VTC_FutureFutureToBank;
%ignore THOST_FTDC_FTC_BankLaunchBankToBroker;
%ignore THOST_FTDC_FTC_BrokerLaunchBankToBroker;
%ignore THOST_FTDC_FTC_BankLaunchBrokerToBank;
%ignore THOST_FTDC_FTC_BrokerLaunchBrokerToBank;  
%feature("director") CThostFtdcTraderSpi; 
%include "ThostFtdcUserApiDataType.h"
%include "ThostFtdcUserApiStruct.h" 
%include "ThostFtdcTraderApi.h"

二、创建文件夹

在工作目录创建 src 文件夹,以及 ctp 文件夹,然后在 ctp 文件夹内创建 thosttraderapi 子文件夹。

mkdir src
mkdir -p ctp/thosttraderapi

三、使用 SWIG

在工作目录执行命令

swig -c++ -java -package ctp.thosttraderapi -outdir src -o thosttraderapi_wrap.cpp thosttraderapi.i

可能要执行 1 分钟,并且可能有一个 Warning 514:xxxxxxxxx 警告,忽略之。

在工作目录可以看到生成了 thosttraderapi_wrap.cppthosttraderapi_wrap.h 两个文件。此外,在 ctp 文件夹中还生成了几百个 .java 文件。

四、编译、打包 Java 文件

在终端中切换到 src 目录内,运行以下命令进行编译:

javac *.java

然后把编译出来的 .class 文件移动到工作目录的 ctp/thosttraderapi 文件夹内:

mv *.class ../ctp/thosttraderapi/

终端回到工作目录,执行

jar cf thosttraderapi.jar ctp

就得到了 thosttraderapi.jar,这是我们 Java 编写程序时导入的包。

4. 编译包装动态库

一、准备静态库 libiconv

首先下载静态库 libiconv.a 放到工作目录。它的作用是把 CTP 返回的 GB2312 的中文信息转换为 UTF-8,否则在 Java 程序中会变成乱码。

至于为什么是静态库,是因为要把这个库链接到最终的 .so 动态库中,而不用另外再放一个 libiconv.so 动态库。为什么是下载一个,是因为本人水平有限,编译出来的只有一个 libiconv.so 动态库,不懂怎么编译一个静态的。而我尝试把 libiconv.so 链接进最终产物后,遇到了报错 libiconv.so.2: 无法打开共享对象文件: 没有那个文件或目录

参考文档的大神作者还提供了不需要 libiconv 的做法,只靠 C++ 标准库的函数,我照着做发现是可以的,但生成的 .so 文件会变大一些。具体步骤是把第一步的 thosttraderapi.i 的内容改为,其余步骤不变:

%module(directors="1") thosttradeapi 
%{ 
#include "ThostFtdcTraderApi.h"
#include <codecvt>
#include <locale>
#include <vector>
#include <string>
using namespace std;
#ifdef _MSC_VER
const static locale g_loc("zh-CN");
#else    
const static locale g_loc("zh_CN.GB18030");
#endif
%}
 
%typemap(out) char[ANY], char[] {
    const std::string &gb2312($1);
    std::vector<wchar_t> wstr(gb2312.size());
    wchar_t* wstrEnd = nullptr;
    const char* gbEnd = nullptr;
    mbstate_t state = {};
    int res = use_facet<codecvt<wchar_t, char, mbstate_t> >
        (g_loc).in(state,
            gb2312.data(), gb2312.data() + gb2312.size(), gbEnd,
            wstr.data(), wstr.data() + wstr.size(), wstrEnd);
 
    if (codecvt_base::ok == res)
    {
        wstring_convert<codecvt_utf8<wchar_t>> cutf8;
        std::string result = cutf8.to_bytes(wstring(wstr.data(), wstrEnd));  
        $result=JCALL1(NewStringUTF,jenv,result.c_str());
    }
    else
    {
        std::string result;
        $result=JCALL1(NewStringUTF,jenv,result.c_str());
    }
}
%feature("director") CThostFtdcTraderSpi; 
%ignore THOST_FTDC_VTC_BankBankToFuture;
%ignore THOST_FTDC_VTC_BankFutureToBank;
%ignore THOST_FTDC_VTC_FutureBankToFuture;
%ignore THOST_FTDC_VTC_FutureFutureToBank;
%ignore THOST_FTDC_FTC_BankLaunchBankToBroker;
%ignore THOST_FTDC_FTC_BrokerLaunchBankToBroker;
%ignore THOST_FTDC_FTC_BankLaunchBrokerToBank;
%ignore THOST_FTDC_FTC_BrokerLaunchBrokerToBank;  
%feature("director") CThostFtdcTraderSpi; 
%include "ThostFtdcUserApiDataType.h"
%include "ThostFtdcUserApiStruct.h" 
%include "ThostFtdcTraderApi.h"

二、Makefile

先把 thosttraderapi_se.so 重命名为 libthosttraderapi.so。然后新建 Makefile,内容如下:

OBJS=thosttraderapi_wrap.o
INCLUDE=-I./ -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux
TARGET=libthosttraderapi_wrap.so
CPPFLAG=-shared -fPIC
CC=g++
LDLIB=-L. -lthosttraderapi
$(TARGET) : $(OBJS)
	$(CC) $(CPPFLAG) $(INCLUDE) -o $(TARGET) $(OBJS) $(LDLIB) ./libiconv.a
$(OBJS) : %.o : %.cpp
	$(CC) -c -fPIC $(INCLUDE) $< -o $@ 
clean:
	-rm -f $(OBJS)
	-rm -f $(TARGET)
install:
	cp $(TARGET) /usr/lib

若使用的是无 libiconv 的版本,则应把 ./libiconv.a 删去。

三、编译

最后,我们执行 make,即可得到 libthosttraderapi_wrap.so

四、使用

libthosttraderapi.so, libthosttraderapi_wrap.so 放到 java.library.path 下,在 Java 代码中导入 thosttraderapi.jar,就可以开始开发了。

相关参考

附录

根据实际使用的经验,发现少数券商返回的某些字符中会含有乱码,造成转为 UTF-8 字符串时出错,导致字符串数据丢失。针对该问题,在使用 libiconv 的情况下,我把 thosttraderapi.iif ($1) 中的代码改为

        iconv_t cd = iconv_open("utf-8//IGNORE", "gb2312");
        if (cd != reinterpret_cast<iconv_t>(-1)) {
            char buf[4096] = {};
            char **in = &$1;
            char *out = buf;
            size_t inlen = strlen($1), outlen = 4096;

            iconv(cd, in, &inlen, &out, &outlen);
            $result = JCALL1(NewStringUTF, jenv, (const char *)buf);
            iconv_close(cd);
        }

问题似乎得到了解决。


发表评论

解决 : *
16 + 45 =