Skip to content

Instantly share code, notes, and snippets.

@LegalizeAdulthood
Last active November 12, 2023 02:34
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save LegalizeAdulthood/4ada76ae95f11fffab04dc7f81a20646 to your computer and use it in GitHub Desktop.
Save LegalizeAdulthood/4ada76ae95f11fffab04dc7f81a20646 to your computer and use it in GitHub Desktop.
Porting from MFC to wxWidgets

Porting from MFC to wxWidgets

This is a step-by-step guide for porting an application from MFC to wxWidgets based on my experience in porting Hippy.

Reference

Basic Advice

Try to keep your application building and running with small changes for as long as possible. If your application has custom control and frame classes, at some point you will have to bite the bullet and change code in several places at once to wxWidgets. This will involve many changes, so try to get as many other small changes done as possible before making the switch.

If you have complex interactions between controls in a dialog or frame, consider first using the Mediator Pattern to decouple the interaction logic from MFC. You can then write unit tests for the mediator to confirm existing application behavior before porting to wxWidgets. Decoupling the logic from the UI toolkit, in this case MFC, will make it less likely that the interactions will change during the port to the wxWidgets UI toolkit.

An example of decoupling user-interface logic from the UI toolkit via the mediator pattern can be seen by comparing two Winlamb example programs: 3-dialog_controls which shows interacting controls on a dialog with inteaction logic directly in event handlers and 4-dialog_controls_mediator which shows the same interaction achieved through a decoupling mediator. These examples are discussed in the video Writing Native Win32 Applications with WinLamb and Modern C++.

Detailed Steps

Build Setup

  • Created a CMake build for your MFC project if you don't already have one.
    • Set global options relevant to MFC:
      • add_definitions(-D_AFXDLL -D_WIN32_WINNT=_WIN32_WINNT_WIN7 -DWINVER=_WIN32_WINNT_WIN7)
      • Pick a different Windows API version if you require more recent functionality than Windows 7; consult your existing Visual Studio project and source files for the relevant version.
      • set(CMAKE_MFC_FLAG 2)
    • Examine other settings in your Visual Studio project files, if you have them.
    • When configuring and building with CMake, use a build directory outside your source directory.
    • Keep your existing Visual Studio solution and project files for comparison as you work on the port.
    • See the modified wxmfc sample for an example of the submodule and CMake changes described above.
  • Obtain wxWidgets from vcpkg
    • Add a vcpkg submodule to your git repository
    • Add a wxwidgets (note: lowercase) dependency to your vcpkg.json manifest
    • Configure CMake with the CMAKE_TOOLCHAIN_FILE from vcpkg to get dependencies.
    • Add find_package(wxwidgets CONFIG REQUIRED) to your CMake project
  • Add wx::base as a dependency. This forces a _UNICODE build.

Unicode Support

  • Adapt any ANSI MFC char style string code to TCHAR MFC style string code.
    • Use CT2A to convert TCHAR/CString style strings to ANSI char strings.
    • Use CA2T to convert char/std::string style strings to Unicode TCHAR strings.
    • Use TCHAR related string functions, such as _tcscpy, _stprintf and so-on.
    • See Text and Strings in Visual C++ for an explanation of how preprocessor defines switch between multi-byte character strings (MBCS), Unicode character strings and ASCII character strings with TCHAR and the associated t string functions.

Unit Testing

  • If you don't have unit tests for your application logic, consider adding them before porting.

Introducing wxWidgets Headers

  • Attempt to #include <wx/wx.h> in your source files as the last included header
    • If you get errors about member functions being undefined when they previously built without problems, it's likely that the member functions in the MFC headers share a name with a Win32 API function.
    • Win32 API functions accepting or returning string arguments are declared with a macro that is conditionally compiled into an ANSI or Unicode name, depending on build flags.
    • When this error occurs many times, the simplest thing is to revert the inclusion of <wx/wx.h> and move on to the next file.
      • This is only a temporary reprieve as you will need to include wxWidgets headers eventually.
    • If the error occurs only once or twice, another option is to conditionally call the W suffixed member or the A suffixed member based on the definition of the UNICODE macro.
  • Move #include <wx/wx.h> from source files to header files for classes so wx-related types can be used in the member functions

