QRust(四)示例程式

dyf029發表於2024-11-11

這一章請跟隨我對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效率還是很高的。實際上時間消耗主要發生在呼叫過程中的序列化和反序列化操作上。

This image has an empty alt attribute; its file name is demo_speed.png

專案中其他示例,主要目的是進行全體資料型別的單元測試。

名稱中帶有empty的示例是在做所有集合型別的空集合測試,因為空集合在序列化和反序列化操作中有特殊性。

相關文章