从上期技术发布CTP接口以来,经过一些摸索和实践,现在已经可以在C#中调用CTP的接口,完成接受行情、下单等一系列功能。 在这里想把实现方法和大家交流一下,希望能给同在研究这个问题的海友一些启发,同时,也希望能得到各位前辈的指点。 由于实现方法并不属于上期技术官方提供,所以难免存在各种各样的问题及BUG,因此这里仅讨论技术方法,对使用该方法所产生的一切后果不承担任何责任。 另外,关于使用C#的好处与坏处,不想赘言,这就和我们的交易一样,亏损本身也是游戏的一部分,所以,如果选择C#的便利性来搭建你的平台,那么就请接受它的不足吧。
从C#的托管代码中,调用C++的非托管代码,主要是使用PInvoke的方式,但CTP的API接口中存在以下两个特点,使我们无法直接调用 1、各种请求(Req函数)是Api类的成员函数,而不是静态函数 2、各种响应,是需要继承Spi类之后,才能在重写的虚函数(是叫这名字吧。。。)中得到回传数据 针对这两个特点,我使用的方法是 1、新建WIN32项目(项目名:CTPWrapper),将Api类的成员函数扩展成静态函数,如: Code: ///创建TraderApi ///@param pszFlowPath 存贮订阅信息文件的目录,默认为当前目录 ///@return 创建出的UserApi extern "C" CTPWRAPPER_API void* CreateTraderApi(const char *pszFlowPath) { return CThostFtdcTraderApi::CreateFtdcTraderApi(pszFlowPath); }; ///注册前置机网络地址 ///@param pszFrontAddress:前置机网络地址。 ///@remark 网络地址的格式为:“protocol://ipaddress:port”,如:”tcp://127.0.0.1:17001”。 ///@remark “tcp”代表传输协议,“127.0.0.1”代表服务器地址。”17001”代表服务器端口号。 extern "C" CTPWRAPPER_API void TraderRegisterFront(void *instance,char *pszFrontAddress) { ((CThostFtdcTraderApi *)instance)->RegisterFront(pszFrontAddress); }; 其中的CTPWRAPPER_API 是 Code: #define CTPWRAPPER_API __declspec(dllexport) 就像这样,实现了两个标准的WIN32API,将其编译成DLL,从C#中就可以直接调用了。 C#中定义PInvoke方法如下 Code: [DllImport("CTPWrapper.dll")] internal static extern IntPtr CreateTraderApi(string pszFlowPath); [DllImport("CTPWrapper.dll")] internal static extern void TraderRegisterFront(IntPtr hTrader, String address); 在C#中创建并使用TraderApi Code: IntPtr _instance = CreateTraderApi(""); TraderRegisterSpi(_instance, this._listener.Instance); 2、使用函数指针从C++的函数中调用C#的方法 为保证C++的函数能够正确的调用C#的方法,函数指针的构成必须一致,因此分别在C++和C#中定义以下内容: Code: C++ ///登录请求响应 typedef void (WINAPI *OnRspUserLoginCallback)(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) ; C# Code: public delegate void UserLoginCallback(IntPtr pRspUserLogin, IntPtr pRspInfo, int nRequestID, [MarshalAs(UnmanagedType.U1)]bool bIsLast); (这里注意下bool类型的定义,在这里我被郁闷了好久) 然后在创建Spi类的时候将C#的回调函数指针作为参数传到C++中 Code: C++中Spi的构造函数 CTPTraderSpi::CTPTraderSpi(OnRspUserLoginCallback callback) { this->m_OnRspUserLoginCallback = callback; } 同1的方法,将构造函数扩展为WIN32API Code: extern "C" CTPWRAPPER_API void* WINAPI CreateTraderSpiClass(OnRspUserLoginCallback callback) { CTPTraderSpi *spi = new CTPTraderSpi(callback); return (void*)spi; } 这样,在C++中得到响应时,就可以调用C#的方法了 Code: ///登录请求响应 void CTPTraderSpi::OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) { if(this->m_OnRspUserLoginCallback) { (this->m_OnRspUserLoginCallback)(pRspUserLogin ,pRspInfo,nRequestID,bIsLast); } } 在C#中,这样使用 Code: UserLoginCallback userLoginCallback = new UserLoginCallback(this.OnUserLogin); _instance = CreateTraderSpiClass(userLoginCallback); OK,到这里,最最核心的东西已经说完了,剩下的都是些技巧和体力活了 比如Spi的回调函数有很多,总不可能在C#中创建好了再一个个传到C++里,怎办呢?其实我是用了个Struct,包好了一起传的 Code: /// <summary> /// 回调函数指针结构 /// </summary> [StructLayout(LayoutKind.Sequential)] internal struct CTPTraderSpiCallbackStruct { ///当客户端与交易后台建立起通信连接时(还未登录前),该方法被调用。 internal FrontConnectedCallback FrontConnectedCallback; ///当客户端与交易后台通信连接断开时,该方法被调用。当发生这个情况后,API会自动重新连接,客户端可不做处理。 ///@param nReason 错误原因 /// 0x1001 网络读失败 /// 0x1002 网络写失败 /// 0x2001 接收心跳超时 /// 0x2002 发送心跳失败 /// 0x2003 收到错误报文 internal FrontDisconnectedCallback FrontDisconnectedCallback; ///心跳超时警告。当长时间未收到报文时,该方法被调用。 ///@param nTimeLapse 距离上次接收报文的时间 internal HeartBeatWarningCallback HeartBeatWarningCallback; ///登录请求响应 internal UserLoginCallback UserLoginCallback; ///登出请求响应 internal UserLogoutCallback UserLogoutCallback; } 以下略 剩下还有一些细节问题,回头再慢慢说好了,整个过程被我封装成了两个工程 C++:CTPWrapper C#:CTPInterop 由于还有很多工作没有完成,也就不发布出来了,有兴趣的朋友可以通过邮件和我联系,我把工程代码发给你 mail:lumen.xh@gmail.com 当然,如果有朋友愿意和我一起把剩下的部分建立起来的话,那就更好了
C++部分其实很简单的,我也是大学之后就没碰过C++了,直接google+MSDN也基本就可以搞定的:) 其实,最多的是体力活。。。。Ctrl+C...Ctrl+V....Ctrl+C...Ctrl+V....