wx網羅系列之翔實:將 MFC 應用程序移植到 Linux

 此文將MFC與wxWidgets做了方方面面的比照,尤其是其類層次結構和一些關鍵性的宏,爲MFC程序員提供了一個向wxWidgets移植的入門指南。排版整理完畢。

您可能仍然在維護用微軟基礎類庫(Microsoft Foundation Classes(MFC))構建的舊的 Windows 應用程序,而現在卻有客戶要求 Linux 版本,該怎麼辦呢?在您的團隊中可能有技術熟練的 MFC 開發人員,但如何達到加速 Linux 開發呢?別急;本文就是針對您這種情況而寫的。依靠 wxWindows(一種用於 C++ 和 Python 的可移植 GUI 工具箱)的幫助,我將以多文檔界面(Multiple Document Interface (MDI))文本編輯器爲例向您演示如何將僅 Windows 的 MFC 應用程序移植到 Linux。類似這樣的小型應用程序有助於我們將討論集中在移植框架的具體細節上,從而避免我們迷失在代碼的汪洋中。可以在本文後面的 參考資料一節中獲取完整的 MFC 應用程序和 wxWindows 應用程序的源代碼。

文檔/視圖概述

我將演示的應用程序使用衆所周知的文檔/視圖體系結構,因爲它可以象大多數應用程序一樣處理文檔。即使您的應用程序不使用文檔/視圖體系結構,我也建議您讀下去。只要您已在轉向這種框架,您就可能想要添加這項功能。

在我的《細述wxWindows》中,曾經指出過 MFC 和 wxWindows 之間具有某些相似性。字符串類 CStringwxString 和事件系統之間都非常相似。但還不止這些相似性。wxWindows 工具箱還提供對文檔/視圖體系結構的類 MFC 支持。

我將從核心類的比較開始。下表列出了兩種框架的文檔/視圖體系結構所涉及的類。

表 1. 文檔/視圖類比較

MFC 類 wxWindows 類
文檔(Document) CDocument wxDocument
視圖(View) CView wxView
編輯視圖(Edit view) CEditView n/a
模板類(Template class) CMultiDocTemplate wxDocTemplate
MDI 父框架(MDI parent frame) CMDIFrameWnd wxDocMDIParentFrame
MDI 子框架(MDI child frame) CMDIChildWnd wxDocMDIChildFrame
文檔管理器(Document manager) n/a wxDocManager

除編輯視圖類以外,每個 MFC 類都有其對應的 wxWindows 類。(最後一項中 MFC 的部分爲空,因爲 MFC 沒有獨立的文檔管理器類。由應用程序類 CWinApp 內部處理文檔。)下列 UML 圖演示了這些類之間的關係:



圖 1. MFC 類
MFC 類


圖 2. wxWindows 類
wxWindows 類 

應用程序

每個框架都提供一個表示應用程序本身的類。MFC 應用程序類聲明瞭一個構造器、一個用於初始化的方法、一個用於事件處理的方法和一個消息映射表。您需要這個消息映射表聲明和事件處理方法,因爲應用程序的“about”對話框將由該類處理。



應用程序類:MFC
class CPortMeApp : public CWinApp
{
public:
CPortMeApp();
virtual BOOL InitInstance();
afx_msg void OnAppAbout();
DECLARE_MESSAGE_MAP()
};

