一、CTPFutrueProvider
https://github.com/fouvy/CTPFutrueProvider
動态連結庫:CTPWrapper 項目概述
應用程式向導已為您建立了此 CTPWrapper DLL。
本檔案概要介紹組成 CTPWrapper 應用程式的每個檔案的内容。
CTPWrapper.vcproj
這是使用應用程式向導生成的 VC++ 項目的主項目檔案。
它包含生成該檔案的 Visual C++ 的版本資訊,以及有關使用應用程式向導選擇的平台、配置和項目功能的資訊。
CTPWrapper.cpp
這是主 DLL 源檔案。
其他标準檔案:
StdAfx.h, StdAfx.cpp
這些檔案用于生成名為 CTPWrapper.pch 的預編譯頭 (PCH) 檔案和名為 StdAfx.obj 的預編譯類型檔案。
其他注釋:
應用程式向導使用“TODO:”訓示應添加或自定義的源代碼部分。
二、PInvoke的方式
今天主要講以下的方式:
http://www.360doc.com/content/14/0211/00/3218170_351502832.shtml
從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:[email protected]
當然,如果有朋友願意和我一起把剩下的部分建立起來的話,那就更好了:D