前言:
目前上期技术CTP系统提供的API版本是C++
版本,而很多客户采用Java
开发,就产生了一些问题。SWIG
是一个能将C/C++
接口转换为其他语言的工具,目前可以支持Python,Java,R
等语言。
本文主要介绍Windows 32位平台(64位平台请使用对应的软件和API)下利用Swig工具将CTP C++接口API转换为Java可调用的接口。原创不易,欢迎点赞。
0. 欢迎交流
github: https://github.com/nicai0609/JAVA-CTPAPI
1. 准备工作
- 从CTP官网上下载
CTP API
点击下载。32位的API文件包清单如下:
error.dtd
error.xml
ThostFtdcMdApi.h
ThostFtdcTraderApi.h
ThostFtdcUserApiDataType.h
ThostFtdcUserApiStruct.h
thostmduserapi.dll
thostmduserapi.lib
thosttraderapi.dll
thosttraderapi.lib
- 安装Swig软件,本文中所用的Swig是
swigwin-2.0.11
版本,点击下载。 - 安装JDK,注意要安装32位版本,将环境变量配置好。本文所用的是
1.8.0_111
版本,点击下载 。 - 安装
libiconv
库。这个库主要适用于字节编码转换,因为CTP的结算单信息是GB2312编码,而Java采用UTF-8编码,如果不进行字节转换,得到的结算单信息中的中文将会是乱码。 本文采用的libiconv版本是自己下载的源码编译的静态库,可至群内直接下载源码自己编译,也可以到我的github libiconv下载编译好的。本文一开始采用的是第三方库libiconv转换,下面也以libiconv为例继续。C++11库中已有字节编码转换方式,采用这种方式可以不用libiconv
库,下面和libiconv
相关的都可以略去,见《Swig转换C++接口中文乱码解决方案》 - 安装Eclipse,注意,也需要下载32位版本。主要用与Java demo的测试,点击下载
- 安装vs2013,主要用于生成包装dll。
2. 通过Swig得到jar包
在刚刚下载得到的API文件夹20180109_tradeapi_windows
内,新建文件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"
这是一个接口文件,用于告诉swig为哪些类和方法创建接口。***.i文件的具体解释参考这篇文章《用Swig封装C/C++》。然后在当前目录内建立文件夹src
,wrap
,tradeapi32
和ctp
,在ctp
文件夹内建立文件夹thosttraderapi
。文件夹结构图如下:
20180109_tradeapi_windows
│
│─── ctp ─── thosttraderapi
│
│─── tradeapi32
│
│─── src
│
│─── wrap
│
│ ThostFtdcMdApi.h
│ ThostFtdcTraderApi.h
│ ThostFtdcUserApiDataType.h
│ …
打开windows cmd
工具,cd
到当前目录\20180109_tradeapi_windows
下。 在cmd
中运行命令
swig.exe -c++ -java -package ctp.thosttraderapi -outdir src -o thosttraderapi_wrap.cpp thosttraderapi.i
这运行可能要持续1分钟,中间会报一次warning 514:...
警告,在程序员眼里警告直接忽略:)。等到运行完成后,可以看到当前目录下生成了
thosttraderapi_wrap.h
thosttraderapi_wrap.cpp
这两个文件是用于包装原来C++
接口的文件,下面要用。打开src
文件夹,可以看到这时在里面生成了一系列方法的java文件,如下:
CThostFtdcAccountregisterField.java
CThostFtdcAuthenticationInfoField.java
… … …
thosttradeapiJNI.java
在cmd
中cd
到src
文件夹底下,运行命令
javac *.java
运行结束之后可以看到生成了等量的class文件,将所有的class文件拷贝到\ctp\thosttraderapi\
文件夹中,cmd
下cd
到20180109_tradeapi_windows
目录下,运行命令
jar cf thosttraderapi.jar ctp
这样我们就在当前文件夹下得到了jar包thosttraderapi.jar
。
3. 通过C++得到java可调用的dll动态库
接下来在wrap
文件夹中,建立一个C++工程,工程为Win32控制台应用程序,工程名为thosttraderapi_wrap
,点下一步,工程的应用程序类型选DLL
,附加选项选空项目。另外建好项目后,在工程属性-c/c+±代码生成-运行库中选多线程(/MT)。步骤图如下:
1)
2)
3)
完成之后,将如下文件拷贝到\wrap\thosttraderapi_wrap
文件夹下:
ThostFtdcTraderApi.h
ThostFtdcUserApiDataType.h
ThostFtdcUserApiStruct.h
thosttraderapi.lib
thosttraderapi_wrap.cpp
thosttraderapi_wrap.h
libiconv.lib
iconv.h
在c++
工程中添加现有项,将这些文件全部添加到工程中去。下面还要做几步:
- 将你安装jdk目录
\Java\jdk1.8.0_111\include
下的jni.h
和win32
文件夹下的jni_md.h, jawt_md.h
一共三个文件拷贝到安装vs的include目录底下\Microsoft Visual Studio 12.0\VC\include
。这是因为thosttraderapi_wrap.cpp
文件中包含了<jni.h>
,这是用于生成Java
可调用接口的库文件。
这所有完成之后,C++
工程中文件应该如下:
然后选择release版编译。我们按F7编译,在\wrap\thosttraderapi_wrap\Release
目录底下可见thosttraderapi_wrap.dll
动态库文件,说明编译成功,这样CTP Java API
就编译成功了。编译中如果出现error LNK2005: abort 已经在 LIBCMT.lib(abort.obj) 中定义...
等错误,可以右击工程 - 属性 ”配置属性 - 链接器 - 命令行” 添加: /NODEFAULTLIB:"libcmt.lib"
解决。
4. Java Demo
打开Eclipse,新建traderapidemo
工程。
- 在工程中新建
lib
文件夹,将刚刚的thosttraderapi.jar
包拷贝到该文件夹底下,刷新工程,在工程中jar包上右击选择BuildPath/Add to Build Path
将jar包导入到工程。 - 将如下动态库文件
thosttraderapi.dll
thosttraderapi_wrap.dll
拷贝到你电脑环境变量path
路径底下,如果自己不清楚,可以在Java
中用如下代码获得
System.out.println(System.getProperty("java.library.path"));
我直接拷贝到了\Java\jdk1.8.0_111
底下。
完整的tradeapidemo代码如下:
import ctp.thosttraderapi.*;
class TraderSpiImpl extends CThostFtdcTraderSpi{
final static String m_BrokerId = "9999";
final static String m_UserId = "070624";
final static String m_InvestorId = "070624";
final static String m_PassWord = "passwd";
final static String m_TradingDay = "20181122";
final static String m_AccountId = "070624";
final static String m_CurrencyId = "CNY";
TraderSpiImpl(CThostFtdcTraderApi traderapi)
{
m_traderapi = traderapi;
}
@Override
public void OnFrontConnected(){
System.out.println("On Front Connected");
CThostFtdcReqUserLoginField field = new CThostFtdcReqUserLoginField();
field.setBrokerID(m_BrokerId);
field.setUserID(m_UserId);
field.setPassword(m_PassWord);
field.setUserProductInfo("JAVA_API");
m_traderapi.ReqUserLogin(field,0);
System.out.println("Send login ok");
}
@Override
public void OnRspUserLogin(CThostFtdcRspUserLoginField pRspUserLogin, CThostFtdcRspInfoField pRspInfo, int nRequestID, boolean bIsLast)
{
if (pRspInfo != null && pRspInfo.getErrorID() != 0)
{
System.out.printf("Login ErrorID[%d] ErrMsg[%s]\n", pRspInfo.getErrorID(), pRspInfo.getErrorMsg());
return;
}
System.out.println("Login success!!!");
CThostFtdcQryTradingAccountField qryTradingAccount = new CThostFtdcQryTradingAccountField();
qryTradingAccount.setBrokerID(m_BrokerId);
qryTradingAccount.setCurrencyID(m_CurrencyId);;
qryTradingAccount.setInvestorID(m_InvestorId);
m_traderapi.ReqQryTradingAccount(qryTradingAccount, 1);
}
@Override
public void OnRspQryTradingAccount(CThostFtdcTradingAccountField pTradingAccount, CThostFtdcRspInfoField pRspInfo, int nRequestID, boolean bIsLast)
{
if (pRspInfo != null && pRspInfo.getErrorID() != 0)
{
System.out.printf("OnRspQryTradingAccount ErrorID[%d] ErrMsg[%s]\n", pRspInfo.getErrorID(), pRspInfo.getErrorMsg());
return;
}
if (pTradingAccount != null)
{
System.out.printf("Balance[%f]Available[%f]WithdrawQuota[%f]Credit[%f]\n",
pTradingAccount.getBalance(), pTradingAccount.getAvailable(), pTradingAccount.getWithdrawQuota(),
pTradingAccount.getCredit());
}
else
{
System.out.printf("NULL obj\n");
}
}
private CThostFtdcTraderApi m_traderapi;
}
public class tradeapidemo{
static{
System.loadLibrary("thosttraderapi");
System.loadLibrary("thosttraderapi_wrap");
}
final static String ctp1_TradeAddress = "tcp://180.168.146.187:10000";
public static void main(String[] args) {
// TODO Auto-generated method stub
CThostFtdcTraderApi traderApi = CThostFtdcTraderApi.CreateFtdcTraderApi();
TraderSpiImpl pTraderSpi = new TraderSpiImpl(traderApi);
traderApi.RegisterSpi(pTraderSpi);
traderApi.RegisterFront(ctp1_TradeAddress);
traderApi.SubscribePublicTopic(THOST_TE_RESUME_TYPE.THOST_TERT_RESTART);
traderApi.SubscribePrivateTopic(THOST_TE_RESUME_TYPE.THOST_TERT_RESTART);
traderApi.Init();
traderApi.Join();
return;
}
}
行情mdapidemo如下
import ctp.thostmduserapi.*;
class mdspiImpl extends CThostFtdcMdSpi{
final static String m_BrokerId = "9999";
final static String m_UserId = "070624";
final static String m_InvestorId = "070624";
final static String m_PassWord = "passwd";
final static String m_TradingDay = "20181122";
final static String m_AccountId = "070624";
final static String m_CurrencyId = "CNY";
mdspiImpl(CThostFtdcMdApi mdapi)
{
m_mdapi = mdapi;
}
public void OnFrontConnected(){
System.out.println("On Front Connected");
CThostFtdcReqUserLoginField field = new CThostFtdcReqUserLoginField();
field.setBrokerID(m_BrokerId);
field.setUserID(m_UserId);
field.setPassword(m_PassWord);
m_mdapi.ReqUserLogin(field, 0);
}
public void OnRspUserLogin(CThostFtdcRspUserLoginField pRspUserLogin, CThostFtdcRspInfoField pRspInfo, int nRequestID, boolean bIsLast) {
if (pRspUserLogin != null) {
System.out.printf("Brokerid[%s]\n",pRspUserLogin.getBrokerID());
}
String[] instruementid = new String[1];
instruementid[0]="rb1906";
m_mdapi.SubscribeMarketData(instruementid,1);
}
public void OnRtnDepthMarketData(CThostFtdcDepthMarketDataField pDepthMarketData) {
if (pDepthMarketData != null)
{
System.out.printf("AskPrice1[%f]BidPrice1[%f]\n",
pDepthMarketData.getAskPrice1(),pDepthMarketData.getBidPrice1());
}
else
{
System.out.printf("NULL obj\n");
}
}
private CThostFtdcMdApi m_mdapi;
}
public class MdapiDemo {
static{
System.loadLibrary("thostmduserapi");
System.loadLibrary("thostmduserapi_wrap");
}
final static String ctp1_MdAddress = "tcp://180.168.146.187:10031";
public static void main(String[] args) {
// TODO Auto-generated method stub
CThostFtdcMdApi mdApi = CThostFtdcMdApi.CreateFtdcMdApi();
mdspiImpl pMdspiImpl = new mdspiImpl(mdApi);
mdApi.RegisterSpi(pMdspiImpl);
mdApi.RegisterFront(ctp1_MdAddress);
mdApi.Init();
mdApi.Join();
return;
}
}