这一章请跟随我对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](https://onthessh.com/wp-content/uploads/2024/09/image-1.png)
项目中其他示例,主要目的是进行全体数据类型的单元测试。
名称中带有empty的示例是在做所有集合类型的空集合测试,因为空集合在序列化和反序列化操作中有特殊性。