建立C#与CTP的接口

Discussion in 'CTP' started by LumenXH, Jul 13, 2009.

  1. 从上期技术发布CTP接口以来,经过一些摸索和实践,现在已经可以在C#中调用CTP的接口,完成接受行情、下单等一系列功能。
    在这里想把实现方法和大家交流一下,希望能给同在研究这个问题的海友一些启发,同时,也希望能得到各位前辈的指点。

    由于实现方法并不属于上期技术官方提供,所以难免存在各种各样的问题及BUG,因此这里仅讨论技术方法,对使用该方法所产生的一切后果不承担任何责任。

    另外,关于使用C#的好处与坏处,不想赘言,这就和我们的交易一样,亏损本身也是游戏的一部分,所以,如果选择C#的便利性来搭建你的平台,那么就请接受它的不足吧。
     
  2. 用VC建立托管代码的dll,把CTP接口的非托管代码的dll包装起来。然后这个托管的dll被C#调用?
     
  3. 进来学习学习。
     
  4. zwz

    zwz

    学习学习
     
  5. 从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

    当然,如果有朋友愿意和我一起把剩下的部分建立起来的话,那就更好了:D
     
  6. 看C++头都晕,难得有高手指点,谢谢了
     
  7. 谢谢,果然是高手啊。
     
  8. 感谢LumenXH为CTP提供C#接口的努力,您的努力将让CTP接入变得更加容易!
     
  9. 学习学习
     
  10. 谢谢,不过这个工程量很浩大啊。而且好像还需要借助c++工具。
     
  11. 装个VS2005或者VS2008,把C#和C++都装上

    LumenXH兄的这个工程是VS2005生成的吧?我用VS2008打开有个转换过程的
     
  12. 还是要感谢上期技术超前的目光,如果没有你们的努力搭建的平台,没有这种开放的接口,所有的一切都是空中楼阁。
    我们这才真的是站在巨人的肩头呢
     
  13. C++部分其实很简单的,我也是大学之后就没碰过C++了,直接google+MSDN也基本就可以搞定的:)

    其实,最多的是体力活。。。。Ctrl+C...Ctrl+V....Ctrl+C...Ctrl+V....
     
  14. 对,是VS2005,我的小本跑VS2008实在有些慢:)
     
  15. 我装的是Express 版的。小机器也跑得快功能上也不错。
     
  16. 用托管DLL 建立Adapter 类的方式在C# 调用会更容易些,
    双方都的程序都更native一点


    今天用了小半天的时间终于能够调用了,但是调试起来也满难的。
     
  17. 你也是用vc++做托管DLL吗?
     
  18. 是的,目前只实现了行情的托管。交易还没有时间做,白天忙, 晚上时间也不多。

    vc++托管还是比较容易的。
     
  19. 呵呵,不知有没有条件测试比较一下托管和非托管的执行效率
     
  20. 我看你在另外一个帖里说是采用扩展.h头文件的方法?好像和LumenXH方法不同,只需要扩展.h头文件就可以了?