期货CTP接口C++源码与C#应用程序的对接

大家知道,期货CTP接口是由上期技术公司提供的,它提供的源码和范例都是用C++语言写的,这在应用上有一定局限性。比如实盘中需要数据库、程序化、K线图,需要这样那样的功能,下单之前要做许多判断和准备……用C++来写会很麻烦的。但是C#不怕做这些麻烦事,C#就是用来干脏活累活的,你把界面、应用逻辑啥的都交给C#,C++就只要管好自己的一件事就行了——怎么和交易所对话,这样,工作量会小得多。


实际上对大多数人来说,没有C#这样的高级语言,根本就干不了活儿。举个例子来说,我要下单,用C++怎么下单?在命令行窗口里,输入合约代码,输入价格,输入手数……噼里啪啦一通猛敲键盘,才能把一个单子下完,是吧?但在c#界面里,只需要点一个按钮:

 

期货CTP接口C++源码与C#应用程序的对接

 

就是“挂买”这个按钮,我点一下,一瞬间,买进就完成了。在这一瞬间,程序为我干了一连串的事:

● 看看我要买的是哪个合约;
● 读取该合约最新盘口,读取预设的挂单偏移值,算出挂单价格;
● 读取默认手数;
● 准备报单,更新报单序号,记录报单信息;
● 对此单进行预检(有无自成交风险、报单序号是否合规、资金是否足够、是否在交易时间、其他方方面面是否合规);
● 是否有旧单要撤(有时需要撤完旧单再下新单,比如平仓时);
● 账户中是否有反向单需要对冲,如果需要,最终是开仓还是平仓;
● 向交易模块发单;
● 向C++模块发单;
● 向交易所报单。

就这一系列的事情,C++所做的只有最后一件,向交易所报单,也就是ReqOrderInsert。
前面的所有啰嗦事,都由C#包办了。
如果都用C++来写,工作量可想而知(实际上对大多数程序员来说是不可行的)。
虽然事情看起来多,但刚才的那一连串,是在约十毫秒内发生的。
这十毫秒的事情,即使用C#语言来写,也是很长很长的,我们把它精简一下,先不考虑什么报单记录、报单校验之类的了,假设点了按钮直接就报单,便于我们看清C#与C++的关系,那么——

C#中的代码是:

///C++所输出的dll的位置
const string dllPath = @"CTP_se.dll";

///引用C++中的下单函数(在C++中,下单的入口是函数“_ReqOrderInsert”
[DllImport(dllPath, EntryPoint = "_ReqOrderInsert")]

