Rust 為嵌入式軟件提供高性能、高可靠性和強(qiáng)大的安全性,并幫助開發(fā)人員檢查并減少?gòu)?fù)雜、低級(jí)應(yīng)用程序常見的內(nèi)存和線程錯(cuò)誤。憑借這些明顯的優(yōu)勢(shì),Rust 在嵌入式軟件開發(fā)人員中越來越受歡迎。同樣,作為跨平臺(tái)嵌入式應(yīng)用程序的框架之一,Qt 為嵌入式領(lǐng)域帶來了強(qiáng)大的業(yè)務(wù)邏輯功能。由于 Rust 還沒有的圖形用戶界面 (GUI),這意味著 Rust-Qt 集成對(duì)嵌入式開發(fā)人員越來越感興趣,并已成為向嵌入式 Rust 應(yīng)用程序添加 GUI 的關(guān)鍵方法之一。
將兩者整合在一起時(shí),挑戰(zhàn)就來了。如果沒有仔細(xì)的計(jì)劃和努力,組合的 Rust-Qt 應(yīng)用程序就會(huì)成為這兩種語(yǔ)言弱屬性的犧牲品。Qt/C++對(duì)Rust的調(diào)用可能不安全,域間可能會(huì)出現(xiàn)并發(fā)問題,Rust的高性能和可移植性可能會(huì)打折扣。
本文介紹了為嵌入式應(yīng)用程序?qū)崿F(xiàn) Rust 和 Qt 世界的推薦原則。
Rust 和 Qt 的背景
Rust 擁有豐富的庫(kù)生態(tài)系統(tǒng),用于序列化和反序列化、異步操作、解析不安全輸入、線程、靜態(tài)分析等,而 Qt 是一個(gè) C++ 工具包,支持跨各種平臺(tái)的豐富的、基于 GUI 的應(yīng)用程序,從 iOS 到嵌入式Linux。Qt 應(yīng)用程序包括表示業(yè)務(wù)邏輯的 C++ 插件、定義 GUI 組件和布局的 QML 以及用于 GUI 腳本的 JavaScript。
將 Rust 庫(kù)集成到 Qt 框架中,為開發(fā)人員提供了一種強(qiáng)大的機(jī)制來提供高性能和安全的基礎(chǔ),從而推動(dòng)復(fù)雜的用戶體驗(yàn)。考慮一個(gè)農(nóng)業(yè)儀表板應(yīng)用程序,該應(yīng)用程序需要安全訪問后端 I/O 微控制器或從基于 RTOS 的單板計(jì)算機(jī)提取數(shù)據(jù)的飛機(jī)駕駛艙顯示器。
一種推薦的應(yīng)用程序架構(gòu)是將業(yè)務(wù)邏輯保留在 Rust 后端,并為用戶界面使用 Qt/C++ 插件。這解耦了兩個(gè)域,將 Qt/QML 用于 GUI 開發(fā)的快速迭代速度和靈活性從業(yè)務(wù)級(jí)代碼中分離出來。
如何為嵌入式軟件集成 Rust 和 Qt
將 Qt 與 Rust 集成的常見方法是讓 Rust 調(diào)用 Qt 的 C++ 庫(kù)。雖然有幾種綁定方法,但這些方法通常不是 Rust 慣用的,并且往往會(huì)失去安全優(yōu)勢(shì),如圖 1 所示。此外,Qt 的大多數(shù) Rust 綁定不會(huì)將 Rust 代碼暴露給 C++,這使得它們難以集成到現(xiàn)有的 C++ 代碼庫(kù)中。

