這一章請跟隨我對QRust專案攜帶的demo示例講解,逐漸熟悉並掌握QRust的使用。
無引數、無返回值的示例
先從最簡單示例foo()開始。
Qt端:
void MainWindow::on_btn_foo_clicked() { ui->ptext_out->appendPlainText("------------- foo() -------------"); Rust rust("foo"); rust.call(); if (!rust.fun_ok()) { ui->ptext_out->appendPlainText("Function execution failure"); return; } ui->ptext_out->appendPlainText("Function execution success"); }
這是一個沒有引數也沒有返回值的呼叫,Rust rust(“foo”); 這一句在例項化呼叫物件的同時設定了業務函式的字串對應名稱,rust.call()發起呼叫,call是同步呼叫函式,表示呼叫會阻塞在這裡,直到收到Rust端的返回。rust.fun_ok()來判斷呼叫過程是否異常。
Rust端match部分:
match fun_name { "foo" => { api::foo(); Ok(None) } ...... }
匹配到”foo”字串後,呼叫業務函式api::foo(), OK(None)表示業務函式沒有返回值。
Rust端業務函式:
pub fn foo() { debug!("foo() running..."); }
有1個引數,無返回值的示例
Qt端:
Rust rust("foo1"); qint32 i = 100; QByteArray ba1 = QRust_Ser::pack_i32(i); rust.call(ba1);
在foo1示例中傳遞了一個int型別引數100,QRust_Ser::pack_i32(i) 語句進行引數序列化打包,然後rust.call(ba1)將打包後的引數隨呼叫傳遞給Rust。
Rust端match部分:
let a1 = de::from_pack(args.pop().unwrap())?;
api::foo1(a1);
Ok(None)
de::from_pack反序列化得到引數100,然後呼叫業務函式foo1。
Rust端業務函式:
pub fn foo1(arg: i32) { debug!("foo1() running... arg: {}", arg); }
有多個引數的示例
Qt端:
Rust rust("foo2"); //引數 1 Parameter 1 QList<qint32> a1 = {100}; QByteArray ba1 = QRust_Ser::pack_list_i32(&a1); //序列化 Serialization //引數 2 Parameter 2 QHash<qint32, QString> a2 = {{1, "abcde"}}; QByteArray ba2 = QRust_Ser::pack_hi_str(&a2); //序列化 Serialization //引數 3 Parameter 3 QHash<QString, QString> a3 = {{"a", "12345中文"}}; QByteArray ba3 = QRust_Ser::pack_hs_str(&a3); //序列化 Serialization rust.call(ba1, ba2, ba3); if (!rust.fun_ok()) { ui->ptext_out->appendPlainText("Function execution failure"); return; } //反序列化 deserialization QHash<QString, QString> ret; if (!QRust_De::upack_hs_str(rust.pop(), &ret)) { ui->ptext_out->appendPlainText("Deserialization failure"); return; }
這個示例傳遞了三個引數,並透過QRust_De::upack_hs_str反序列化得到業務函式的返回值,rust.pop()獲取並消耗第一個返回值,如果業務函式存在多個返回值,再次呼叫rust.pop()即可獲得第二個。反序列化時需要先定義結果變數,對應是QHash<QString, QString> ret語句,然後將ret的指標傳給upack_hs_str函式, upack_hs_str內部反序列化後將結果值寫入ret變數。
Rust端match部分:
let a1 = de::from_pack(args.pop().unwrap())?; let a2 = de::from_pack(args.pop().unwrap())?; let a3 = de::from_pack(args.pop().unwrap())?; let ret = api::foo2(a1, a2, a3); let pack = ser::to_pack(&ret)?; Ok(Some(pack))
分別反序列化獲得三個引數,注意要和Qt端的順序一致。業務函式的返回值透過to_pack序列化組包,然後返回給呼叫者。
這裡需要說一下to_pack函式的引數,對於所有的集合型別和基本型別中的String和struct是傳的引用(例子中是&ret),而對於其他的基本型別都是傳值的,這是因為考慮到傳引用的型別都是不定長的,有可能佔用了很多記憶體,傳引用可以減少記憶體的消耗。
返回值被封裝成Ok(Some(pack))是有含義的,Ok表示函式呼叫成功而非異常,Some表示有值而非空。
Rust端業務函式:
pub fn foo2(arg1: Vec<i32>, arg2: HashMap<i32, String>, arg3: HashMap<String, String>) -> HashMap<String, String> { debug!("foo2() running... arg1: {:?}, arg2:{:?}, args:{:?}", arg1, arg2, arg3); arg3 }
自定義struct示例
這是個傳遞自定義struct資料的示例,先看Qt端的struct定義:
struct A { Q_GADGET Q_PROPERTY(bool a MEMBER a); Q_PROPERTY(QList<quint32> v MEMBER v); public: bool a; QList<quint32> v; };
第二章節對這個struct的定義有詳細的說明,再看呼叫部分:
Rust rust("foo_struct"); A a1; a1.a = true; a1.v.append(10); QByteArray ba1 = QRust_Ser::pack_struct(&a1); quint8 count = 5; QByteArray ba2 = QRust_Ser::pack_u8(count); rust.call(ba1, ba2); if (!rust.fun_ok()) { ui->ptext_out->appendPlainText("Function execution failure"); return; } QHash<QString, A> rets; if (!QRust_De::upack_hs_struct(rust.pop(), &rets)) { ui->ptext_out->appendPlainText("Deserialization failure"); return; }
可以看到在QRust中,自定義的struct的序列化和反序列化過程和前面的簡單變數是一樣的,並沒有複雜性。
Rust端match部分:
let arg = de::from_pack(args.pop().unwrap())?; let count = de::from_pack(args.pop().unwrap())?; let ret = api::foo_struct(arg, count); let pack = ser::to_pack(&ret)?; Ok(Some(pack))
Rust端業務函式:
pub fn foo_struct(arg: A, count: u8) -> HashMap<String, A> { debug!("foo_struct() runnint... count: {}, arg: {:#?}", count, arg); let mut v = HashMap::new(); for i in 0..count { v.insert(i.to_string(), arg.clone()); } v }
多個引數、多個返回值的示例
Qt端:
QList<bool> b = {true, false}; QList<qint8> i8 = {-1, 0, 1}; QList<qint16> i16 = {-1, 0, 1}; QList<qint32> i32 = {-1, 0, 1}; QList<qint64> i64 = {-1, 0, 1}; QList<quint8> u8 = {0, 1, 2}; QList<quint16> u16 = {0, 1, 2}; QList<quint32> u32 = {0, 1, 2}; QList<quint64> u64 = {0, 1, 2}; QList<float> f32 = {(float)0.1, (float)0.2, (float)0.3}; QList<double> f64 = {0.1, 0.2, 0.3}; QList<QString> str = {"abcde", "12345", "中文"}; QList<A> strcts; A a1; a1.a = true; a1.v.append(10); strcts.append(a1); QByteArray ba1 = QRust_Ser::pack_list_bool(&b); QByteArray ba2 = QRust_Ser::pack_list_i8(&i8); QByteArray ba3 = QRust_Ser::pack_list_i16(&i16); QByteArray ba4 = QRust_Ser::pack_list_i32(&i32); QByteArray ba5 = QRust_Ser::pack_list_i64(&i64); QByteArray ba6 = QRust_Ser::pack_list_u8(&u8); QByteArray ba7 = QRust_Ser::pack_list_u16(&u16); QByteArray ba8 = QRust_Ser::pack_list_u32(&u32); QByteArray ba9 = QRust_Ser::pack_list_u64(&u64); QByteArray ba10= QRust_Ser::pack_list_f32(&f32); QByteArray ba11= QRust_Ser::pack_list_f64(&f64); QByteArray ba12= QRust_Ser::pack_list_str(&str); QByteArray ba13= QRust_Ser::pack_list_struct(&strcts); Rust rust("test_list"); rust.call(ba1, ba2, ba3, ba4, ba5, ba6, ba7, ba8, ba9, ba10, ba11, ba12, ba13); QList<bool> ret_b; QList<qint8> ret_i8; QList<qint16> ret_i16; QList<qint32> ret_i32; QList<qint64> ret_i64; QList<quint8> ret_u8; QList<quint16> ret_u16; QList<quint32> ret_u32; QList<quint64> ret_u64; QList<float> ret_f32; QList<double> ret_f64; QList<QString> ret_str; QList<A> ret_strcts; QRust_De::upack_list_bool(rust.pop(), &ret_b); QRust_De::upack_list_i8(rust.pop(), &ret_i8); QRust_De::upack_list_i16(rust.pop(), &ret_i16); QRust_De::upack_list_i32(rust.pop(), &ret_i32); QRust_De::upack_list_i64(rust.pop(), &ret_i64); QRust_De::upack_list_u8(rust.pop(), &ret_u8); QRust_De::upack_list_u16(rust.pop(), &ret_u16); QRust_De::upack_list_u32(rust.pop(), &ret_u32); QRust_De::upack_list_u64(rust.pop(), &ret_u64); QRust_De::upack_list_f32(rust.pop(), &ret_f32); QRust_De::upack_list_f64(rust.pop(), &ret_f64); QRust_De::upack_list_str(rust.pop(), &ret_str); QRust_De::upack_list_struct(rust.pop(), &ret_strcts);
這個示例是對所有List型別的單元測試,將所有list型別引數傳入和傳回。
Rust端match部分:
let b = de::from_pack(args.pop().unwrap())?; let i8 = de::from_pack(args.pop().unwrap())?; let i16 = de::from_pack(args.pop().unwrap())?; let i32 = de::from_pack(args.pop().unwrap())?; let i64 = de::from_pack(args.pop().unwrap())?; let u8 = de::from_pack(args.pop().unwrap())?; let u16 = de::from_pack(args.pop().unwrap())?; let u32 = de::from_pack(args.pop().unwrap())?; let u64 = de::from_pack(args.pop().unwrap())?; let f32 = de::from_pack(args.pop().unwrap())?; let f64 = de::from_pack(args.pop().unwrap())?; let str = de::from_pack(args.pop().unwrap())?; let strct = de::from_pack(args.pop().unwrap())?; let (b, i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, str, strct) = api::test_list(b, i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, str, strct); let mut pack = Vec::new(); pack.extend(ser::to_pack(&b)?); pack.extend(ser::to_pack(&i8)?); pack.extend(ser::to_pack(&i16)?); pack.extend(ser::to_pack(&i32)?); pack.extend(ser::to_pack(&i64)?); pack.extend(ser::to_pack(&u8)?); pack.extend(ser::to_pack(&u16)?); pack.extend(ser::to_pack(&u32)?); pack.extend(ser::to_pack(&u64)?); pack.extend(ser::to_pack(&f32)?); pack.extend(ser::to_pack(&f64)?); pack.extend(ser::to_pack(&str)?); pack.extend(ser::to_pack(&strct)?); let pack_multi = ser::to_pack_multi(pack); Ok(Some(pack_multi))
多返回值時,每個返回打包後依次新增(extend)到多返回值的集合中,ser::to_pack_multi函式將集合再打包返回。
Rust端業務函式:
pub fn test_list(b:Vec<bool>, i8:Vec<i8>, i16:Vec<i16>, i32:Vec<i32>, i64:Vec<i64>, u8:Vec<u8>, u16:Vec<u16>, u32:Vec<u32>, u64:Vec<u64>, f32:Vec<f32>, f64:Vec<f64>, str:Vec<String>, strct: Vec<A>) -> (Vec<bool>, Vec<i8>, Vec<i16>, Vec<i32>, Vec<i64>, Vec<u8>, Vec<u16>, Vec<u32>, Vec<u64>, Vec<f32>, Vec<f64>, Vec<String>, Vec<A>) { debug!("b: {:?}", b); debug!("i8: {:?}", i8); debug!("i16: {:?}", i16); debug!("i32: {:?}", i32); debug!("i64: {:?}", i64); debug!("u8: {:?}", u8); debug!("u16: {:?}", u16); debug!("u32: {:?}", u32); debug!("u64: {:?}", u64); debug!("f32: {:?}", f32); debug!("f64: {:?}", f64); debug!("str: {:?}", str); debug!("strct: {:#?}", strct); (b, i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, str, strct) }
非同步呼叫示例
如果業務函式執行時間較長,同步呼叫將會阻塞Qt的視窗執行緒,造成視窗程式無響應。在QRust中非同步呼叫是透過Qt的訊號/槽機制來執行的。
Qt端mainwindow.h檔案:
private: Ui::MainWindow *ui; Rust *rust_asyn;
先定義一個Rust變數指標,非同步呼叫時要用到它。
再定義一個呼叫完成的槽函式:
private slots: void finish();
然後在建構函式中進行訊號和槽的繫結(mainwindow.cpp檔案):
rust_asyn = new Rust(); connect(rust_asyn, &Rust::fun_finish, this, &MainWindow::finish);
new Rust()例項化Rust物件,注意這裡沒有定義要呼叫的函式字串名。
我們再看呼叫時的程式碼:
rust_asyn->reset(); rust_asyn->setFunName("test_asyn"); qint32 i = 7; QByteArray ba1 = QRust_Ser::pack_i32(i); rust_asyn->asyncall(ba1);
同步呼叫時Rust物件隨呼叫完成就可以釋放了,而非同步呼叫時rust物件因為訊號和槽的原因,通常生命週期比較長,因此這裡的寫法和前面是有區別的:
- reset()會重置rust物件,這意味著rust物件是可以重複使用的。
- setFunName設定呼叫的業務函式名稱。
- asyncall說明這是一次非同步呼叫,asyncall函式是立即返回的,因此不佔用GUI執行緒。
QRust內部會監視呼叫,如果得到了Rust的返回,立即發出訊號,槽函式finish即刻執行:
void MainWindow::finish() { ui->btn_asyn->setEnabled(true); //enable button ui->btn_asyn->setStyleSheet("Color:black"); if (!rust_asyn->fun_ok()) { ui->ptext_out->appendPlainText("Function execution failure"); return; } qint32 ret; if (!QRust_De::upack_i32(rust_asyn->pop(), &ret)) { ui->ptext_out->appendPlainText("Deserialization failure"); return; } QString out = QString("result: %1").arg(QString::number(ret)); ui->ptext_out->appendPlainText(out); }
非同步呼叫是Qt端的技術,Rust端不關心也無法區分呼叫是同步還是非同步的。
Rust端match部分:
let i = de::from_pack(args.pop().unwrap())?; let ret = api::test_asyn(i); let pack = ser::to_pack(&ret)?; Ok(Some(pack))
Rust端業務函式:
pub fn test_asyn(i: i32) -> i32 { debug!("test_asyn() start..."); //休眠10秒 let ten_secs = time::Duration::from_millis(10_000); thread::sleep(ten_secs); debug!("test_asyn() finish..."); i + i }
效能測試示例
這個示例透過10萬次的呼叫,計算 1 + 2 + 3 + …… + 100000 的值。
Qt端:
quint64 n = 0; int max = 100000; ui->ptext_out->appendPlainText("------------- test speed -------------"); qint64 start = QDateTime::currentMSecsSinceEpoch(); for (int i = 1; i <= max; i++) { Rust rust("test_speed"); QByteArray ba1 = QRust_Ser::pack_u64(n); QByteArray ba2 = QRust_Ser::pack_i32(i); rust.call(ba1, ba2); if (!rust.fun_ok()) { ui->ptext_out->appendPlainText("Function execution failure"); return; } if (!QRust_De::upack_u64(rust.pop(), &n)) { ui->ptext_out->appendPlainText("Deserialization failure"); return; } } qint64 end = QDateTime::currentMSecsSinceEpoch(); QString out1 = QString("1 + 2 + 3 + ... + %1 = %2").arg(QString::number(max), QString::number(n)); QString out2 = QString("time: %1ms").arg(QString::number(end-start)); ui->ptext_out->appendPlainText(out1); ui->ptext_out->appendPlainText(out2);
Rust的match部分:
let n = de::from_pack(args.pop().unwrap())?; let i = de::from_pack(args.pop().unwrap())?; let n = test_speed(n, i); let pack = ser::to_pack(&n)?; Ok(Some(pack))
Rust端業務函式:
pub fn test_speed(n: u64, i: i32) -> u64 { n + i as u64 }
下面是在我的筆記本上執行效能測試示例的截圖,10萬次呼叫消耗1秒多時間,可以看到QRust效率還是很高的。實際上時間消耗主要發生在呼叫過程中的序列化和反序列化操作上。
專案中其他示例,主要目的是進行全體資料型別的單元測試。
名稱中帶有empty的示例是在做所有集合型別的空集合測試,因為空集合在序列化和反序列化操作中有特殊性。