///C#的下单函数,“extern”标签决定了它要按照上一句话的规定引用C++中的函数“_ReqOrderInsert”
static extern void ReqOrderInsert(
char[] BrokerID, //经纪公司代码(字符串,必填)
char[] InvestorID, //投资者代码(字符串,必填)
char[] InstrumentID, //合约代码(字符串,必填)
char[] ExchangeID, //交易所代码(字符串,必填)
char[] OrderRef, //报单引用(字符串,必填)
char[] UserID, //用户代码(字符串,不需要就填“null”)
int OrderPriceType, //报单价格条件(必填,1为任意价,2为限价,3为最优价,4为最新价,
//5为最新价浮动上浮1个ticks,6为最新价浮动上浮2个ticks,
//7为最新价浮动上浮3个ticks,8为卖一价,
//9为卖一价浮动上浮1个ticks,10为卖一价浮动上浮2个ticks,
//11为卖一价浮动上浮3个ticks,12为买一价,
//13为买一价浮动上浮1个ticks,14为买一价浮动上浮2个ticks,
//15为买一价浮动上浮3个ticks,16为五档价)
int Direction, //买卖方向(1为买 ,-1为卖)
int CombOffsetFlag, //组合开平标志(必填,1为开,0为平,-1为平今,-2为平昨,-3为强平,-4为强减,-5为本地强平)
int CombHedgeFlag, //组合投机套保标志(必填,0为投机,1为套利,2为套保)
double LimitPrice, //价格(实数,必填)
int VolumeTotalOriginal, //手数(必填)
int TimeCondition, //有效期类型(必填,1为立即完成,否则撤销,2为本节有效,3为当日有效,一般填3,4为指定日期前有效,5为撤销前有效,6为集合竞价有效)
char[] GTDDate, //GTD日期(字符串,不需要就填“null”)
int VolumeCondition, //成交量类型(必填,1为任何数量 ,2为最小数量 ,3为全部数量,一般填1)
int MinVolume, //最小成交量(必填)
int ContingentCondition, //触发条件(必填,1为立即,2为止损,3为止赢,4为预埋单,一般填1
//5为最新价大于条件价,6为最新价大于等于条件价,
//7为最新价小于条件价,8为最新价小于等于条件价,
//9为卖一价大于条件价,10为卖一价大于等于条件价,
//11为卖一价小于条件价,12为卖一价小于等于条件价,
//13为买一价大于条件价,14为买一价大于等于条件价,
//15为买一价小于条件价,16为买一价小于等于条件价)
double StopPrice, //止损价(实数,不需要就填“0”)
int ForceCloseReason, //强平原因(必填,0为非强平 ,1为资金不足 ,2为客户超仓 ,一般填0
//3为会员超仓 ,4为持仓非整数倍 ,5为违规 ,6为其它 ,7为自然人临近交割)
int IsAutoSuspend, //自动挂起标志(整数,必填,一般填0,表示不自动挂起)
char[] BusinessUnit, //业务单元(字符串,不需要就填“null”)
int RequestID, //请求编号(整数,不需要就填“Int32.MinValue”)
int UserForceClose, //用户强评标志(整数,必填,一般填0,表示否)
int IsSwapOrder, //互换单标志(整数,不需要就填“Int32.MinValue”)
int tdClientI //交易通讯线程序号
);

///单击挂买按钮的事件
private void 挂买按钮_Click(object sender, EventArgs e)
{
    string brokerID = "9999";
    string userID = "071988";
    string insID = "ni2003";
    string exchangeID = "SHFE";
    string ordID = "10000";
    int direction = 1;
    int oc = 1;
    double price = 108590;
    int lot = 1;
    int tdClientI = 0;
    ReqOrderInsert(brokerID.ToCharArray(), userID.ToCharArray(), insID.ToCharArray(), exchangeID.ToCharArray(), ordID.ToCharArray(), 
        null, 2, direction, oc, 0, price, lot, 3, null, 1, 1, 1, 0, 0, 0, null, Int32.MinValue, 0, Int32.MinValue, tdClientI);
}

 

而在C++中,与上述C#功能对接的函数,名叫“_ReqOrderInsert”:

