文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>qt/embedded

qt/embedded

时间:2006-03-01  来源:zywsdu

以 Qt/Embedded 進行嵌入式系統的開發

Dr. Dobb's Journal March 2002

原作:Matthias Kalle Dalheimer and Steffen Hansen
繁體中文翻譯:黃敬群 <[email protected]>

Kalle 是 Klarälvdalens Datakonsult AB 公司的總裁兼執行長,可以透過電子郵件跟他聯絡:[email protected]。Steffen 是 Klarälvdalens Datakonsult AB 中的資深工程師,跟 Kalle 一樣,他也是 KDE 計畫的核心開發者,你可以跟他聯繫: [email protected].

當進行手持式裝置,如 iPAQ、Palm,與 Visor,的軟體開發時,我們總會感受到迎面而來的挑戰。一方面,使用者期望的是相當耗費系統資源的 GUI,像是虛擬鍵盤 (virtual keyboard) 之類的。而另一方面,你必須保有一般嵌入式系統中相似的空間與處理量 [譯注:言下之意應該是指你必須設計出使用者想要的華麗介面,但還必須好好考慮嵌入式系統中錙銖必較的資源。原文是 "On the other hand, you must contend with the space and processing constraints that are normal in the embedded world."]。基於以上的衡量,Linux 日漸被嵌入式裝置,如手持式電腦等,列入偏好的系統平台。不僅是因為 Linux 對系統資源的掌握相當有彈性 (特別是針對短少的 Flash 與 RAM 空間),也是因為特別有效率(there are no per-device royalties required when using Linux [譯注,這句註解我不能會意,所以保留原文])。本文中,我們會探討要如何使用 Qt/Embedded (http://www.trolltech.com/),來對手持式裝置開發Linux 為基礎的應用程式。Trolltech 推出的 C++ GUI Qt/Embedded toolkit 在 UNIX/X11 與 Linux 平台上,是以 GPL 與商業授權的方式發行的。

Qt/Embedded 與其他用以開發嵌入式裝置的 toolkit 不同的是,Qt/Embedded 並不只是針對嵌入式裝置而設計,相反的,這是來自桌上版本 (UNIX/X11、Windows,與 MacOS X 等平台) Qt 的移植。因此,你可以持續開發桌面應用程式的經驗,來進行嵌入式應用程式開發,因此,你不必重新學習一套新的 API,也不需要適應新的程式設計技巧。而這並不是說,你該直接原本桌面應用程式到精簡型的電腦裝置上。事實上,手持式裝置對於螢幕尺寸、drag-and-drop,與記憶體資源都有相當的需求差異。不過,Qt 與 Qt/Embedded 在基本的 API 上都是一致的。

理論上,Qt/Embedded 能夠在任何可跑 Linux framebuffer 的嵌入式裝置上運作。但實際上,每種新的設備或多或少需要點移植的動作。已經支援 Qt/Embedded 的裝置包含 Compaq iPAQ 與 HP Cassiopeia [譯注:其實據 Trollech 和協力廠商的說法,已經在更多的硬體平台支援了]。這裡我們用 iPAQ 作為 Qt 程式開發的的示範平台,當然,這些技巧對相似的設備都可適用。

熱身運動

當然,你第一步的動作就是在 iPAQ (預先安裝了 Microsoft 的 Pocket Windows) 安裝並設定 Linux,然後在自己的電腦安裝開發套件 (也就是 "tool chain")。既然安裝的程序已經不在本文的範圍,所以我們建議你閱讀線上文件,請見 "Resource Center" 的第五頁。並且,因為記憶體在嵌入式設備中通常是重要的資源,所以你不必把 Qt 全部納入,像是XML parsers、table 元件,與網路模組等就可以考慮去除,只要保留在你的應用程式所需要的功能即可。詳細的資料,請見 "Making Qt/Embedded Lean and Mean" [譯注:本文也有中文翻譯,請見 "如何精簡 Qt/Embedded"] 一文。

一旦 Linux 已經安裝在 iPAQ 上,你將會看到一個閃動的游標與提示符號。接著,你可以安裝 Qt Palmtop Environment (QPE [譯注:現在 Trolltech 改稱這項產品為 Qtopia],請見 http://qpe.sourceforge.net/),這裡頭包含為數不少的應用程式。然而,QPE 卻沒有內建財務處理的應用程式,因此,我們現在將開發這樣的程式出來,姑且稱為 "Expenses" 好了。我們要達到的目的是可以由使用者輸入 / 編輯 / 刪除金融記錄、給予不同的匯率,以及把這些項目分類。

Signals 與 Slots 機制

Qt 中最顯著的特徵就屬 signals 與 slots 機制了。每個 GUI toolkit 都必須處理程式功能 (如呼叫一個 method) 間如何聯繫與使用者互動的問題 (舉例來說,就是指按下按鈕或選擇一個選單項目這樣的動作)。基本的想法是一個元件 (如 GUI 元件) 在某件事發生時,可以觸發 (emit) 對應的 signals [譯注:這邊的 signal 是通用性的說法,表示某種訊息、信號的用語,與 UNIX 系統的 SIGNAL 無關],而這個「某件事」可以是使用者按下按鈕、資料經由網路傳輸過來,或是其他的。另一方面,slots 簡單來說,就是用以處理某個任務的方式。你可以透過 QObject::connect() 來聯繫 signals 與 slots。舉例來說,ExpensesMainWindow (繪製程式主視窗的 class) 的 constructor 中 (完整程式碼在 ExpensesMainWindow.cpp),可以發現以下程式碼片段:

 connect( _newExpenseAction, SIGNAL( activated() ), this, SLOT( slotNewExpense() ) );

這上面的程式碼片段就定義了:一旦 _newExpenseAction 物件觸發 (emit) 了 activated() 的 signal 時,Qt 核心就會喚起 (invoke) 當前物件中的 slotNewExpense() method。 _newExpenseAction 是一種 QAction* type,而 QAction 是一個抽象 (abstract) 的 class,用來表示某種動作、行為的概念。你可以想像在一般程式中,我們都是透過程式中 Menu、Button 這樣的 Widget 來操控我們的程式,而一旦程式從這些 Widget 收到使用者的輸入後,變會開始進行後端的處理,可能是開啟某個檔案這樣的動作,最後,再將執行結果顯示出來,以讓使用者知道剛剛的動作會造成什麼反應,用來表示以上的概念,就是 QAction [譯注:原文過於簡化,所以我用自己的說法重新說了一次,原文是這樣的: ", which is a class that abstracts from how users invoke a certain functionality; this can currently be via menu items or toolbar buttons."]。一旦使用者喚起這樣的功能,activated() 這個 signal 就會被 QAction 所觸發 (emitted)。Qt reference documentation 裡頭有指明 Qt class 中有哪些 signal 和 slot。slotNewExpense() 這個 slot 就是我們要在 ExpensesMainWindow 實作的功能。

再說一次,slots 只是個平常的 C++ method,不過被偽裝成 "slots" 罷了 [譯注:後面的段落會說明這句話]。而,相對來說,signal 就是 Qt 內部所實作出來的,只會在程式宣告 (declaration) 與 connect() 呼叫中出現。

咱們看看 class ExpensesMainWindow (在 ExpensesMainWindow.h) 的程式宣告:

private slots: void slotQuit(); void slotNewExpense(); void slotDeleteExpense();

虛擬關鍵字 slots 指出這些 methods (當然還有其他的) 是被宣告成 slots。當然,你不必對 C++ compile 進行修補就可以編譯 Qt 程式了 —— 虛擬關鍵字 slots 會透過 C 箝制處理器 (preprocessor) 自動轉換。然而,你必須呼叫一個由 Qt 提供的程式,"Meta Object Compiler" (moc),來解析你的 class 宣告。moc 匯出必要的資訊並把這些寫入到一個 C 原始程式檔,而你必須編譯並連結剛剛產生的程式碼到你的程式中。配合適當的工具,你甚至不必理會這項 [譯注:像是後面會提到的 qmake 就會幫你處理以上的細節]。

現在,讓我們看看 class ExpenseDialog 的 constructor 中的程式碼片段:(在ExpenseDialog.cpp,列表一)

 QPushButton* cancelPB = new QPushButton( "&Cancel", this ); connect( cancelPB, SIGNAL( clicked() ), this, SLOT( accept() ) );

在這段程式碼片段中,我們連結 (connect) class QPushButton 中的 clicked() signal 到 class ExpenseDialog 的 accept() slot。實際上,我們並沒有定義 accept() 這個 slot,而是因為繼承自其 base class,QDialog —— 另一項證明說 slots 就跟一般的 method 沒有兩樣,可以修飾以 private、protected、public、virtual 等 modifier,甚至覆寫 (reimplemented)。class QDialog 的 accept() slot 只是簡單的關閉一個典型對話框並傳回一個已關閉對話框的傳回碼 (return code) (請見 ExpensesMainWindow.cpp 的 ExpensesMainWindow::slotNewExpense() 與ExpensesMainWindow::slotEditExpense() 這兩個 method)。

class ExpenseDialog 的例子還提供其他兩個 Qt 程式設計的技巧。如果你檢閱原始程式碼,你會發現我們在 Heap 中 [譯注:C++ compiler 會設法配置一塊記憶體區域來安置所產生的物件,這塊區域就稱為 Heap] 建立許多 "widgets" (Qt 用以稱呼使用者介面元件的項目,就好比 Java 的 AWT 或 Windows 系統的 "controls"),但甚至連用來解構 (destruct) 的 destructor 都沒有。我們這樣的行徑會造成大量 memory leak [譯注:memory leak 是只因為沒有經過正常程序的釋放記憶體,多餘的空間 (就是之前配置的) 造常記憶體使用上不連續甚至不足的現象] 嗎?對記憶體錙銖必較的嵌入式系統來說,這樣的揮霍怎麼可以呢?

當然不!Qt 會自動維護所有具有繼承關係 (a parent-child hierarchy) 的 widgets (實際上,只要直接或間接繼承 QObject 的物件都是如此):一旦 widget 建立後,會向其 parent (所繼承的 class) 註冊,而這個 parent 會在其被釋放 (deleted) 時自動的刪除其 child [譯注:就是前述的 widget,如此的機制是透過 C++ virtual destructor 來實現的]。只要ExpenseDialog 物件被釋放,所有在此對話框裡頭的 widget 也將跟著被釋放,也因為 ExpenseDialog 物件在「堆疊」 (stack,[譯注:請讀者不要弄錯這邊 "stack" 的意思,原作者所指的 stack,應該是描述 ExpenseDialog 是建構在 QDialog 之上,而 QDialog 又是建構在 QWidget 之上這樣的關係,以資料結構的角度來說,就是 "stack"]) 中建立,所以一旦不在 scope 中,物件就會被釋放其佔有的記憶體空間。以上自動釋放記憶體的技巧就是你為何應該而最好建立在這「堆疊」之上的 widget —— 當底層 parent 被釋放時,這些 widgets 也會跟著被釋放。不過,這不適用於最底層 (top-level) 的 widgets,如對話框 [譯注:QDialog] 或主視窗 [譯注:main application window,對應 QMainWindow],因為這些 widget 沒有 parent,也因此無法自動釋放。

這樣自動釋放記憶體的機制,使得程式設計上有點類似 Java AWT 或 Swing:Widgets 一旦建立後,就無須顧慮釋放的問題,因為系統會自動處理。這也是為何 Qt GUI 程式設計中不常見到 destructor。Qt 的方式比 Java 所採用的 garbage collection 更為有效 —— child widgets 只會在需要的時候存在,而若採用 garbage collection,就會一直保留在下一次執行 garbage collection thread 時 [譯注:在 Java VM 的設計中,Garbage Collector 也是個在 VM 中的 thread,但是其 priority 是最低的 (-1),直到沒有其他 thread 佔據 CPU 資源時才會有機會執行]。

最後,ExpenseDialog 的 costructor 引用了版面配置 (layout management [譯注:也時也稱為 "Geometry Management]),這是另一項常見的 Qt 技巧。對 Java 的開發者來說,版面配置是常見的程式設計方式,但只寫過 Windows 應用程式的開發者或許會感到陌生。透過版面配置,你就不需要特別去指明 widget 的絕對位置,而是相較於其他 widgets 該出現的相對位置 [譯注:Layout Manager 會自動調整相關 widget 的位置與大小]。舉例來說,在 constructor 中,我們有個垂直導向 (vertically oriented) 的 layout (class QVBoxLayout 中的 toplevelVBL) 用來管理其他兩個 layout,一個是 grid 導向的 ( class QGridLayout 中的 dataGL),而另一個是水平導向 (class QHBoxLayout 中的 buttonsHBL)。These in turn manage the input fields with their labels and the two buttons at the bottom. [譯注:這句我保留原文,因為翻譯出來有點彆扭,如果您看了程式的 screenshot 應該可以知道原作者的描述。像這樣包含若干個 Layout Manager 的方式就稱為巢狀 (Nested) Layout Management]

版面配置不少優點:如果翻譯成其他語文,對話框不需要重新設計,他們在使用者手動調整時依然顯示良好;and finally it is even easier to use after you have gotten the hang of it.[譯注:這一句我實在沒什麼感覺,保留原文]

當然,我們可以透過圖形化的 Qt Designer 工具來協助建立上述的對話框,但是手動建構這些程式碼總是好事。

如果你使用 QPE,你可以稍加修改程式碼,以便能夠完美的與 QPE 整合:在 main.cpp (列表二) 中,將 QApplication 取代成 QPEApplication 並且連結到 libqpe.so。

QMake

Qt 這個高度可攜性的 toolkit 運作相當好,但當實際進行編譯時,總會有點問題。因此,QMake (已包含於 Qt 3) 設計來把專案描述檔 (.pro) 轉換成切合開發平台的 Makefile。QMake 支援相當多不同的 UNIX 版本與編譯器,像是 Windows、MacOS X,應用在嵌入式系統的 Linux 等等。列表三 是個典型的 .pro 檔。如果 QMake 設定無誤,那麼執行 qmake -o Makefile expenses.pro 就會建立適合你所用平台的 Makefile。QMake 撰寫的語法和編譯器名稱、編譯器選項、共享程式庫 (shared libraries) 建構方式、... 等都無關,qmake 甚至可以自動呼叫 moc 來減輕程式設計者的負擔。透過 expenses.pro 檔的撰寫、qmake,以及 Qt 3,你就可以在任何 Qt 所支援的平台上建構你的應用程式 —— 而不僅只是 Qt/Embedded [譯注:這樣的說法有點像是廣告說詞,事實上這只適用在不使用與平台相依 (platform-dependent) 功能的情況,而,如前面所說,Qt 與 Qt/Embedded 也有一定程度的不相容,不過基本 API 與概念都是一致的]。只要少許修改,甚至可以在 Qr 2.x 系列下建構 —— 你只要把 QDateEdit 這個在 Qt 2.x 中沒納入的 widget 替換掉 (three spin boxes come to mind),並且,因為 Qt 2.x 系列沒有包含 qmake,你必須改用 qmake 的前身 tmake,可在這邊取得:ftp://ftp.trolltech.com/pub/freebies/tmake/。列表三 不僅適用於 qmake,也可用於 tmake。

結論

當在手持式電腦上進行開發時,有幾點限制必須謹記在心:第一、既然 iPAQ 螢幕顯示是 240x320 pixel,我們要把應用程式視窗調整成這個大小,對輸入新的財務記錄的對話框也要保以此限制。為了讓中央 listview 元件的欄位至少符合有效的寬度,我們必須採用他們標題的縮寫字 (abbreviation),而這也可能造成問題,如果使用的縮寫字不能被使用者所理解。A toolbar usually does not provide anything that the menu does not provide itself, and given that a toolbar takes at least 20 pixels in a vertical direction (1/16th of the total available vertical space!), this decision was not hard to take.

Finally, we decided not to use the spreadsheet-like QTable class for displaying the entered expenses even though it would have had some advantages. However, QListView is more lightweight, both in run-time memory and in the code it takes in the Qt library, so we decided to go with that.

DDJ

列表一

#include "ExpenseDialog.h" #include "Expense.h" #include <qcombobox.h> #include <qdatetimeedit.h> #include <qlabel.h> #include <qlayout.h> #include <qlineedit.h> #include <qpushbutton.h> #include <qvalidator.h> ExpenseDialog::ExpenseDialog( const QStringList& categories, const QStringList& currencies, QWidget* parent, const char* name ) : QDialog( parent, name, true ) { // The top-level layout manager QVBoxLayout* toplevelVBL = new QVBoxLayout( this, 10 ); // Create the widgets for data entry QGridLayout* dataGL = new QGridLayout( 5, 2, 10 ); toplevelVBL->addLayout( dataGL ); QLabel* dateLA = new QLabel( "Date:", this ); dataGL->addWidget( dateLA, 0, 0 ); _dateDE = new QDateEdit( this ); _dateDE->setDate( QDate::currentDate() ); dataGL->addWidget( _dateDE, 0, 1 ); QLabel* descriptionLA = new QLabel( "Description:", this ); dataGL->addWidget( descriptionLA, 1, 0 ); _descriptionED = new QLineEdit( this ); dataGL->addWidget( _descriptionED, 1, 1 ); QLabel* categoryLA = new QLabel( "Category:", this ); dataGL->addWidget( categoryLA, 2, 0 ); _categoryCO = new QComboBox( this ); _categoryCO->insertStringList( categories ); dataGL->addWidget( _categoryCO, 2, 1 ); QLabel* currencyLA = new QLabel( "Currency:", this ); dataGL->addWidget( currencyLA, 3, 0 ); _currencyCO = new QComboBox( this ); _currencyCO->insertStringList( currencies ); dataGL->addWidget( _currencyCO, 3, 1 ); QLabel* amountLA = new QLabel( "Amount:", this ); dataGL->addWidget( amountLA, 4, 0 ); _amountED = new QLineEdit( this ); _amountED->setValidator( new QDoubleValidator( _amountED ) ); dataGL->addWidget( _amountED, 4, 1 ); // Create the OK and Cancel buttons QHBoxLayout* buttonsHBL = new QHBoxLayout( 10 ); toplevelVBL->addLayout( buttonsHBL ); QPushButton* okPB = new QPushButton( "&OK", this ); okPB->setDefault( true ); connect( okPB, SIGNAL( clicked() ), this, SLOT( accept() ) ); buttonsHBL->addWidget( okPB ); QPushButton* cancelPB = new QPushButton( "&Cancel", this ); connect( cancelPB, SIGNAL( clicked() ), this, SLOT( accept() ) ); buttonsHBL->addWidget( cancelPB ); // restrict even this dialog to 240x320 setMaximumSize( 240, 320 ); } void ExpenseDialog::setExpenseValues( Expense* expense ) { // preset the widgets with the values from the Expense object _dateDE->setDate( expense->date() ); _descriptionED->setText( expense->description() ); for( int i = 0; i < _categoryCO->count(); i ) if( _categoryCO->text( i ) == expense->category() ) { _categoryCO->setCurrentItem( i ); break; } for( int i = 0; i < _currencyCO->count(); i ) if( _currencyCO->text( i ) == expense->currency() ) { _currencyCO->setCurrentItem( i ); break; } _amountED->setText( QString::number( expense->amount(), 'f', 2 ) ); }  QDate ExpenseDialog::date() const { return _dateDE->date(); } QString ExpenseDialog::description() const { return _descriptionED->text(); } QString ExpenseDialog::category() const { if ( _categoryCO->count() > 0 ) return _categoryCO->currentText(); else return QString::null; } QString ExpenseDialog::currency() const { if ( _currencyCO->count() > 0 ) return _currencyCO->currentText(); else return QString::null; } double ExpenseDialog::amount() const { double value = 0.0; bool ok; value = _amountED->text().toDouble( &ok ); if ( ok ) return value; else return 0.0; } 

列表二

#include <qapplication.h> #include "ExpensesMainWindow.h" int main( int argc, char* argv[] ) { QApplication app( argc, argv ); ExpensesMainWindow mainWindow; mainWindow.show(); app.setMainWidget( &mainWindow ); return app.exec(); } 

列表三

TEMPLATE = app TARGET = expenses SOURCES = ExpensesMainWindow.cpp main.cpp Expense.cpp ExpenseDialog.cpp HEADERS = ExpensesMainWindow.h Expense.h ExpenseDialog.h 
相关阅读 更多 +
排行榜 更多 +
盛大娱乐手机版安卓下载

盛大娱乐手机版安卓下载

棋牌卡牌 下载
苏打音乐app下载安装免费

苏打音乐app下载安装免费

趣味娱乐 下载
弓箭手战士酷跑

弓箭手战士酷跑

飞行射击 下载