圖 1. Rust-Qt 綁定的挑戰(zhàn)()
一種更有效的方法是以安全的方式連接兩種語(yǔ)言,盡可能多地保留 Rust 固有的安全優(yōu)勢(shì)。為此,Rust 需要能夠使用自己的QObject子類和實(shí)例以的妥協(xié)來擴(kuò)展 Qt 對(duì)象系統(tǒng)。
后一種方法的一個(gè)例子是開源庫(kù)CXX-Qt,它由 KDAB 發(fā)起并管理其正在進(jìn)行的開發(fā)和改進(jìn)。該庫(kù)將 Qt 強(qiáng)大的面向?qū)ο蠛驮獙?duì)象系統(tǒng)與 Rust 相結(jié)合,并基于和擴(kuò)展了另一個(gè)開源 Rust/C++ 互操作性庫(kù)CXX 。在 CXX-Qt 中,新的QObject子類由 Rust 模塊中的項(xiàng)目組成,以執(zhí)行橋接功能。這些子類就像 QML 和 C++ 中的任何其他QObject一樣被實(shí)例化,將 Rust 特性暴露給 Qt。
CXX-Qt 定義的每個(gè)QObject都包含兩個(gè)組件:
一個(gè)基于 C++ 的對(duì)象,它是一個(gè)公開屬性和可調(diào)用方法的包裝器
存儲(chǔ)屬性、實(shí)現(xiàn)可調(diào)用對(duì)象、管理內(nèi)部狀態(tài)并處理來自屬性和后臺(tái)線程的更改請(qǐng)求的Rust結(jié)構(gòu)
CXX-Qt 自動(dòng)生成代碼以在 Rust 和 Qt/C++ 域之間傳輸數(shù)據(jù),并使用名為CXX的庫(kù)在兩者之間進(jìn)行通信。
CXX-Qt 橋接方法的主要原則
為了解釋有效的 Rust-Qt 橋是如何工作的,我們將描述 CXX-Qt 庫(kù)背后的幾個(gè)關(guān)鍵原則。
在 Rust 中聲明 QObject
Qt 的設(shè)計(jì)本質(zhì)上是面向?qū)ο蟮模瑢?duì)于 C++ 和 QML 都是如此,而 Rust 不支持繼承和多態(tài)性等常見的類特性。為了克服這個(gè)限制,CXX-Qt 在 Rust 中擴(kuò)展了 Qt 對(duì)象系統(tǒng),以提供一種自然的方式來集成兩種語(yǔ)言,同時(shí)保持慣用的 Rust 代碼。
CXX-Qt 橋可以包括以下部分:
模塊周圍的宏指示內(nèi)容與 CXX-Qt 相關(guān)
定義 QObject、Qt 的任何屬性和任何私有狀態(tài)的結(jié)構(gòu)
Qobject 結(jié)構(gòu)的可選實(shí)現(xiàn),其中函數(shù)可以標(biāo)記為 qinvokable,允許從 QML 和 C++ 調(diào)用它們
定義 QObject 信號(hào)的枚舉
普通 CXX 塊
CXX-Qt 在代碼生成期間將此 Rust 模塊擴(kuò)展為QObject的 C++ 子類和RustObj結(jié)構(gòu),如圖 2 所示。
圖 2. CXX-Qt 如何擴(kuò)展 QObjects 以供運(yùn)行時(shí)使用()
下面是一個(gè) QObject 示例,其中包含三個(gè)用于跨域操作的可調(diào)用方法和一個(gè) Rust-only 方法:
#[cxx_qt::bridge]
mod my_object {
不安全的外部“C++”{
包括!(“cxx-qt-lib/qstring.h”);
輸入 QString = cxx_qt_lib::QString;
包括!(“cxx-qt-lib/qurl.h”);
輸入 QUrl = cxx_qt_lib::QUrl;
}
#[cxx_qt::qobject]
#[推導(dǎo)(默認(rèn))]
酒吧結(jié)構(gòu)我的對(duì)象{
#[q屬性]
is_connected:布爾值,
#[q屬性]
網(wǎng)址:QUrl,
}
#[cxx_qt::qsignals(MyObject)]
酒吧枚舉連接{
連接的,
錯(cuò)誤{消息:QString},
}
實(shí)現(xiàn) qobject::MyObject {
#[qinvokable]
pub fn connect(mut self: Pin《&mut Self》, url: QUrl) {
self.as_mut().set_url(url);
如果自己
.as_ref()
.url()
.to_string()
.starts_with(“https://kdab.com”)
{
self.as_mut().set_is_connected(true);
self.emit(Connection::Connected);
} 別的 {
self.as_mut().set_is_connected(false);
self.emit(連接::錯(cuò)誤{
message: QString::from(“URL 不以 https://kdab.com” 開頭),
});
}
}
}
}
使用#[qinvokable]屬性聲明的方法(例如上面的connect)暴露給 QML 和 C++,參數(shù)和返回類型在 Qt 端匹配。使用#[qproperty] 屬性(例如上面的 is_connected)聲明的 QObject 結(jié)構(gòu)中的字段作為 Q_PROPERTY 公開給 QML 和 C++。用#[cxx_qt::qsignals(T)] 屬性標(biāo)記的枚舉定義了在 QObject T 上聲明的信號(hào)。請(qǐng)注意,CXX-Qt 會(huì)自動(dòng)在 snake_case (Rust) 和 CamelCase (Qt) 命名之間進(jìn)行轉(zhuǎn)換。
在此示例中,connect 方法用于從 Rust 改變 QObject 的 url 和 connected 屬性。然后發(fā)出一個(gè)信號(hào),指示連接是否成功或是否發(fā)生錯(cuò)誤。
沒有用屬性標(biāo)記的方法或字段被認(rèn)為是 Rust 私有的,可用于管理內(nèi)部狀態(tài)或用作線程實(shí)例數(shù)據(jù)。
由于QObject是橋的 C++ 端擁有的構(gòu)造 Qt 對(duì)象,因此它會(huì)在運(yùn)行時(shí)銷毀 C++ 對(duì)象時(shí)被銷毀。
跨橋聲明通用數(shù)據(jù)類型
原始數(shù)據(jù)類型和 CXX 類型可以跨橋使用,不需要在 Rust 和 Qt 之間進(jìn)行轉(zhuǎn)換。庫(kù) cxx_qt_lib 提供代表常見 Qt 類型(例如 QColor、QString、QVariant 等)的 Rust 類型,以供跨橋使用。
隨著項(xiàng)目的發(fā)展,我們計(jì)劃在cxx_qt_lib中加入更多常用的Qt類型,比如Qt容器類型(如QHash和 QVector) 和其他對(duì) Qt C++ 或 QML 有用的類型。然后,我們希望通過向 Rust 生態(tài)系統(tǒng)中已建立的板條箱添加轉(zhuǎn)換來增強(qiáng)這些類型。例如,我們計(jì)劃支持從 QColor 到顏色包類型的轉(zhuǎn)換或從 QDateTime 到日期時(shí)間包類型的轉(zhuǎn)換,或者使用 Rust 包對(duì) Qt 類型進(jìn)行(反)序列化等。
保持線程安全
Rust-Qt 應(yīng)用程序中安全線程背后的一般概念是每當(dāng) Rust 代碼執(zhí)行時(shí)在 C++ 端獲取一個(gè)鎖,以防止該代碼被多個(gè)線程執(zhí)行。這意味著所有直接從 C++ 調(diào)用的 Rust 代碼,例如可調(diào)用對(duì)象,僅在 Qt 線程上執(zhí)行。
為了允許開發(fā)人員將狀態(tài)從后臺(tái) Rust 線??程同步到 Qt,CXX-Qt 提供了一個(gè)幫助程序,允許您從后臺(tái)線程對(duì) Rust 閉包進(jìn)行排隊(duì)。然后從 Qt 事件循環(huán)中執(zhí)行閉包。
// 在可調(diào)用對(duì)象中,請(qǐng)求 qt 線程的句柄
讓 qt_thread = self.qt_thread();
// 產(chǎn)生一個(gè) Rust 線??程
std::thread::spawn(移動(dòng) || {
讓 value = compute_value_on_rust_thread();
// 使用閉包移動(dòng)值并在 Qt 事件循環(huán)中運(yùn)行任務(wù)
線程
.queue(移動(dòng)|mut qobject| {
// 發(fā)生在 Qt 事件循環(huán)中
qobject.set_value(值);
})
.unwrap();
});
將 Rust QObject 暴露給 QML
要在 Qt 應(yīng)用程序中使用 Rust 代碼,必須將 Rust QObject 導(dǎo)出到 QML。CXX-Qt 通過為Rust 中定義的每個(gè)QObject子類生成一個(gè) C++ 類來簡(jiǎn)化這個(gè)過程——開發(fā)人員只需要在 main.cpp 中包含兩個(gè)語(yǔ)句:
包括 QObject 類,例如,
#include “cxx-qt-gen/my_object.h”
將 QObject 類導(dǎo)出到 QML,例如,
qml注冊(cè)類型( “com.kdab.cxx_qt.demo” , 1 , 0 , “MyObject” );
構(gòu)建系統(tǒng)
CXX-Qt 支持使用 CMake 或 Cargo 構(gòu)建。
使用 CMake,Corrosion(以前稱為 cmake-cargo)用于將 Rust crate 作為靜態(tài)庫(kù)導(dǎo)入,然后鏈接到您的 Qt 應(yīng)用程序可執(zhí)行文件或庫(kù)。這允許將 CXX-Qt 輕松集成到現(xiàn)有的 CMake Qt 應(yīng)用程序中。構(gòu)建 Rust crate 時(shí),build.rs 文件會(huì)編譯 CXX-Qt 生成的任何 C++ 代碼。
對(duì)于使用 Rust 的 Cargo 構(gòu)建系統(tǒng)的項(xiàng)目,CXX-Qt 也可以在沒有 CMake 的情況下從 Cargo 使用。使用此方法,Rust 項(xiàng)目的 build.rs 文件還會(huì)觸發(fā) Qt 資源的構(gòu)建、任何其他 C++ 文件的編譯以及鏈接到任何其他 Qt 模塊。
結(jié)論
隨著集成 Rust 和 Qt 的用例的增長(zhǎng),開發(fā)團(tuán)隊(duì)?wèi)?yīng)該知道他們除了一對(duì)一直接綁定之外的選擇。CXX-Qt 庫(kù)提供了一種可行的橋接機(jī)制,以慣用的方式保留了 Rust 的線程安全和性能優(yōu)勢(shì),同時(shí)還允許開發(fā)人員使用普通的 Qt 和 Rust 代碼。
通過理解這里的概念,包括 Qt 對(duì)象和 Rust QObject之間的映射、Rust 的常見 Qt 類型以及定義運(yùn)行時(shí)互操作性的宏和代碼生成,開發(fā)人員可以更好地為他們的應(yīng)用程序開發(fā)選擇正確的 Rust-Qt 集成機(jī)制。
評(píng)論