///C++内部的报单函数
int ReqOrderInsert(CThostFtdcInputOrderField* pInputOrder, int tdClientI) {
    string str_ref(pInputOrder->OrderRef);
    string insID(pInputOrder->InstrumentID);
    logWithTimeBUI(tdClientI, insID, "尝试报单 ordID." + str_ref);
    tdApiList::iterator i = tdAPIs.begin();
    advance(i, tdClientI);
    int r = (*i)->ReqOrderInsert(pInputOrder, ++nRequestID); //这一句,向交易所报单
    logBUI(tdClientI, insID, "返回" + toStr(r));
    return r;
}
///C++与C#对接的报单函数
CTP_API void __stdcall _ReqOrderInsert(char* BrokerID, char* InvestorID, char* InstrumentID, 
char* ExchangeID, char* OrderRef, char* UserID, int OrderPriceType, int Direction, 
int CombOffsetFlag, int CombHedgeFlag, double LimitPrice, int VolumeTotalOriginal, 
int TimeCondition, char* GTDDate, int VolumeCondition, int MinVolume, 
int ContingentCondition, double StopPrice, int ForceCloseReason, int IsAutoSuspend, 
char* BusinessUnit, int RequestID, int UserForceClose, int IsSwapOrder, int tdClientI)
{
    CThostFtdcInputOrderField req;
    memset(&req, 0, sizeof(req));
    if (BrokerID != NULL) strcpy(req.BrokerID, BrokerID);//经纪公司代码
    if (InvestorID != NULL) strcpy(req.InvestorID, InvestorID);//投资者代码
    if (InstrumentID != NULL) strcpy(req.InstrumentID, InstrumentID);//合约代码
    if (ExchangeID != NULL) strcpy(req.ExchangeID, ExchangeID);//所属交易所
    if (OrderRef != NULL) strcpy(req.OrderRef, OrderRef);//报单引用
    if (UserID != NULL) strcpy(req.UserID, UserID);//用户代码
    if (OrderPriceType == 1) req.OrderPriceType = THOST_FTDC_OPT_AnyPrice;//任意价
    else if (OrderPriceType == 2) req.OrderPriceType = THOST_FTDC_OPT_LimitPrice;//限价
    else if (OrderPriceType == 3) req.OrderPriceType = THOST_FTDC_OPT_BestPrice;//最优价
    else if (OrderPriceType == 4) req.OrderPriceType = THOST_FTDC_OPT_LastPrice;//最新价
    else if (OrderPriceType == 5) req.OrderPriceType = THOST_FTDC_OPT_LastPricePlusOneTicks;//最新价浮动上浮1个ticks
    else if (OrderPriceType == 6) req.OrderPriceType = THOST_FTDC_OPT_LastPricePlusTwoTicks;//最新价浮动上浮2个ticks
    else if (OrderPriceType == 7) req.OrderPriceType = THOST_FTDC_OPT_LastPricePlusThreeTicks;//最新价浮动上浮3个ticks
    else if (OrderPriceType == 8) req.OrderPriceType = THOST_FTDC_OPT_AskPrice1;//卖一价
    else if (OrderPriceType == 9) req.OrderPriceType = THOST_FTDC_OPT_AskPrice1PlusOneTicks;//卖一价浮动上浮1个ticks
    else if (OrderPriceType == 10) req.OrderPriceType = THOST_FTDC_OPT_AskPrice1PlusTwoTicks;//卖一价浮动上浮2个ticks
    else if (OrderPriceType == 11) req.OrderPriceType = THOST_FTDC_OPT_AskPrice1PlusThreeTicks;//卖一价浮动上浮3个ticks
    else if (OrderPriceType == 12) req.OrderPriceType = THOST_FTDC_OPT_BidPrice1;//买一价
    else if (OrderPriceType == 13) req.OrderPriceType = THOST_FTDC_OPT_BidPrice1PlusOneTicks;//买一价浮动上浮1个ticks
    else if (OrderPriceType == 14) req.OrderPriceType = THOST_FTDC_OPT_BidPrice1PlusTwoTicks;//买一价浮动上浮2个ticks
    else if (OrderPriceType == 15) req.OrderPriceType = THOST_FTDC_OPT_BidPrice1PlusThreeTicks;//买一价浮动上浮3个ticks
    else if (OrderPriceType == 16) req.OrderPriceType = THOST_FTDC_OPT_FiveLevelPrice;//五档价
    else return;//报单价格条件
    if (Direction > 0) req.Direction = THOST_FTDC_D_Buy;//买 
    else if (Direction < 0) req.Direction = THOST_FTDC_D_Sell;//卖 
    else return;//买卖方向
    if (CombOffsetFlag == 1) req.CombOffsetFlag[0] = THOST_FTDC_OF_Open;//开
    else if (CombOffsetFlag == 0) req.CombOffsetFlag[0] = THOST_FTDC_OF_Close;//平
    else if (CombOffsetFlag == -1) req.CombOffsetFlag[0] = THOST_FTDC_OF_CloseToday;//平今
    else if (CombOffsetFlag == -2) req.CombOffsetFlag[0] = THOST_FTDC_OF_CloseYesterday;//平昨
    else if (CombOffsetFlag == -3) req.CombOffsetFlag[0] = THOST_FTDC_OF_ForceClose;//强平
    else if (CombOffsetFlag == -4) req.CombOffsetFlag[0] = THOST_FTDC_OF_ForceOff;//强减
    else if (CombOffsetFlag == -5) req.CombOffsetFlag[0] = THOST_FTDC_OF_LocalForceClose;//本地强平
    else return;//组合开平标志
    if (CombHedgeFlag == 0) req.CombHedgeFlag[0] = THOST_FTDC_HF_Speculation;//投机
    else if (CombHedgeFlag == 1) req.CombHedgeFlag[0] = THOST_FTDC_HF_Arbitrage;//套利
    else if (CombHedgeFlag == 2) req.CombHedgeFlag[0] = THOST_FTDC_HF_Hedge;//套保
    else return;//组合投机套保标志
    if (LimitPrice > 0) req.LimitPrice = LimitPrice;//价格
    req.VolumeTotalOriginal = VolumeTotalOriginal;//数量
    if (TimeCondition == 1) req.TimeCondition = THOST_FTDC_TC_IOC;//立即完成,否则撤销 
    else if (TimeCondition == 2) req.TimeCondition = THOST_FTDC_TC_GFS;//本节有效 
    else if (TimeCondition == 3) req.TimeCondition = THOST_FTDC_TC_GFD;//当日有效 
    else if (TimeCondition == 4) req.TimeCondition = THOST_FTDC_TC_GTD;//指定日期前有效 
    else if (TimeCondition == 5) req.TimeCondition = THOST_FTDC_TC_GTC;//撤销前有效 
    else if (TimeCondition == 6) req.TimeCondition = THOST_FTDC_TC_GFA;//集合竞价有效 
    else return;//有效期类型
    if (GTDDate != NULL) strcpy(req.GTDDate, GTDDate);//GTD日期
    if (VolumeCondition == 1) req.VolumeCondition = THOST_FTDC_VC_AV;//任何数量 
    else if (VolumeCondition == 2) req.VolumeCondition = THOST_FTDC_VC_MV;//最小数量 
    else if (VolumeCondition == 3) req.VolumeCondition = THOST_FTDC_VC_CV;//全部数量 
    else return;//成交量类型
    req.MinVolume = MinVolume;//最小成交量
    if (ContingentCondition == 1) req.ContingentCondition = THOST_FTDC_CC_Immediately;//立即
    else if (ContingentCondition == 2) req.ContingentCondition = THOST_FTDC_CC_Touch;//止损
    else if (ContingentCondition == 3) req.ContingentCondition = THOST_FTDC_CC_TouchProfit;//止赢
    else if (ContingentCondition == 4) req.ContingentCondition = THOST_FTDC_CC_ParkedOrder;//预埋单
    else if (ContingentCondition == 5) req.ContingentCondition = THOST_FTDC_CC_LastPriceGreaterThanStopPrice;//最新价大于条件价
    else if (ContingentCondition == 6) req.ContingentCondition = THOST_FTDC_CC_LastPriceGreaterEqualStopPrice;//最新价大于等于条件价
    else if (ContingentCondition == 7) req.ContingentCondition = THOST_FTDC_CC_LastPriceLesserThanStopPrice;//最新价小于条件价
    else if (ContingentCondition == 8) req.ContingentCondition = THOST_FTDC_CC_LastPriceLesserEqualStopPrice;//最新价小于等于条件价
    else if (ContingentCondition == 9) req.ContingentCondition = THOST_FTDC_CC_AskPriceGreaterThanStopPrice;//卖一价大于条件价
    else if (ContingentCondition == 10) req.ContingentCondition = THOST_FTDC_CC_AskPriceGreaterEqualStopPrice;//卖一价大于等于条件价
    else if (ContingentCondition == 11) req.ContingentCondition = THOST_FTDC_CC_AskPriceLesserThanStopPrice;//卖一价小于条件价
    else if (ContingentCondition == 12) req.ContingentCondition = THOST_FTDC_CC_AskPriceLesserEqualStopPrice;//卖一价小于等于条件价
    else if (ContingentCondition == 13) req.ContingentCondition = THOST_FTDC_CC_BidPriceGreaterThanStopPrice;//买一价大于条件价
    else if (ContingentCondition == 14) req.ContingentCondition = THOST_FTDC_CC_BidPriceGreaterEqualStopPrice;//买一价大于等于条件价
    else if (ContingentCondition == 15) req.ContingentCondition = THOST_FTDC_CC_BidPriceLesserThanStopPrice;//买一价小于条件价
    else if (ContingentCondition == 16) req.ContingentCondition = THOST_FTDC_CC_BidPriceLesserEqualStopPrice;//买一价小于等于条件价
    else return;//触发条件
    if (StopPrice > 0) req.StopPrice = StopPrice;//止损价
    if (ForceCloseReason == 0) req.ForceCloseReason = THOST_FTDC_FCC_NotForceClose;//非强平 
    else if (ForceCloseReason == 1) req.ForceCloseReason = THOST_FTDC_FCC_LackDeposit;//资金不足 
    else if (ForceCloseReason == 2) req.ForceCloseReason = THOST_FTDC_FCC_ClientOverPositionLimit;//客户超仓 
    else if (ForceCloseReason == 3) req.ForceCloseReason = THOST_FTDC_FCC_MemberOverPositionLimit;//会员超仓 
    else if (ForceCloseReason == 4) req.ForceCloseReason = THOST_FTDC_FCC_NotMultiple;//持仓非整数倍 
    else if (ForceCloseReason == 5) req.ForceCloseReason = THOST_FTDC_FCC_Violation;//违规 
    else if (ForceCloseReason == 6) req.ForceCloseReason = THOST_FTDC_FCC_Other;//其它 
    else if (ForceCloseReason == 7) req.ForceCloseReason = THOST_FTDC_FCC_PersonDeliv;//自然人临近交割 
    else return;//强平原因
    if (IsAutoSuspend > -2147483647) req.IsAutoSuspend = IsAutoSuspend;//自动挂起标志
    if (BusinessUnit != NULL) strcpy(req.BusinessUnit, BusinessUnit);//业务单元
    if (RequestID > -2147483647) req.RequestID = RequestID;//请求编号
    if (UserForceClose > -2147483647) req.UserForceClose = UserForceClose;//用户强评标志
    if (IsSwapOrder > -2147483647) req.IsSwapOrder = IsSwapOrder;//互换单标志
    ReqOrderInsert(&req, tdClientI);
}

 

