https://blog.csdn.net/wowotuo/article/details/86669758
這裏指的手工封裝,是指不用外部類似swig專用的庫。
一、庫、配置
1、DLL 交互的庫
(1)libloading
https://github.com/nagisa/rust_libloading
(2)libc
Raw FFI bindings to platform libraries like libc.
https://github.com/rust-lang/libc
2、類型轉換庫
Rust封裝CTP,涉及兩個方面類型,一個是rust轉c++; 另一個是c++轉rust.
cbindgen: 可以根據此庫,把rust的rs文件生成相應的頭文件,提高效率,減少手工封裝過程。
https://crates.io/crates/cbindgen
rust-bindgen: 可以根據此庫將C/C++的頭文件,自動生成Rust 的C綁定文件。
https://github.com/rust-lang/rust-bindgen
二、CTP DLL資料
http://www.sfit.com.cn/5_2_DocumentDown.htm
Api有3種通訊模式:
• 對話通訊模式:由客戶端主動發起請求。Thost收到請求、處理請求後,返回1條或者多條響應紀錄。例如登入、各項查詢、報單、撤單等操作。
• 私有通訊模式:由Thost主動向客戶端發出的相關信息。例如委託回報、成交回報、錯單回報等
• 廣播通訊模式:由Thost主動向所有客戶端發出的公共信息,例如行情等。
文件名 文件描述
FtdcTraderApi.h 交易接口頭文件
ThostFtdcMdApi.h 行情接口頭文件
FtdcUserApiStruct.h 定義了一系列業務相關的數據結構頭文件
FtdcUserApiDataType.h 定義了 API 所需的一系列數據類型頭文件
thosttraderapi.dll,thostmduserapi.dll 動態鏈接庫二進制文件
thostraderapi.lib,thostmduserapi.lib 導入庫文件
CTP相關知識、封裝具體的介紹可以參考:
https://zhuanlan.zhihu.com/p/20031646
Python量化交易平臺開發教程系列1-類CTP交易API的工作原理
https://zhuanlan.zhihu.com/p/20031660
Python量化交易平臺開發教程系列2-類CTP交易API的Python封裝設計
關於測試環境SIMNOW:
CTP開發中使用的模擬賬號密碼,要到SIMNOW上註冊。BrokerID爲9999,賬號即investorId,密碼爲SIMNOW的登陸密碼。
三、CTP中 C++ .h文件的改寫
在ctp文件中,有一類是數據結構.h;一類是類和虛方法接口的.h文件;量大,需要寫一個自動轉換的程序。
1、ThostFtdcUserApiDataType.h和ThostFtdcUserApiStruct.h
(1)ThostFtdcUserApiDataType.h頭文件中的類型
比如:
/
///TFtdcTraderIDType是一個交易所交易員代碼類型
/
typedef char TThostFtdcTraderIDType[21];
/
///TFtdcInvestorIDType是一個投資者代碼類型
/
typedef char TThostFtdcInvestorIDType[13];
/
///TFtdcBrokerIDType是一個經紀公司代碼類型
/
typedef char TThostFtdcBrokerIDType[11];
/
///TFtdcBrokerAbbrType是一個經紀公司簡稱類型
/
typedef char TThostFtdcBrokerAbbrType[9];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(2)ThostFtdcUserApiStruct.h中結構體
struct CThostFtdcDisseminationField
{
TThostFtdcSequenceSeriesType SequenceSeries;
TThostFtdcSequenceNoType SequenceNo;
};
1
2
3
4
5
要轉換成rust 中的結構體:[名字和類型可以沿用原來的,只是換個方式],比如:
pub struct CThostFtdcDisseminationField
{
pub SequenceSeries : i16 ,
pub SequenceNo : i32 ,
}
pub struct CThostFtdcReqUserLoginField
{
pub TradingDay : & 'static str ,
pub BrokerID : & 'static str ,
pub UserID : & 'static str ,
pub Password : & 'static str ,
pub UserProductInfo : & 'static str ,
pub InterfaceProductInfo : & 'static str ,
pub ProtocolInfo : & 'static str ,
pub MacAddress : & 'static str ,
pub OneTimePassword : & 'static str ,
pub ClientIPAddress : & 'static str ,
pub LoginRemark : & 'static str ,
}
pub struct CThostFtdcRspUserLoginField
{
pub TradingDay : & 'static str ,
pub LoginTime : & 'static str ,
pub BrokerID : & 'static str ,
pub UserID : & 'static str ,
pub SystemName : & 'static str ,
pub FrontID : i32 ,
pub SessionID : i32 ,
pub MaxOrderRef : & 'static str ,
pub SHFETime : & 'static str ,
pub DCETime : & 'static str ,
pub CZCETime : & 'static str ,
pub FFEXTime : & 'static str ,
pub INETime : & 'static str ,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
轉換成rust中這個類型後,rust才能使用。
細節:這裏暫時沒有考慮int->c_int之類。只是提供一個方法。
2、tradeApi.h 和mdApi.h的改寫
(1)主要是把類改成struct;
tradeapi中:
class CThostFtdcTraderSpi
class TRADER_API_EXPORT CThostFtdcTraderApi
1
2
mdapi中:
class CThostFtdcMdSpi
class MD_API_EXPORT CThostFtdcMdApi
1
2
(2)還有虛方法改成接口 trait;比如:
virtual void OnFrontConnected(){};
///當客戶端與交易後臺通信連接斷開時,該方法被調用。當發生這個情況後,API會自動重新連接,客戶端可不做處理。
///@param nReason 錯誤原因
/// 0x1001 網絡讀失敗
/// 0x1002 網絡寫失敗
/// 0x2001 接收心跳超時
/// 0x2002 發送心跳失敗
/// 0x2003 收到錯誤報文
virtual void OnFrontDisconnected(int nReason){};
1
2
3
4
5
6
7
8
9
10
四、封裝:Rust與C++交互的核心技術
封裝的目標是什麼,簡單地說,需要根據http://www.sfit.com.cn/5_2_DocumentDown.htm提供的4個頭文件,兩個C++原生的dll,以及lib文件,生成一個(或兩個)封裝的dll:
使之,
能夠接受原生dll回調信息,與c++交互;
能夠接受rust中參數,與原生dll交互;
(一)關於相關庫
相關的庫還是不少的,下面只是一些例子。
cc-rs:
https://github.com/alexcrichton/cc-rs
A library to compile C/C++/assembly into a Rust library/application.
libloading
https://github.com/nagisa/rust_libloading
A memory-safer wrapper around system dynamic library loading primitives. The most important safety guarantee by this library is prevention of dangling-Symbols that may occur after a Library is unloaded.
bindgen
bindgen automatically generates Rust FFI bindings to C (and some C++) libraries.
https://github.com/rust-lang/rust-bindgen
(二)關於封裝
1、封裝Rust 調用原生dll部分
比如Req開頭的:
extern crate libloading as lib;
//const
#[cfg(target_arch = "x86")]
fn load_ordinal_lib() -> Library {
Library::new("D:\\ctp-api\\tradeapi\\thostmduserapi.dll").expect("thostmduserapi.dll")
}
#[cfg(target_arch = "x86")]
//virtual void RegisterFront(char *pszFrontAddress) = 0;
fn RegisterFront(pszFrontAddress: String) -> lib::Result<()> {
let lib = load_ordinal_lib();
unsafe {
let func: lib::Symbol<unsafe extern "C" fn(String) -> ()> = lib.get(b"RegisterFront")?;
Ok(func(pszFrontAddress))
}
}
//virtual void RegisterNameServer(char *pszNsAddress) = 0;
//#[cfg(target_arch = "x86")]
fn RegisterNameServer(pszNsAddress: String) -> lib::Result<()> {
let lib = lib::Library::new("D:\\ctp-api\\tradeapi\\thostmduserapi.dll")?;
unsafe {
let func: lib::Symbol<unsafe extern "C" fn(String) -> ()> =
lib.get(b"RegisterNameServer")?;
Ok(func(pszNsAddress))
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2、封裝原生dll回傳給rust的部分
比如:On打開頭的。
這個應是在C++中寫的。方向與上面是相反的。
// Rust: 供c++中調用
pub extern "C" fn onRspUserLogin(
pRspUserLogin: CThostFtdcRspUserLoginField,
pRspInfo: CThostFtdcRspInfoField,
nRequestID: i32,
bIsLast: bool,
) {
}
1
2
3
4
5
6
7
C++中:(大致的寫法,相當於僞代碼吧)
virtual void OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) {
this->onRspUserLogin(pRspUserLogin, pRspInfo, nRequestID, bIsLast) //調用rust中的函數
}
1
2
3
4
這樣,就把c++中的代碼和rust函數之間的消息傳遞建立起來了。
下面文章值得參考一下:
https://blog.csdn.net/guiqulaxi920/article/details/78653054
Rust與C交互(FFI)中複雜類型的處理
五、與CTP 相關的一些具體技術問題
建議可以參考一下:
https://github.com/tashaxing/CTPtest
其中的難點在於:
int main()
{
// 賬號密碼
cout << "請輸入賬號: ";
scanf("%s", gInvesterID);
cout << "請輸入密碼: ";
scanf("%s", gInvesterPassword);
// 初始化行情線程
cout << "初始化行情..." << endl;
g_pMdUserApi = CThostFtdcMdApi::CreateFtdcMdApi(); // 創建行情實例
CThostFtdcMdSpi *pMdUserSpi = new CustomMdSpi; // 創建行情回調實例
g_pMdUserApi->RegisterSpi(pMdUserSpi); // 註冊事件類
g_pMdUserApi->RegisterFront(gMdFrontAddr); // 設置行情前置地址
g_pMdUserApi->Init(); // 連接運行
// 初始化交易線程
cout << "初始化交易..." << endl;
g_pTradeUserApi = CThostFtdcTraderApi::CreateFtdcTraderApi(); // 創建交易實例
//CThostFtdcTraderSpi *pTradeSpi = new CustomTradeSpi;
CustomTradeSpi *pTradeSpi = new CustomTradeSpi; // 創建交易回調實例
g_pTradeUserApi->RegisterSpi(pTradeSpi); // 註冊事件類
g_pTradeUserApi->SubscribePublicTopic(THOST_TERT_RESTART); // 訂閱公共流
g_pTradeUserApi->SubscribePrivateTopic(THOST_TERT_RESTART); // 訂閱私有流
g_pTradeUserApi->RegisterFront(gTradeFrontAddr); // 設置交易前置地址
g_pTradeUserApi->Init(); // 連接運行
// 等到線程退出
g_pMdUserApi->Join();
delete pMdUserSpi;
g_pMdUserApi->Release();
g_pTradeUserApi->Join();
delete pTradeSpi;
g_pTradeUserApi->Release();
// 轉換本地k線數據
//TickToKlineHelper tickToKlineHelper;
//tickToKlineHelper.KLineFromLocalData("market_data.csv", "K_line_data.csv");
getchar();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
其中,
(1) 初始化行情線程和初始化交易線程中難點;
(2) 這裏c++的類在rust中只能用struct對應;
(3) 註冊、回調和訂閱在rust 中如何實現?
待續…
————————————————
版權聲明:本文爲CSDN博主「songroom」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/wowotuo/article/details/86669758