注:最初創建 MFC 應用程序時,我使用 Microsoft Visual Studio 所包含的應用程序嚮導來創建,但在我的代碼片段中,我將不給出由嚮導生成的、有時會使人迷惑的註釋( //{{AFX_MSG 及類似的東西)。完整的源代碼,請參閱 ZIP 壓縮文檔。

對應的 wxWindows 類看起來略微有些不同。它也聲明瞭一個構造器及一個用於初始化的方法,但卻不需要任何東西來處理消息。如同您隨後將看到的一樣,在主框架類中處理“about”對話框。



應用程序類:wxWindows
class PortedApp : public wxApp
{
public:
PortedApp();
bool OnInit();
int OnExit();
protected:
wxDocManager* m_docManager;
};

正如下面所描述的那樣,這個類需要一個 wxDocManager 屬性來處理在初始化方法 OnInit() 中創建的模板。應用程序退出時,清理方法 OnExit() 將刪除這個 wxDocManager 對象。

所有應用程序都需要其入口點(也稱爲 main()WinMain() )。在實現這一點的方法上,兩種框架略微有些不同。在 MFC 中,象這樣創建應用程序類的靜態對象:

CPortMeApp theApp;

在 wxWindows 中,則象這樣使用 IMPLEMENT_APP() 宏:

IMPLEMENT_APP(PortedApp)

如果對該宏所做的事情感興趣,請查看頭文件 wx/app.h 中它的定義,在可下載的源代碼中找到該頭文件。基本上,它是爲所使用的平臺插入適當的入口點函數。創建了應用程序類的對象之後,需要對其進行初始化。對於 MFC,Microsoft 建議不使用應用程序對象的構造器來初始化對象。而是應該使用其 InitInstance() 方法。要執行任何清理,請實現 ExitInstance() 方法。

雖然應用程序初始化有很多事情要做,這裏我將只着重討論與文檔/視圖有關的代碼。要建立文檔/視圖框架, InitInstance() 方法必須創建一個 CMultiDocTemplate ,如下所示:



創建文檔/視圖代碼:MFC
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(IDR_PORTMETYPE,
RUNTIME_CLASS(CPortMeDoc),
RUNTIME_CLASS(CChildFrame),
RUNTIME_CLASS(CPortMeView));

wxWindows 應用程序提供 OnInit() 方法來做任何初始化工作,並提供 OnExit() 用於清理。要在 wxWindows 應用程序中建立文檔/視圖框架, OnInit() 方法必須象這樣創建 wxDocTemplate



創建文檔/視圖代碼:wxWindows
m_docManager = new wxDocManager();
wxDocTemplate* pDocTemplate;
pDocTemplate = new wxDocTemplate(m_docManager, "Pom", "*.pom", "", "pom", "Pom Doc", "Text View",
CLASSINFO(PortedDoc),CLASSINFO(PortedView));

MFC 和 wxWindows 所做的事情基本上相同。框架需要關於哪個文檔同哪個視圖有關以及這個組合處理哪種文檔的信息。類型包括文檔的描述性名稱以及這類文檔的文件擴展名。兩種框架都使用模板來處理這一問題(請注意這與標準 C++ 模板沒有什麼關係)。

MFC CMultiDocTemplate 也保存關於同文檔相關聯的子框架的信息,並且該模板被添加到管理該模板的應用程序對象中。wxWindows wxDocTemplate 額外需要一個管理模板的 wxDocManager 對象。請記住, wxDocManager 是 wxWindows 應用程序的應用程序類的一個屬性。

完成文檔/視圖框架初始化之後,就創建了應用程序的主框架。在 MFC 應用程序的 InitInstance() 方法中,按如下創建一個主框架:



創建主框架:MFC
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();

調用 pMainFrame->LoadFrame(IDR_MAINFRAME) 從資源文件裝入所有關於主框架的信息。

在 wxWindows 中,可以從資源文件建立菜單、對話框以及控件,或者可以在代碼中創建它們。我更喜歡在代碼中創建它們,但如果您願意將代碼同資源分離,就應該看一看 wxWindows 資源系統(請參閱 wxWindows 文檔中的主題概述)。



創建主框架:wxWindows
m_mainFrame = new MainFrame(m_docManager, (wxFrame*) NULL, "DocView Demo",
wxPoint(0, 0), wxSize(500, 400),
wxDEFAULT_FRAME_STYLE);
// Set up menu bar...
m_mainFrame->SetMenuBar(menu_bar);
m_mainFrame->Centre(wxBOTH);
m_mainFrame->Show(TRUE);
SetTopWindow(m_mainFrame);

在查看應用程序初始化完成後發生了什麼事情之前,讓我向您演示每個框架的文檔和視圖類。


文檔

文檔保存應用程序處理的基於文件的數據。它負責從文件裝入該數據,並在必要的時候將該數據保存迴文件。該 MFC 類的聲明類似於這樣:



文檔類聲明:MFC
class CPortMeDoc : public CDocument
{
protected:
CPortMeDoc();
DECLARE_DYNCREATE(CPortMeDoc)
public:
virtual BOOL OnNewDocument();
virtual void Serialize(CArchive& ar);
virtual ~CPortMeDoc();
DECLARE_MESSAGE_MAP()};

請注意:該類有一個受保護(protected)的構造器。決不要直接創建該類的任何對象;相反,框架使用序列化來創建文檔。因此,需要使用 DECLARE_DYNCREATE() 宏。wxWindows 類的聲明類似於這樣:



文檔類聲明:wxWindows
class PortedDoc : public wxDocument
{
public:
virtual bool OnSaveDocument(const wxString& filename);
virtual bool OnOpenDocument(const wxString& filename);
virtual bool IsModified() const;
virtual void Modify(bool mod);
private:
DECLARE_DYNAMIC_CLASS(PortedDoc)};

DECLARE_DYNAMIC_CLASS() 宏是必需的,因爲 wxWindows 就象 MFC 一樣動態地創建該類的對象。


視圖

如果 MFC 應用程序處理文本文檔,那麼從 CEditView 派生視圖類是一個好主意。該類已經在其客戶機窗口內提供了基本的編輯功能和文本控件。這裏我遵循自己的建議,MFC 視圖類的聲明類似於這樣:



視圖類聲明:MFC
class CPortMeView : public CEditView
{
protected:
CPortMeView();
DECLARE_DYNCREATE(CPortMeView)
public:
CPortMeDoc* GetDocument();
virtual void OnDraw(CDC* pDC);
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
virtual ~CPortMeView();
DECLARE_MESSAGE_MAP()};

在 wxWindows 中,(還)沒有專門的編輯視圖。但是創建您自己的編輯視圖卻很容易。只需在視圖框架內有一個由 wxTextCtrl 派生的文本控件。視圖類將控件和框架都當作類屬性來處理。因此 wxWindows 聲明類似於這樣:



視圖類聲明:wxWindows
class PortedView : public wxView
{
public:
MyTextCtrl* textsw;
PortedView();
bool OnCreate(wxDocument* doc, long flags);
void OnDraw(wxDC* dc);
bool OnClose(bool deleteWindow = TRUE);
private:
wxMDIChildFrame* CreateChildFrame(wxDocument* doc, wxView* view);
wxFrame* frame;
DECLARE_DYNAMIC_CLASS(PortedView)
};

除了常見的用於窗口創建、窗口重畫以及窗口關閉的事件處理程序之外,該類還有一個創建框架並填充其菜單欄的方法。

雖然擁有公共(public)屬性不是一個好主意,但是爲簡單起見,我在這裏還是這麼做。在您的應用程序中應該避免這麼做(我將在以後的版本中除去它)。


主框架

既然已經有了處理和顯示數據的文檔和視圖類,並且也有了處理文檔/視圖框架的應用程序類,現在需要一個主框架類來同用戶交互。同樣,兩種框架提供類似的功能,而實際實現有略微不同。MFC 主框架類將一個狀態欄和一個工具欄作爲屬性保存,同時 MFC 主框架類還提供一些方法來處理窗口創建,以及聲明消息映射表。請注意:該類是從 CMDIFrameWnd 派生而來的,因爲該應用程序有一個 MDI 界面。



主框架類:MFC
class CMainFrame : public CMDIFrameWnd
{
public:
CMainFrame();
virtual ~CMainFrame();
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
CStatusBar m_wndStatusBar;
CToolBar m_wndToolBar;
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
DECLARE_MESSAGE_MAP()
private:
DECLARE_DYNAMIC(CMainFrame)
};

wxWindows 主框架類將菜單作爲屬性保存,還具有創建其工具欄的方法,以及聲明一個事件表外加一個處理應用程序的 about 對話框的方法。因爲這個應用程序有一個 MDI 界面,所以該類是從 wxDocMDIParentFrame 派生而來。



主框架類:wxWindows
class MainFrame : public wxDocMDIParentFrame
{
public:
wxMenu* editMenu;
MainFrame(wxDocManager* manager, wxFrame* frame, const wxString& title,
const wxPoint& pos, const wxSize& size, long type);
void OnAbout(wxCommandEvent& event);
void RecreateToolbar();
private:
DECLARE_CLASS(MainFrame);
DECLARE_EVENT_TABLE();
};


全部工作原理

移植完成了。我來回顧一下實現這一點所需做的事情。MFC 應用程序類(派生自 CWinApp )移植到派生自 wxApp 的應用程序類,包括初始化代碼。將 MFC 文檔類(派生自 CDocument )移植到派生自 wxDocument 的文檔類。將 MFC 視圖類(派生自 CView )移植到派生自 wxView 的文檔類。除了這些同文檔/視圖有關的類之外,將 MFC 主框架類(派生自 CMDIFrameWnd )移植到派生自 wxDocMDIParentFrame 的類。

在其目前的版本中,該應用程序已經能夠做很多工作。可以處理多個文本文件。可以打開、編輯並保存這些文件,並且應用程序保持最近打開文件的歷史記錄。如果不得不從頭編寫這些功能,那麼將需要更多行代碼 ― 更多行代碼意味着要測試和維護更多行。在兩種框架中,都使用了專門的命令標識符來處理常用的同文檔/視圖有關的命令。常用的文件命令是新建、打開和保存。常用的編輯命令是剪切、複製和粘貼。其它經常使用的命令有打印命令(這個應用程序中還沒有實現該命令)和幫助命令。下表列出了這兩種框架結構的文檔/視圖體系結構所包含的命令標識符。

表 2. 標準的命令標識符

MFC wxWindows
ID_FILE_OPEN wxID_OPEN
ID_FILE_CLOSE wxID_CLOSE
ID_FILE_NEW wxID_NEW
ID_FILE_SAVE wxID_SAVE
ID_FILE_SAVE_AS wxID_SAVEAS
ID_EDIT_CUT wxID_CUT
ID_EDIT_COPY wxID_COPY
ID_EDIT_PASTE wxID_PASTE
ID_APP_EXIT wxID_EXIT
ID_EDIT_UNDO wxID_UNDO
ID_EDIT_REDO wxID_REDO
ID_HELP_INDEX wxID_HELP
ID_FILE_PRINT wxID_PRINT
ID_FILE_PRINT_SETUP wxID_PRINT_SETUP
ID_FILE_PRINT_PREVIEW wxID_PREVIEW

窗口管理器

如果您對 Linux 開發真的很感興趣,則可能已經做了一些研究,發現 Linux 上有不同的窗口管理器。曾經是 MFC 開發人員的您可能覺得這很奇怪。在進行 Windows 開發時,您無須操心不同的窗口管理器,因爲只有一個窗口管理器。

開始 wxWindows 開發時,您可能想知道您的應用程序是否將運行在兩個主流的窗口管理器 ― K 桌面環境(K Desktop Environment (KDE))和 GNOME ― 之上。我已經在 Microsoft Windows NT 4.0、使用公共桌面環境(Common Desktop Environment (CDE))的 Sun Solaris 2.6 以及使用 KDE 的 Linux 2.2 上使用了 wxWindows 工具箱,沒有任何問題。由於 wxWindows 僅僅是一個建立在其它低級別 GUI 工具箱上的高級別層次,所以可以選擇如何構建 Linux 應用程序。可以使用 Motif 版本(或者還要更好一些的,免費 Lesstif)或者 wxWindows 的 GTK+ 版本。GTK+ 是 GNOME 使用的基礎窗口構件工具箱,但是它在 KDE 下也運行得非常好。我建議您使用 GTK+ 版本,因爲它通常更新,有許多開發人員對它進行開發,而且有用戶在使用它。因此,如果您希望獲得更多幫助,則可以求助於郵件列表或新聞組,詢問關於 GTK+ 版本的問題。

下面是前前後後的抓屏,可以讓您對移植的應用程序有一個大致的認識。



圖 3. 原始的 MFC 應用程序
原始的 MFC 應用程序


圖 4. Windows 上的 wxWindows 應用程序
Windows 上的 wxWindows 應用程序


圖 5. Linux/KDE 上的 wxWindows 應用程序
Linux/KDE 上的 wxWindows 應用程序 

真實的故事

wxWindows 工具箱不只是書呆子的另一個玩具。它成熟、穩定,並且人們在實際應用程序中用它來解決實際問題。

wxWindows 項目創始人 Julian Smart 目前致力於 Red Hat,他已經將 eCos 配置工具(eCos Configuration Tool)移植到了 wxWindows。eCos 配置工具是一個配置 eCos 嵌入式操作系統的圖形工具。人們已將最初的 MFC 應用程序移植到了 wxWindows,尤其是在 Linux 上(參閱 參考資料)。 SciTech Software 的人員也已經使用 wxWindows 來全部重新開發用於 SciTech Display Doctor 產品的前端 GUI。IBM 已經獲得了 SciTech Display Doctor 的一個專門版本的許可證,IBM 現在將其作爲 IBM 所有基於 OS/2 Warp 的操作系統(包括 Warp Client、Workspace On Demand 及 Warp Server for e-business)的主要顯示驅動程序來分發。SciTech Software 對 wxWindows 社區做出了積極的貢獻,它提交了擴展包 wxApplet、wxUniversal 和 wxMGL。使用 wxMGL,wxWindows 應用程序甚至可以運行在 DOS 和其它嵌入式操作系統之上,例如 QNX、RT-Target、SMX 等等。SciTech Software 全力資助 wxUniversal 和 wxMGL 項目(參閱 參考資料)。


結束語

本文演示了使用 wxWindows 工具箱將 MFC 文檔/視圖框架的 Windows 應用程序移植到 Linux 的基本原理。wxWindows 工具箱同 MFC 框架有一些相似性,從而幫助 MFC 開發人員可以達到加速 Linux 開發。有了這些基礎知識,您應該能夠將最棒的應用程序移植到 Linux。但不要忘了:wxWindows 不是隻能用於 Linux。也許該是考慮在其它 UNIX 或者甚至 Macintosh 上來運行您的應用程序一個版本的時候了。



參考資料

  • 您可以參閱本文在 developerWorks 全球站點上的 英文原文.

  • 從 Markus 的主頁下載本文所演示的原始應用程序和移植的應用程序的 完整源代碼


  • 有關 wxWindows 的概述,請閱讀 Markus 的文章 細述 wxWindowsdeveloperWorks,2001 年 2 月)。


  • wxWindows 主頁是 wxWindows 社區成員要去的主要場所。它提供關於 wxWindows 的信息及對它的支持,包括下載、郵件列表、樣本應用程序以及與 wxWindows 有關的工具。


  • wxPython 將 Python 腳本語言同 wxWindows GUI 庫結合起來。有關 wxPython 的介紹,請閱讀 wxPython for newbiesdeveloperWorks,2001 年 3 月)。


  • Coding with KParts討論了 K 桌面環境(K Desktop Environment)圖形組件的 KParts 體系結構( developerWorks,2002 年 2 月)。


  • 有關更多關於 K 桌面環境的內容,請訪問 KDE 主頁


  • 有關更多關於 GNOME 的信息,請查看 GNOME 主頁


  • 在 Red Hat 站點上可以找到更多關於 Red Hat eCos 配置工具的內容。


  • 可以在 SciTech Software 站點上獲取更多關於 SciTech Display Doctor的信息。


  • 可以在 OS/2 Warp 頁面上找到更多關於 OS/2 設備驅動程序包(它使用 SciTech Display Doctor)的信息。


  • developerWorksLinux 專區裏可以找到 更多 Linux 文章


關於作者

 

Markus Neifer 最初使用 LOGO 教學語言來編程,後來他使用過多種 BASIC 語言。在研究地理信息學期間,他學了一些 C,但很快又轉向了 C++ 和 Java,因爲這兩種語言具有面向對象的本質。他曾在研發領域工作過,期間他出版過關於科技軟件的面向對象開發的文章。目前,他是地理信息系統領域的軟件工程師。可以通過 [email protected]和 Markus 聯繫。

發佈了37 篇原創文章 · 獲贊 2 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章