与此牵连的代码还有很多,恕我无法完全贴在此处,因为实在是太多、太多了。需要深入了解的,可以私聊我,QQ是791269791,微信是minMove(博客园也有我的联系方式)。
在这里,我讲一下主要的思路,可能熟悉C++的同学直接就明白了。

我们要解决的问题是,把C++接口接到C#上。
之所以要这么做,是因为:
1.不得不有C++,因为上期官方提供的接口就是C++的;
2.但是C++又很麻烦,我们不想用它写应用逻辑;
3.应用逻辑,用C#来写比较省事,所以我们选择C#;
4.但是C#要听得懂C++“说话”才行啊,所以要把C++的接口接到C#上。

 

上期官方提供的接口,是些什么东东?

 

期货CTP接口C++源码与C#应用程序的对接

 

这是我的期货软件的最终的输出目录,exe文件就在里面,那么与exe文件在一起的,有很多dll,其中,上期官方提供的dll,就是“thostmduserapi_se”、“thosttraderapi_se”、“WinDataCollect”三个dll,也就是刚才说的,上期官方提供的接口。
“thostmduserapi_se”是行情接口,“thosttraderapi_se”是交易接口,这两个dll已经用了十年,我都不用看它那稀奇古怪的名字都能把它默写出来。
“WinDataCollect”则是去年6月才普及的东西,所谓“看穿式认证”需要的东西。