String Handling

  • Convert uses of CString to uses of wxString, one class at a time
    • Pay attention to the differences between the member function CString::Format and the static member function wxString::Format.
    • Pay attention to the semantics of other string member functions.
      • CString::Delete(0) is not the same as wxString::Remove(0)
      • wxString does not have TrimLeft or TrimRight, but you can use wxString::find_first_not_of and wxString::find_last_not_of members to locate the inner portion of the string you wish to keep and then use wxString::SubString to extract the inner portion.
    • When you have completed this step, you will have an MFC program using wxString for internal storage and member functions. You can interoperate with MFC functions through conversions to LPCTSTR and LPTSTR.
    • If a class only used MFC headers for CString, then drop the inclusion of MFC headers from the class's header file.
  • Convert uses of the _T macro to wxT macro and uses of TCHAR type to the wxChartype.
  • While wxChar gives you a portable datatype for character buffers, wxWidgets doesn't provide the corresponding string functions such as _tcscpy provided by the Windows header <tchar.h>.
  • Replace C style (t)string manipulation functions with corresponding wxString instances and member functions.

Logging and Assertions

  • Change uses of ASSERT to wxASSERT
  • Change uses of TRACE to wxLogDebug

File Handling

  • MFC uses CFile, CStdioFile and CMemFile to encapsulate reading and writing from files.
    • More advanced usages encapsulate socket communication as a file stream.
    • wxWidgets has its own networking stream classes to cover those usages.
  • std::ifstream and std::ofstream are options; both MFC and wxWidgets predate a standard library and evolved file handling classes independently.
  • wxWidgets uses wxFile, wxFFile and wxTextFile for basic file I/O.
  • Switch usages of CStdioFile to either wxFile or wxTextFile, depending on the usage pattern.
  • Switch code that uses that base class abstraction CFile to wxFile.

MessageBox and Other Standard Dialogs

  • Add a dependency on the CMake imported target wx::core for code that uses wxMessageBox
  • Convert uses of MessageBox to wxMessageBox
  • Convert uses of MessageBeep to wxBell
  • Convert use of standard modal dialogs

User Settings

  • CWinApp::SetRegistryKey sets the registry key used for application settings, including the most recently used (MRU) file list
    • Use wxConfig in wxWidgets applications to store user settings.
    • Use wxFileHistory to manage the MRU file list.

Migrating the Application Class

  • <wx/msw/mfc.h> provides some classes to interoperate between wxWidgets and MFC.
    • Include this header in the source file where your CWinApp derived class is declared
    • Change the base application class from CWinApp to wxMFCWinApp
  • <wx/nativewin.h> declares wxNativeWindow and wxNativeContainerWindow
    • wxNativeWindow is for using native windows inside wxWidgets windows
    • wxNativeContainerWindow is for creating other wxWindows inside it

Migrate Resources

  • MFC applications can include images as resources in .rc files that are loaded programmatically
  • wxWidgets has an XML based resource system stored in .xrc files
  • Migrate the image resources from MFC resource scripts to wxWidgets xrc files and load wxImage or wxBitmap objects

Migrate Controls

  • Migrate standard controls to their equivalent wxWidgets controls
  • Host the wxWidgets controls in a wxNativeContainerWindow constructed from the MFC m_hWnd
  • Start from the inner-most controls and work outwards
  • Use wxWidgets sizer classes to arrange controls within panels
  • Once a source file representing a control no longer depends on MFC, remove inclusion of any MFC headers from its source and header files.
  • Remove any inclusion of <wx/msw/mfc.h>

Migrate Top-Level Window Frames

  • Once all the child controls are migrated to wxWidget controls, migrate the top-level frame to a wxWidgets frame
  • Once a source file representing a top-level frame no longer depends on MFC, remove inclusion of any MFC headers and its source and header files.
  • Remove any inclusion of <wx/msw/mfc.h>

Decouple the Application Class from MFC

  • Change your application class to derive from wxApp
  • Change the application class to create the first top-level frame as a wxWidgets frame.
  • Remove inclusion of any MFC headers from application source and header files.
  • Remove any inclusion of <wx/msw/mfc.h>

Final Removal of MFC

  • Remove the CMake variable setting for CMAKE_MFC_FLAG

Isolate any Remaining Windows Dependencies

  • You may be using other aspects of the Win32 API that are not encapsulated by MFC, e.g. direct use of CreateProcess
  • Look for existing wxWidgets classes that offer the same functionality
  • Look for user-contributed components to wxWidgets that offer the same functionality
    • If it's missing from wxWidgets directly, chances are someone else may have already solved the same problem
  • Create your own wxWidgets oriented class that abstracts the functionality, e.g. your own wxProcess class
    • wxProcess calls CreateProcess on Windows
    • wxProcess calls posix_spawn on linux and macOS

Cross Platform Build

  • Create a continuous integration build for your application on linux
    • Do this even if you only plan to ship on Windows
    • Address any new compilation warnings or errors that result from using gcc/clang to build your code
  • Create a continuous integration build for your application on macOS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment