114 Using cmake to build MFC projects

Preprocessor flags that might need to be defined

Preprocessor flag Description
UNICODE Used by Windows headers. Affects the character set the Windows header files treat as default. So if you define UNICODE, then GetWindowText will map to GetWindowTextW instead of GetWindowTextA, for example. Similarly, the TEXT macro will map to L"..." instead of "...". See TEXT vs. _TEXT vs. _T, and UNICODE vs. _UNICODE by Raymond Chen.
_UNICODE Used by C-runtime/MFC headers. Affects the character set the C runtime header files treat as default. So if you define _UNICODE, then _tcslen will map to wcslen instead of strlen, for example. Similarly, the _TEXT macro will map to L"..." instead of "...".
_WINDOWS
WIN32
_AFXDLL Should be defined when using MFC in a Shared DLL. When using vcxproj projects if you pick "Use of MFC in a Shared DLL" then _AFXDLL is automatically defined.
_AFXEXT MFC extension DLLs should be compiled with _AFXEXT defined.

Unresolved winmain error

It's essentially a limitation when using the Ninja (and Makefile) generators in CMake, you have to explicitly specify the application entry point:


# CMakeLists.txt
...
target_link_options(${PROJECT_NAME} PRIVATE "/entry:wWinMainCRTStartup")

See Unresolved WinMain error in Unicode MFC application built with CMake using Ninja on stack overflow.

Also see the Unresolved WinMain error in Unicode MFC application built with CMake using Ninja generator issue on gitlab.kitware.com.

cedaAddDependentProject

cedaAddDependentProject(myproject) should be called on a non-XCPP library or executable myproject which depends on one or more XCPP projects in the same workspace. This is to ensure xcpp is run in order to generate the headers before trying to compile files in myproject.

cedaSetDefaultsForTarget

cedaSetDefaultsForTarget(myproject) should be called on a non-XCPP library or executable myproject to set the locations for generated files, following the conventions used by XCPP projects.

Example CMakeLists.txt for an XCPP MFC extension DLL

See Microsoft documentation of MFC extension DLLs.

Requirements:

  • Extension DLLs are built using the dynamic-link library version of MFC (also known as the shared version of MFC). Therefore set(CMAKE_MFC_FLAG 2) must be used.
  • Preprocessor macros _AFXDLL and _AFXEXT must be defined.
  • Don't instantiate a class derived from CWinApp
  • Provide a DllMain function (see below)
  • The executable that uses the extension DLL must be compiled with _AFXDLL and use the shared MFC

# MFC extension DLL cxMFC written in Xc++
add_definitions(-DUNICODE -D_UNICODE -D_WINDOWS -D_AFXEXT -D_AFXDLL -DWIN32)
set(CMAKE_MFC_FLAG 2)
cedaTarget(XCPP NAMESPACE CedaGui PROJPATH CedaGui/cxMFC)
target_link_libraries(cxMFC
    PUBLIC
        Ceda::cxUtils
        Ceda::cxLss
        Ceda::cxObject
        Ceda::cxPersistStore
        Ceda::cxOperation
        Ceda::cxWorkingSetIpc
        Ceda::cxMessage
        CedaGui::cxGeom
        CedaGui::cxModel)

DllMain should be written like this:


extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        if (!AfxInitExtensionModule(cxMFC_DLL, hInstance)) return 0;
        new CDynLinkLibrary(cxMFC_DLL);
    }
    else if (dwReason == DLL_PROCESS_DETACH)
    {
        AfxTermExtensionModule(cxMFC_DLL);
    }
    return 1;
}

CDynLinkLibrary allows the extension dll to provide resources, this works as long as they don't have overlapping resource ids.

Example CMakeLists.txt for a non-XCPP project

In the following example exMFC is a non-XCPP project.

cedaAddDependentProject(exMFC) is required because exMFC depends on XCPP projects cxGeom, cxModel, cxMFC and therefore must be able to see the generated headers in those projects.


add_definitions(-DUNICODE -D_UNICODE -D_WINDOWS -D_AFXDLL -DWIN32)

# set CMAKE_MFC_FLAG to 1 for the static MFC library, or 2 for the shared one
set(CMAKE_MFC_FLAG 2)

add_executable(exMFC WIN32
    ClassView.cpp
    ClassView.h
    exMFC.cpp
    exMFC.h
    exMFC.rc
    exMFCDoc.cpp
    exMFCDoc.h
    exMFCView.cpp
    exMFCView.h
    FileView.cpp
    FileView.h
    framework.h
    MainFrm.cpp
    MainFrm.h
    OutputWnd.cpp
    OutputWnd.h
    pch.cpp
    pch.h
    PropertiesWnd.cpp
    PropertiesWnd.h
    Resource.h
    targetver.h
    ViewTree.cpp
    ViewTree.h)

target_link_libraries(exMFC
    PUBLIC
        Ceda::cxUtils
        Ceda::cxLss
        Ceda::cxObject
        Ceda::cxPersistStore
        Ceda::cxOperation
        Ceda::cxWorkingSetIpc
        Ceda::cxMessage
        CedaGui::cxGeom
        CedaGui::cxModel
        CedaGui::cxMFC)

cedaAddDependentProject(exMFC)
cedaSetDefaultsForTarget(exMFC)

# See https://stackoverflow.com/questions/63924292/unresolved-winmain-error-in-unicode-mfc-application-built-with-cmake-using-ninja and
#     https://gitlab.kitware.com/cmake/cmake/-/issues/21202
target_link_options(exMFC PRIVATE "/entry:wWinMainCRTStartup")