作为应用程序,作为exe文件,有了这些dll就足够与交易所对话了。
但作为开发者,这还不够。如果你要写C++部分的源代码,你需要的、来自上期官方的、传说中叫做“接口”的东西,除了上面说的三个dll,还有一些文件,总的来说,是这些东西:

error.dtd
error.xml
ThostFtdcMdApi.h
ThostFtdcTraderApi.h
ThostFtdcUserApiDataType.h
ThostFtdcUserApiStruct.h
thostmduserapi_se.dll
thostmduserapi_se.lib
thosttraderapi_se.dll
thosttraderapi_se.lib
DataCollect.h
WinDataCollect.dll
WinDataCollect.lib

我们刚才说的那三个dll文件就在其中。除此以外,还有:
库文件:以“lib”为后缀,让我们的C++项目能找到上期官方提供的dll。一个lib配一个dll,是配对的,比如,“thosttraderapi_se.lib”与“thosttraderapi_se.dll”配对,都是交易接口。
头文件:以“h”为后缀,是可以用记事本打开的,你可以清清楚楚地看到它的源代码,因为它就是拿来给你引用的,这里面有官方制作的很多函数,你可以在C++项目中引用。
至于怎么引用、怎么摆放这些文件,熟悉C++的同学不用说也知道,不熟悉的,说了也不明白,那必须看了示范项目才明白,可以进一步沟通。

 

