Microsoft Foundation Classes (MFC) を使って作成された従来のWindowsアプリケーションを維持・管理している一方で、クライアントからはLinuxバージョンがほしいと要求されている方、みなさんのチームには非常に熟練したMFCプログラマーの方もおられるでしょうが、Linuxの開発スピードに付いていくにはどうすればよいのでしょうか。慌てる必要はありません。本稿は、そういうみなさんのためのものです。C++、Python向けのポータブルなGUIツールキットであるwxWindowsを利用して、Windows専用のMFCアプリケーションをLinuxに移植する方法を紹介したいと思います。例として取り上げるのは、マルチプル・ドキュメント・インターフェース (MDI) のテキスト・エディターです。この程度の小さなアプリケーションにすれば、フレームワークを移植するための基礎に焦点を絞りやすくなりますし、あふれるようなコードに迷子になることもありません。MFCアプリケーションとwxWindowsアプリケーションの両方の完全なソース・コードは、本稿末の 参考文献 のコーナーから入手できます。
本稿で説明するアプリケーションは、皆さんに馴染み深いドキュメント / ビュー・アーキテクチャーを採用しています。それは、ドキュメントの取り扱いが、他のアプリケーションと共通しているからです。みなさんのアプリケーションがドキュメント / ビュー・アーキテクチャーを使用しないものであっても、一読の価値はあると思います。フレームワークの切り替えにすでに着手されているのならば、この機能の追加に興味を持たれるかもしれません。
wxWindowsに関する
以前の記事
で、筆者は、MFCとwxWindowsにいくつか類似点のあることを指摘しました。文字列クラスの
CString
と
wxString
やイベント・システムは、お互い非常に類似しています。しかし、類似点はこれだけに留まりません。wxWindowsツールキットは、ドキュメント
/ ビュー・アーキテクチャーについても、MFCと同様の機能をサポートしています。
まずは、コア・クラスの比較から始めたいと思います。以下の表は、双方のフレームワークのドキュメント / ビュー・アーキテクチャーに関係するクラスを一覧にしたものです。
表1. ドキュメント / ビュー・クラスの比較
| クラス | MFCのクラス | wxWindowsのクラス |
| ドキュメント | CDocument | wxDocument |
| ビュー | CView | wxView |
| エディット・ビュー | CEditView | なし |
| テンプレート・クラス | CMultiDocTemplate | wxDocTemplate |
| MDIの親フレーム | CMDIFrameWnd | wxDocMDIParentFrame |
| MDIの子フレーム | CMDIChildWnd | wxDocMDIChildFrame |
| ドキュメント・マネージャー | なし | wxDocManager |
エディット・ビュー・クラスを除けば、MFCのクラスに対応するものがwxWindowsにもあります。(最後の項目はMFCでは「なし」になっていますが、これは、MFCが、独立したドキュメント・マネージャー・クラスを用意していないためです。ドキュメントは、アプリケーション・クラス
CWinApp
によって内部的に処理されるようになっています。)以下のUMLダイアグラムは、クラス間の関係を表したものです。
図1. MFCのクラス間の関係
図2. 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;
};
|
このクラスでは、以下で説明しますが、
OnInit()
という初期化メソッドで作成されるテンプレートを処理するために、
wxDocManager
という属性が必要となっています。この
wxDocManager
オブジェクトは、アプリケーションが終了する際に、クリーンアップ・メソッド
OnExit()
によって削除されます。
どんなアプリケーションでも、エントリー・ポイント (
main()
とか
WinMain()
として知られているもの)
が必要です。この点に関して、2つのフレームワークは少し違った手法を採用しています。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にするのは上手なやり方ではありませんが、ここでは簡単のために、そのようにしています。みなさんのアプリケーションでは、これを真似しないでください (筆者も、先々はpublicを外すつもりです)。
以上、データを処理するためのドキュメント・クラスとデータを表示するためのビュー・クラスを用意し、さらにドキュメント
/
ビュー・フレームワークを扱うためのアプリケーション・クラスも用意できましたので、次は、ユーザーとのやりとりを行うためのメイン・フレーム・クラスを準備します。これについても、具体的な実現方法は少し違っているにしろ、両方のフレームワークが同様の機能をサポートしています。MFCのメイン・フレーム・クラスは、ステータス・バーとツールバーを属性として保有し、ウィンドウの生成を処理するためのメソッドを用意し、メッセージ・マップを宣言しています。今回はMDIインターフェースを備えたアプリケーションですので、このメイン・フレーム・クラスは、
CMDIFrameWnd
から派生させていることに注意してください。
メイン・フレーム・クラス: 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
から派生させたクラスに移植しました。
ここまでの時点でも、このアプリケーションはかなりのことをします。たとえば、複数のテキスト・ファイルを扱うことができます。それらのファイルをオープン、編集、保存することができますし、アプリケーションは、最近オープンしたファイルの履歴を保持します。これらの機能を一からコーディングするとすれば、もっとたくさんの行のコードを記述しなければなりません。そのことは、もっとたくさんの行をテスト、保守しなければならないということでもあります。どちらのフレームワークでも、一般的なドキュメント/ ビュー関係のコマンドを処理するために、専用のコマンドIDを使用します。たとえば、ファイル関係の一般的なコマンドには、新規作成、オープン、保存があります。一般的な編集コマンドには、切り取り、コピー、貼り付けがあります。その他、よく使用されるコマンドには、このアプリケーションでは実装されていませんが印刷コマンドがありますし、ヘルプ・コマンドもそうです。下表は、両方のフレームワークのドキュメント/ ビュー・アーキテクチャー関連コマンドのID一覧です。
表2. 標準的なコマンドID
| 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用の開発を行う場合、ウィンドウ・マネージャーは1つしか存在しませんので、ウィンドウ・マネージャーの種類に気を配る必要はありません。
wxWindowsで開発を始める場合、ウィンドウ・マネージャーの双璧であるK Desktop Environment (KDE) とGNOMEの両方でアプリケーションを実行できるかどうかが気になるかもしれません。筆者は、wxWindowsツールキットを次の環境で使用しましたが、何も問題はありませんでした。Microsoft Windows NT 4.0、Sun Solaris 2.6 (Common Desktop Environment (CDE) 使用) 、およびLinux 2.2 (KDE使用)。wxWindowsは、他の低位のGUIツールキットの上に位置する上位層ですので、Linuxアプリケーションを開発する方法として、いくつかの選択肢が利用できます。Motifバージョン (あるいは、もっと上手い方法はフリーソフトのLesstif) を利用することもできれば、wxWindowsのGTK+ バージョンを利用することもできます。GTK+ は、GNOMEがベースとして使用しているウィジェット・ツールキットですが、KDEでも問題なく動作します。みなさんにはGTK+ バージョンをお薦めします。通常こちらのほうが新しいし、こちらを使っている開発者のほうが多いし、ユーザーもこちらのほうが多いからです。したがって、メーリング・リストやニュースグループでGTK+ バージョンについて質問すると、答がたくさん得られることが期待できます。
以下は、移植前と移植後でアプリケーションの画面がどんな感じになるかを示したものです。
図3. もともとのMFCアプリケーション
図4. WindowsでのwxWindowsアプリケーション
図5. Linux/KDEでのwxWindowsアプリケーション
wxWindowsツールキットは、「おたく」向けの新しいおもちゃではありません。成熟し安定していますし、多くの人が実際的な問題を解決するための実際的なアプリケーションに使用しています。
wxWindowsプロジェクトの創始者で、現在Red HatにいるJulian Smartは、eCos Configuration ToolをwxWindowsに移植しています。eCos Configuration Toolというのは、eCos組み込み型オペレーティング・システムを構成するためのグラフィック・ツールです。元はMFCアプリケーションだったのですが、他でもなくwxWindowsとLinuxに移植されています ( 参考文献 参照)。またSciTech Softwareも、同社のSciTech Display Doctorという製品向けに、wxWindowsを使用して、フロントエンドGUIを完全に開発し直しています。IBMは、SciTech Display Doctorの特殊バージョンのライセンスを取得し、現在、IBMは、Warp Client、Workspace On Demand、Warp Server for e-businessなど、同社のOS/2 Warpベースのすべてのオペレーティング・システムの基本的なディスプレイ・ドライバーとして、この製品を配布しています。SciTech Software社は、wxWindowsコミュニティーに対していろいろと活発に貢献しており、wxApplet、wxUniversal、wxMGLといった拡張製品を提供しています。wxMGLを付加すると、wxWindowsアプリケーションは、DOSや、QNX、RT-Target、SMXなどの組み込み型オペレーティング・システムでも実行できるようになります。SciTech Software社は、wxUniversalとwxMGLの両方のプロジェクトに全額の資金を提供しています ( 参考文献 参照)。
本稿では、MFCドキュメント / ビュー・フレームワークを利用しているWindowsアプリケーションを、wxWindowsツールキットを使ってLinuxに移植するときの基本的な手法を紹介しました。wxWindowsツールキットは、数多くの点でMFCフレームワークに類似しているため、MFCプログラマーも、すぐさまLinux開発のスピードに付いていけるようになります。基本的な知識を身につけましたので、みなさんのすばらしいアプリケーションをLinuxに移植できるようになったはずです。しかし忘れないでください。wxWindowsはLinux専用ではありません。たぶん、みなさんのアプリケーションに、他のUNIXや、さらにはMacintoshででも実行できるようなバージョンを用意することを考えてもよいのではないでしょうか。
-
本稿で紹介した元のアプリケーションと移植版の両方の
全ソース・コード
は、Markusのホームページからダウンロードできます。
-
wxWindowsの概要が、Markus著の記事
wxWindowsの概要
(developerWorks、2001年2月) で紹介されています。
-
wxWindowsコミュニティーのメンバーになりたい方は、
wxWindowsのホームページ
が玄関になります。ここには、ダウンロード、メーリング・リスト、サンプル・アプリケーション、wxWindows関連のツールなど、wxWindowsに関する情報やサポートが提供されています。
-
KPartsを用いたコーディング
(developerWorks、2002年2月) では、K Desktop
Environment用グラフィック・コンポーネントのKPartsアーキテクチャーが解説されています。
-
K Desktop Environmentについて詳しく知りたい方は、
KDEホームページ
を訪れてください。
-
GNOMEについて詳しく知りたい方は、
GNOMEホームページ
を訪れてください。
-
Red Hat社のサイトに
eCos Configuration Tool
の詳細が紹介されています。
-
SciTech Software社のサイトに
SciTech Display Doctor
の詳細が紹介されています。
-
developerWorks のLinuxゾーンには、他にも
Linux関係の記事が多数
掲載されています。
Markus Neiferの初めてのプログラミングはLOGOの亀の助けを得て行ったものでした。その後で、さまざまなBASICを使用しました。地理情報の研究中にCをある程度学習しましたが、すぐに、オブジェクト指向である点が気に入ってC++ とJavaに乗り換えました。R&Dの仕事をしていたときに、科学ソフトウェアのオブジェクト指向開発に関する記事を発表しました。現在は、地理情報システムの分野でソフトウェア・エンジニアとして働いています。彼の連絡先はmarkusneifer@my-deja.com です。