Still maintaining that legacy Windows application built using Microsoft Foundation Classes (MFC), but now you have clients requesting a Linux version? You may have highly skilled MFC developers on your team, but how do you come up to speed with Linux development? Don't panic; this article is for you. With the help of wxWindows, a portable GUI toolkit for C++ and Python, I'll show you how to port a Windows-only MFC application to Linux using a Multiple Document Interface (MDI) text editor as an example. A small application like this helps focus the discussion on the nuts and bolts of porting the framework and prevents us from getting lost in a sea of code. Complete source code for both the MFC application and the wxWindows application is available in the Resources section later in this article.
The application I'll illustrate uses the well-known document/view architecture, since it deals with documents as most applications do. Even if your application does not use the document/view architecture, I recommend you read on. You might want to add this feature as long as you're already switching the framework.
In my
previous article
about wxWindows, I pointed out some similarities between MFC and
wxWindows. The string classes CString and
wxString and the event system are very close to
each other. But the similarities do not end here. The wxWindows toolkit
also provides MFC-like support for the document/view architecture.
I'll start with a comparison of the core classes. The following table lists the classes involved in the document/view architecture for both frameworks.
Table 1. Document/view class comparison
| Class | MFC class | wxWindows class |
| Document | CDocument | wxDocument |
| View | CView | wxView |
| Edit view | CEditView | n/a |
| Template class | CMultiDocTemplate | wxDocTemplate |
| MDI parent frame | CMDIFrameWnd | wxDocMDIParentFrame |
| MDI child frame | CMDIChildWnd | wxDocMDIChildFrame |
| Document manager | n/a | wxDocManager |
Except for the edit view class, each MFC class has its wxWindows
counterpart. (The last item is empty for MFC, because MFC does not have a
separate document manager class. The documents are handled internally by
the application class CWinApp.) The following
UML diagrams show the relationships between the classes:
Figure 1. MFC classes
Figure 2. wxWindows classes
Each framework provides a class that represents the application itself. The MFC application class declares a constructor, a method for initialization, a method for event handling, and a message map. You need the message map declaration and the event handling method, because the about dialog of the application will be handled by this class.
Application class: MFC
class CPortMeApp : public CWinApp
{
public:
CPortMeApp();
virtual BOOL InitInstance();
afx_msg void OnAppAbout();
DECLARE_MESSAGE_MAP()
};
|
Note: I used the application wizard included in Microsoft Visual Studio to
initially create the MFC application, but I won't show the
sometimes-confusing wizard-generated comments
(//{{AFX_MSG and the like) in my code snippets.
See the ZIP archive for full source code.
The wxWindows counterpart looks slightly different. It too declares a constructor and a method for initialization but needs nothing for message handling. As you'll see later, the about dialog is handled in the main frame class.
Application class: wxWindows
class PortedApp : public wxApp
{
public:
PortedApp();
bool OnInit();
int OnExit();
protected:
wxDocManager* m_docManager;
}; |
This class needs a wxDocManager attribute to
handle the templates created in the initialization method
OnInit(), as described below. The cleanup
method OnExit() will delete this
wxDocManager object when the application
quits.
Every application needs its entry point (also known as
main() or
WinMain()). The two frameworks have slightly
different approaches to this. In MFC you create a static object of your
application class like this:
CPortMeApp theApp;
In wxWindows you use the IMPLEMENT_APP() macro
like this:
IMPLEMENT_APP(PortedApp)
If you're interested in what this macro does, take a look at its definition
in header file wx/app.h, which you'll find in
the downloadable source code. Basically, it will insert the appropriate
entry point function for the platform at hand. After an object of the
application class is created, you need to initialize this object. For MFC,
Microsoft recommends not using the application object's constructor to
initialize the object. Instead, you should use its
InitInstance() method. To perform any cleanup,
implement the ExitInstance() method.
While application initialization has a lot to it, I'll focus on
document/view-related code here. To set up the document/view framework,
the InitInstance() method has to create a
CMultiDocTemplate, like so:
Setting up the document/view code: MFC
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_PORTMETYPE,
RUNTIME_CLASS(CPortMeDoc),
RUNTIME_CLASS(CChildFrame),
RUNTIME_CLASS(CPortMeView));
|
The wxWindows application provides an OnInit()
method to do any initialization work and an
OnExit() method for cleanup. To set up the
document/view framework in our wxWindows application, the
OnInit() method has to create a
wxDocTemplate like this:
Setting up the document/view code: wxWindows
m_docManager = new wxDocManager();
wxDocTemplate* pDocTemplate;
pDocTemplate = new wxDocTemplate(
m_docManager, "Pom", "*.pom", "", "pom", "Pom Doc", "Text View",
CLASSINFO(PortedDoc),
CLASSINFO(PortedView));
|
What happens is basically the same for MFC and wxWindows. The framework needs information about which document relates to which view and what type of document this combination handles. The type includes a descriptive name for the document and the file extension for such documents. Both frameworks use a template to handle this (please note that this has nothing to do with Standard C++ templates).
The MFC CMultiDocTemplate also holds information
about what child frame relates to a document, and the template is added to
the application object that manages the templates. The wxWindows
wxDocTemplate additionally needs a
wxDocManager object that manages the templates.
Remember that the wxDocManager is an attribute
of the application class for the wxWindows application.
After document/view framework initialization is complete, the main frame
for the application is created. In the MFC application's
InitInstance() method, you create a main frame
as follows:
Creating a main frame: MFC
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
|
The call
pMainFrame->LoadFrame(IDR_MAINFRAME)
loads all information about the main frame from a resource file.
In wxWindows you can set up your menus, dialogs, and controls from a resource file or you can create them in your code. I prefer to create them in the code, but if you like to separate your code and your resources, you should have a look at the wxWindows resource system (see topic overviews in the wxWindows documentation).
Creating a main frame: 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);
|
Before I look at what happens after application initialization is complete, let me show you the document and view classes for each framework.
The document holds the file-based data that your application works with. It is responsible for loading this data from file and saving it back to file if necessary. The declaration for the MFC class looks like this:
Document class declaration: MFC
class CPortMeDoc : public CDocument
{
protected:
CPortMeDoc();
DECLARE_DYNCREATE(CPortMeDoc)
public:
virtual BOOL OnNewDocument();
virtual void Serialize(CArchive& ar);
virtual ~CPortMeDoc();
DECLARE_MESSAGE_MAP()
};
|
Please note that this class has a protected constructor. You will never
create any objects of this class directly; instead, the framework uses
serialization to create a document. Therefore, you need to use the
DECLARE_DYNCREATE() macro. The declaration for
our wxWindows class looks like this:
Document class declaration: 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)
};
|
The DECLARE_DYNAMIC_CLASS() macro is necessary,
because wxWindows will create objects of this class dynamically just like
MFC does.
If your MFC application deals with text documents, it's a very good idea
to derive your view class from CEditView. This
class already offers basic editing functionality and a text control inside
its client window. I follow my own advice here, and the declaration for
the MFC view class looks like this:
View class declaration: 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()
};
|
In wxWindows there's no special edit view (yet). But it's easy to create
your own. You just need a wxTextCtrl-derived
text control inside the views frame. The view class handles both the
control and the frame as class attributes. Therefore the wxWindows
declaration looks like this:
View class declaration: 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)
};
|
In addition to the usual event handlers for window creation, window redraw, and window closing, this class has a method that creates the frame and populates its menu bar.
While it's not a good idea to have public attributes, I'm doing it for simplicity here. You should avoid this in your applications (and I will remove this in a future version).
Now that you have the document class and the view class to handle and show
the data, and you also have an application class to handle the
document/view framework, you need a main frame class to interact with the
user. Again, both frameworks offer similar functionality while the actual
implementations are slightly different. The MFC main frame class holds a
status bar and a toolbar as attributes, offers methods to handle window
creation, and declares a message map. Please note that this class derives
from CMDIFrameWnd because this application has
a MDI interface.
Main frame class: 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)
};
|
The wxWindows main frame class holds its menu as an attribute, has methods
to create its toolbar, and declares an event table plus a method to handle
the application's about dialog. Because this application has a MDI
interface, this class derives from
wxDocMDIParentFrame.
Main frame class: 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();
};
|
The port is finished. I'll review what was needed to get to this point.
The MFC application class (CWinApp-derived) was
ported to a wxApp-derived application class,
including initialization code. The MFC document class
(CDocument-derived) was ported to a
wxDocument-derived document class. The MFC view
class (CView-derived) was ported to a
wxView-derived document class. Besides these
document/view-related classes, the MFC main frame class
(CMDIFrameWnd-derived) was ported to a
wxDocMDIParentFrame-derived class.
In its current version, the application already does quite a bit. You can work with multiple text files. You can open, edit, and save them, and the application maintains a history of recently opened files. If you had to code this functionality from scratch, many more lines of code would be needed -- and more lines of code means more lines to test and maintain. In both frameworks, special command identifiers are used to handle the common document/view-related commands. Common file commands are new, open, and save. Common edit commands are cut, copy, and paste. Other often-used commands are the print command, which is not implemented in this application, and the help command. The following table lists the command identifiers involved in the document/view architecture for both frameworks.
Table 2. Standard command identifiers
| 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 |
If you're serious about Linux development, you may already have done some research and found out that there are different window managers available for Linux. As a former MFC developer, you might find this odd. When developing for Windows, you don't need to worry about different window managers because there's only one available.
When starting out with wxWindows development, you might wonder whether your application will run on both leading window managers, K Desktop Environment (KDE) and GNOME. I have used the wxWindows toolkit on Microsoft Windows NT 4.0, Sun Solaris 2.6 using the Common Desktop Environment (CDE), and Linux 2.2 using KDE without problems. As wxWindows is just a high-level layer build on top of other lower-level GUI toolkits, you have some options for how to build your Linux application. You can use the Motif version (or better yet, the free Lesstif) or the GTK+ version of wxWindows. GTK+ is the base widget toolkit GNOME uses, but it also works well under KDE. I recommend that you use the GTK+ version, because it is usually more current, has more developers working on it, and has more users using it. Therefore, you can expect more help if you ask questions about the GTK+ version on the mailing list or in the newsgroup.
Below are some before-and-after screenshots to give you an idea of what the ported application looks like.
Figure 3. Original MFC application
Figure 4. wxWindows application on Windows
Figure 5. wxWindows application on Linux/KDE
The wxWindows toolkit is not just another toy for nerds. It's mature and stable, and people are using it in real-world applications to solve real-world problems.
wxWindows project founder Julian Smart, currently with Red Hat, has ported the eCos Configuration Tool to wxWindows. The eCos Configuration Tool is a graphic tool for configuring the eCos embedded operating system. The original MFC application has been ported to wxWindows and Linux in particular (see Resources). The folks at SciTech Software have also used wxWindows to completely redevelop the front-end GUI for their SciTech Display Doctor product. IBM has licensed a special version of SciTech Display Doctor, which is now distributed by IBM as the primary display driver for all of IBM's OS/2 Warp-based operating systems, including the Warp Client, Workspace On Demand, and Warp Server for e-business. SciTech Software is an active contributor to the wxWindows community and has submitted the extensions wxApplet, wxUniversal, and wxMGL. With wxMGL, wxWindows apps can even run on DOS and other embedded operating systems such as QNX, RT-Target, SMX, and others. SciTech Software has fully funded both the wxUniversal and wxMGL projects (see Resources).
This article has shown the basic principles of porting Windows applications that use the MFC document/view framework to Linux using the wxWindows toolkit. The wxWindows toolkit offers several similarities to the MFC framework to help MFC developers quickly come up to speed on Linux development. Armed with this basic knowledge, you should now be able to port your cool killer application to Linux. But never forget: wxWindows is not a Linux-only thing. Maybe it's time to think about a version of your application that runs on other UNIXes or even on the Macintosh.
- Download
complete source code
for both the original and ported applications presented in this
article from Markus' home page.
- For an overview of wxWindows, read Markus' article
Looking through wxWindows
(developerWorks, February 2001).
- The wxWindows home page is the
main place to go for wxWindows community members. It provides
information on and support for wxWindows, including downloads, mailing
lists, sample applications, and wxWindows-related
tools.
- Coding with KParts
discusses the KParts architecture for graphical components for the K
Desktop Environment (developerWorks, February
2002).
- For more on the K Desktop Environment, go to the
KDE home page.
- Check out the GNOME home page for
more information on GNOME.
- More on the Red Hat
eCos Configuration Tool
can be found on the Red Hat site.
- You can get more information on
SciTech Display Doctor at
the SciTech Software site.
- More information on the
OS/2 Device Driver Pak
(which uses SciTech Display Doctor) can be found on the OS/2 Warp
page.
- Find
more Linux articles
in the developerWorks Linux zone.
Markus Neifer first programmed with the help of the LOGO turtle and used various flavors of BASIC after that. During his studies of geoinformatics he learned some C but moved quickly to C++ and Java because of their object-oriented nature. He has worked in R&D where he has published articles on object-oriented development of scientific software. Currently he's working as a software engineer in the field of geographic information systems.