指令和回报


这是期货接口的两个重要类型:

指令
就是我让CTP为我做什么,或者向它询问什么。
例如,登录时我把我的用户名、密码等告诉它,这就是登录指令。又比如,我向它查询合约ni2003的手续费,这属于查询指令。凡是指令,都是“我对CTP说的话”。

回报
我发了指令之后,CTP答复我,比如说我登录是否成功,我问的合约的手续费是多少,这都属于回报。
另外,有时候我没发指令,CTP也会主动发消息给我,比如某合约的行情数据、某笔单子的成交消息,这也属于回报。
回报就是“CTP对我说的话”。

记住啊,重要的事情说三遍,指令与回报,是CTP接口的核心内容。
期货交易的事,充斥着指令与回报的来回。我们与交易所的对话,就是指令与回报的来回。
我告诉交易所,我要以涨停价买2手ni2003,这是指令。
然后,这个单子挂上了,交易所把单子挂上的信息反馈给我,这是回报。
有1手成交了,成交回报又到了。
我想撤剩下的那手,把指令发给交易所。
撤单成功后,交易所把撤单回报发给我。
……
诸如此类,反复循环,这就是交易的过程。
而且,订阅行情、登录、查询……也都是由指令与回报的来来回回组成的。
现在你足够明白指令与回报的重要性了吧?

 

常用的指令与回报

 

指令与回报常常是成对的,比如登录指令ReqUserLogin与登录回报OnRspUserLogin就是一对,按这个规律,我们把常用的指令与回报整理一下。

 

类型 名称             说明

指令 RegisterSpi、RegisterFront  连接行情服务器
回报 OnFrontConnected      行情连接回报

指令 ReqUserLogin        登录行情服务器
回报 OnRspUserLogin       行情登录回报

指令 SubscribeMarketData     订阅行情
回报 OnRtnDepthMarketData    行情推送

指令 UnSubscribeMarketData    退订行情(无回报)
回报

指令
回报 onRspMdError         行情报错(无指令)

指令 RegisterSpi、RegisterFront   连接交易服务器
回报 OnFrontConnected       交易连接回报

指令 GetTradingDay         获取当前交易日(直接返回交易日数值,无回报)
回报

指令 ReqAuthenticate        请求客户端认证
回报 OnRspAuthenticate       客户端认证回报

指令 ReqUserLogin         登录交易服务器
回报 OnRspUserLogin        交易登录回报

指令 ReqUserLogout        登出交易服务器
回报 OnRspUserLogout       交易登出回报

指令 ReqSettlementInfoConfirm    投资者结算结果确认
回报 OnRspSettlementInfoConfirm   结算确认回报

指令 ReqQryInstrument        查询合约
回报 OnRspQryInstrument      查询合约回报

指令 ReqQryOrder          查询报单
回报 OnRspQryOrder        查询报单回报

指令 ReqQryInvestorPosition     查询持仓
回报 OnRspQryInvestorPosition    查询持仓回报

指令 ReqQryTradingAccount     查询资金账户
回报 OnRspQryTradingAccount    查询资金账户回报

指令 ReqQryInstrumentMarginRate  查询合约保证金率
回报 OnRspQryInstrumentMarginRate 查询合约保证金率回报

指令 ReqQryInstrumentCommissionRate 查询合约手续费率
回报 OnRspQryInstrumentCommissionRate 查询合约手续费率回报

指令 ReqQrySettlementInfo       查询结算单
回报 OnRspQrySettlementInfo     查询结算单回报

指令 ReqQryProduct          查询产品
回报 OnRspQryProduct         查询产品回报

指令 ReqQryTrade           查询成交
回报 OnRspQryTrade          查询成交回报

指令 ReqQryInvestorPositionDetail    查询投资者持仓明细
回报 OnRspQryInvestorPositionDetail   查询持仓明细回报

指令 ReqQryInvestor          查询投资者
回报 OnRspQryInvestor         查询投资者回报

指令 ReqOrderInsert、ReqOrderAction  报单、撤单
回报 OnRtnOrder           报单回报或撤单回报(靠参数区分回报类型)
回报 OnRspOrderInsert         报单被前置机拒绝的回报
回报 OnErrRtnOrderInsert        报单被交易所拒绝的回报
回报 OnRspOrderAction         撤单被前置机拒绝的回报
回报 OnErrRtnOrderAction        撤单被交易所拒绝的回报
回报 OnRtnTrade            成交回报

指令
回报 OnRspTdError           交易报错(无指令)

 

可以看出,指令函数绝大多数都以“Req”开头,回报函数都以“On”开头。
记住这个“命名潜规则”,你在阅读C++代码时,就容易区分指令与回报了。
然后呢,指令是怎么发出去的?回报是怎么从交易所来到我们的计算机上的?
更具体地说,指令是怎么由C#传到C++的?回报是怎么从C++传到C#的?
现在来研究这些问题。

 

指令流程

 

以连接行情的指令为例。
C#中的函数是:

///C++所输出的dll的位置
const string dllPath = @"CTP_se.dll";
///引用C++中的函数“_connectMd”
[DllImport(dllPath, EntryPoint = "_connectMd")]
///C#中的函数,“extern”标签决定了它要按照上一句话的规定引用C++中的函数“_connectMd”
static extern int ConnectMd(
char[] pszFrontAddress //行情服务器地址(字符串,必填)
);
public int connectMd(string tryingMdAdd)
{
    return ConnectMd(tryingMdAdd.ToCharArray());
}

C++中的主要函数是:

int connectMd(char *pszFrontAddress) {
    string str(pszFrontAddress);
    logWithTime("在已经有" + toStr(mdClientsN) + "个用户时,有新用户尝试行情连接" + str);
    MDSPI* mdProxy = new MDSPI();
    mdProxy->clientI = mdClientsN;
    mdSPIs.push_back(mdProxy);
    CThostFtdcMdApi* mdApi = CThostFtdcMdApi::CreateFtdcMdApi();
    mdAPIs.push_back(mdApi);
    mdClientsN++;
    log("mdClientsN = " + toStr(mdClientsN));
    mdApi->RegisterSpi((CThostFtdcMdSpi*)mdProxy);
    log("RegisterMdSpi");
    mdApi->RegisterFront(pszFrontAddress);
    logWithTime("RegisterFront(注册行情地址)" + str + "完毕");
    mdApi->Init();
    logWithTime("Init(初始化)完毕");
    logWithTime("为其分配序号" + toStr(mdClientsN - 1));
    return mdClientsN - 1;
}
CTP_API int __stdcall _connectMd(char *pszFrontAddress)
{ return connectMd(pszFrontAddress); }

请谅解没有在此贴出相关的海量代码,但熟悉C++的同学可能知道这个C++项目的构造。
这里只举了行情连接指令的例子,其他指令的流程与此类似,请大家举一反三地去理解。

 

回报流程

 

回报可就比指令麻烦多了。
我们刚才写指令时,非常简单,就像一根针似的简单——C#直接找到C++里的函数,调用即可。
但是回报没法简单地顺着流程调用函数,回报的流程是先到C++再找C#,我们没有办法让C++直接调用C#的函数,因为没有这样的语法。
比如说行情连接回报吧,它来了,先到thostmduserapi_se.dll,这是上期官方提供的接口,是官方派到我们电脑里的“卧底”,官方的消息来了当然先找它。
然后,它把消息传给C#,C#作为界面应用程序做些收到消息后该做的事。
就这么简单吗?做梦。
首先,消息不能从官方的dll直接跳到我们的C#里,因为我们没法修改官方的dll。
那么它得通过一个中介,我们自制的dll,一个C++版的dll,我们通常给它命名为“CTP.dll”(不光是我,很多开发者都是这样给这个中介命名的),这是一个翻译,负责官方dll与我们C#程序之间的沟通。。
官方dll与CTP.dll对话是没有障碍的,剩下的问题就全在于,CTP.dll怎么与C#对话。他的母语是C++,他懂C#,所以他能当这个翻译。
他又怎么懂C#?
我要提前把结论放在这儿,然后再说理由。结论是:CTP.dll能够找到C#的某个函数的内存地址,以便在需要让这个函数做点什么时把它激活,以这种方式让C++的消息遥控C#的某个动作,也就当了翻译,而并不是说可以让一个C++程序直接读C#代码。
内存地址,这是关键词。
紧紧抓住这个关键词,我们来理解以下流程。
以行情连接回报为例。
回报来到我们的电脑里,首先找到CTP官方提供的thostmduserapi_se.dll,再找到我们自制的CTP.dll(这两个都是C++的啊),调用回报处理函数“OnFrontConnected”。

void CTP_API MDSPI::OnFrontConnected()
{
    if (toDoOnMdFrontConnected != NULL) toDoOnMdFrontConnected(MDSPI::clientI);
}

也就是说,当行情连接回报来到本机的时候,我们要做的是这件事:如果“toDoOnMdFrontConnected”这个东西非空,就调用它。
那么,“toDoOnMdFrontConnected”是个什么东西呢?
它是一个函数指针。
它指向哪里呢?
指向界面程序在收到行情连接回报后要执行的一个函数。这个函数,写在界面程序里:

void onMdFrontConnected(int clientI)
{
    //……
}

我们姑且不管省略号表示的是什么(收到行情回报后到底要做什么),先来解决这个问题:在C#里要做的这件事,如何能让C++遥控它。
要把这个函数的内存地址告诉C++,也就是说,在C++中注册这个函数的指针:

CTP_API void WINAPI registerOnMdFrontConnected(ToDoOnMdFrontConnected callback) 
{ toDoOnMdFrontConnected = callback; }

这就是注册函数指针的动作,这里面的“callback”是C#传来的函数指针,“toDoOnMdFrontConnected”是C++对此函数指针的记录。
这个“registerOnMdFrontConnected”,是一个C++指令,C#要调用它就很简单了。C#调用它,实际上就是把函数指针告诉C++,具体来说:
1.首先要在C#中为上述函数onMdFrontConnected创建一个代理,因为它自己是没法把内存地址传给C++的,只有代理可以做到。
2.C#调用C++函数registerOnMdFrontConnected,参数就是上述代理,这样,就把函数onMdFrontConnected的内存地址传给了C++。
然后,C++收到交易所消息时,执行OnFrontConnected,发现toDoOnMdFrontConnected非空,就执行toDoOnMdFrontConnected,这其实是C#函数onMdFrontConnected的指针,于是就执行了这个C#函数。
就这样,C++隔空发力遥控C#的一幕,就神奇地实现了。
于是,我们收到交易所的消息需要记录数据库需要校正记录,需要撤旧单开新仓,需要检测程序化策略,需要做这个做那个时,再也不用麻烦C++了,在我们熟悉的C#语言里写就行了。
于是,我们的交易软件,再不是冷漠的黑色DOS窗口了,而可以是——

期货CTP接口C++源码与C#应用程序的对接

期货CTP接口C++源码与C#应用程序的对接

期货CTP接口C++源码与C#应用程序的对接

期货CTP接口C++源码与C#应用程序的对接

期货CTP接口C++源码与C#应用程序的对接

期货CTP接口C++源码与C#应用程序的对接

上一篇:Hello CTP(九)——REM行情API


下一篇:Hello CTP(八)——REM仓位计算