Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Hosting of MFC MDI Applications from Within WinForms and WPF Applications

0.00/5 (No votes)
31 May 2022 5  
Building old MFC app in .NET app, replacing MFC MDI Framework with modern WinForms or WPF Framework and integrating MFC Views and Dialogs in it
This article demonstrates the concept of how to build an old MFC application in a .NET application, to replace the MFC MDI Framework with the modern WinForms or the WPF Framework and to integrate MFC Views and Dialogs in it.

Programming Guide with source code for hosting simple wizards MFC/Dialog application:

Contents

Introduction

As far as the way in which .NET has been developed, for a bunch of applications made in the MFC MDI architecture, there is a new problem of expensive migration into .NET. I've found only one migration tool - RC Converter by DudeLabs. But it supports the transformation of a small part of MFC MDI GUI - MFC resources only. The larger part of the GUI and all business logic, you have to convert yourself.

As an alternate to the migration scenario, Microsoft offers some interoperation solutions (in an interoperation scenario, you don't try to turn the MFC code into .NET automatically, you just try to ensure that they can work together):

The first technology allows to host the MFC control only. ("If your Win32 logic is already packaged up nicely as a control...") You can turn a dialog into a control with some efforts - but not the larger MFC MDI application.

The MFC Windows Forms support classes allow the backwards integration of the .NET code in an MFC application. It lets you arrange .NET Forms as Views or Dialogs. There are some important limitations in this case: the .NET Framework cannot be integrated (the old MFC Framework has to be used), the part of the integrated Forms functionality is not available (for example, laying out), and a separate control can be integrated as a Form only (for example, the .NET MenuStrip cannot be used in the MFC Framework). I find that these kinds of interoperation solutions are pure alternates to the migration scenario, and I haven't found real examples of these solutions on the Internet.

In view of a specific application (UMLEditor - revisiting the vector editor by Johan Rosengren), this article illustrates how you can build your old MFC MDI application in an existing .NET application or rapidly create a new WinForms/WPF GUI Framework for it. After this, you can gradually migrate from the old MFC dialogs and views to the new WinForms and then to the WPF UI elements. Such an intermediate integration of MFC and WinForms might seem to be more expedient for larger MFC MDI projects in comparison with a complete migration into .NET.

From the technical point of view, a mixed MFC DLL with a CWinApp-derived class, the hosting of MFC views in WinForms controls, and the support of the WPF auto-layout in hosted .NET 1.x and .NET 2.0 WinForms controls used in this article, are not discussed on the Internet.

It is an example of how your application's GUI framework can be evolved by using the MfcAdapter wrapper (50 KB! source code for the WinForms and WPF version):

UMLEditor as an original MFC MDI application:

UMLEditor built using the WinForms Framework - Forms with docking and skinning (stardock):

UMLEditor built in a WPF page (FlowDocument with two UML diagrams):

Mixed MFC With a CWinApp-derived Class and Managed C++ DLL

As a rule, the MFC class based application architecture is strongly typed. A lot of classes, for example, the MDI classes CDocument, CView, CFrameWnd, and others cannot be used without the CWinApp (application) class and a complicated initialization. The simple solution in this case is to put the full application in a DLL and to mix with the managed C++ classes. We can use these classes as the managed interface between the WinForms Framework and the MFC application. In this case, we have to resolve the problem with a "second" MFC application main window. It has to be invisible, and all the MFC windows have to be hosted in a WinForms MainForm window. However, all the functionality of the MFC application can be used in a managed Framework in this case. First of all, let's discuss mixed DLL creation. We have different solutions for VS2003 and VS2005 as the mixed DLL loading process is different in each case (Initialization of Mixed Assemblies).

VS2003

Let's use a standard solution from MSDN (Converting Managed Extensions for C++ Projects from Pure Intermediate Language to Mixed Mode). See the paragraph "To modify your DLL that contains consumers that use the managed code and DLL exports or managed entry points."

In order to prevent calling unsafe code during DLL initialization (loader lock problem), Microsoft offers to use the /noentry linker option. In this case, the mixed DLL has no entry point, _CRT_INIT doesn't call the static MFC constructors, and _DllMainCRTStartup doesn't call the MFC DllMain (which calls CWinApp::InitInstance). Only managed objects would be created and initialized.

To create static MFC objects and to initialize MFC objects, you have to call the __crt_dll_initialize() static function from your managed code. You have to be careful about the place to call __crt_dll_initialize(), and be careful with your MFC constructors and the InitInstance method code - don't forget about the loader lock problem, and don't use un-initialized MFC native code!!!

Besides this, you have to explicitly call InitApplication at the beginning of InitInstance instead of calling CWinApp::InitInstance(). Otherwise, that will cause a problem with CDocManager, PreTranslateMessage (translation of the short keys into command messages), memory leaks, and others.

There are some general notes on the initialization of mixed MFC and managed C++ DLLs. I won't describe the special cases of MFC object initialization (for example, the initialization of an extension DLL's resource chain), because that is not the main theme of this article. The last note is about termination. You have to be careful to close your CWinApp object first (see the example code in MfcAppAdapter.cpp) and then make a call to __crt_dll_terminate(). The WinForms Framework would be closed properly in this case.

Now, we can check this solution with the UMLEditor (or with the simple wizards MFC/Dialog application). In the case of the UMLEditor, the following project configuration changes were made:

Configuration Type Dynamic Library (.dll)
Character Set Use Unicode Character Set
Use Managed Extensions Yes
Output File .dll
Additional Options /noentry

Update options which come against the compiler option /clr for the project configuration file and for other source files.

Basic Runtime Checks Default

Create the MfcAppAdapter.cpp file, as shown in the picture below, with a simple managed class. The use of the "Singleton" pattern allows us to encapsulate initialization and the disposing of this class from the WinForms code. It enables the MFC application to initialize before its first use, and terminate the MFC application before the WinForms application exits. Besides this, the MfcAppAdapter enables wrapping of the MFC command message interface. We use this class only for DLL testing (more safety code is in the download):

MC++
#include "stdafx.h"
#include "_vcclrit.h"

using namespace System;
using namespace System::Windows::Forms;

public __gc class MfcAppAdapter
{
  private:
    static MfcAppAdapter* m_Instance;
    MfcAppAdapter()
    {
      __crt_dll_initialize();
    
      Application::add_ApplicationExit(
          new EventHandler(this, 
             &MfcAppAdapter::OnApplicationExit));
    }

    void OnApplicationExit(Object* sender, EventArgs* e)
    {
      AFX_MANAGE_STATE(AfxGetStaticModuleState());
      CMDIFrameWnd* pMainFrame = 
          dynamic_cast <CMDIFrameWnd*>(AfxGetMainWnd());
      ::SendMessage(pMainFrame->GetSafeHwnd(),WM_CLOSE,0,0);

      //change DLL flag in AFX_MODULE_STATE to careful call 
      //ExitInstance as for application context
      AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
      pModuleState->m_bDLL = NULL;
 
      __crt_dll_terminate();
      m_Instance=NULL;
    }

  public:
    static MfcAppAdapter* GetInstance()
    {
      if(m_Instance==NULL)
      {
        m_Instance = new MfcAppAdapter();
      }
      return m_Instance;
    }

    bool OnCmdMsg( int nID)
    {
      AFX_MANAGE_STATE(AfxGetStaticModuleState());

      CMDIFrameWnd* pMainFrame = 
          dynamic_cast <CMDIFrameWnd*>(AfxGetMainWnd());
      return pMainFrame->OnCmdMsg(nID, 0, NULL, NULL) != 
                                                   FALSE;
    }
};

The UMLEditor doesn't need special initialization, so we only add InitApplication() to UMLEditorDemo::InitInstance in UMLEditorDemo.cpp. (For other applications, some small changes are necessary.)

After building the DLL, we can host and test it in a simple C# WinForms application (see the picture):

C#
public Form1()
{
   ...
   private void button1_Click(object sender, 
                              System.EventArgs e)
   {
        //ID_APP_ABOUT
        MfcAppAdapter.GetInstance().OnCmdMsg(0xE140);
   }
}

We send the ID_APP_ABOUT command message to the UMLEditor using a button click:

VS2005

As we can see above, only managed initialization is performed in VS2003. We make an unmanaged initialization ourselves after a managed initialization. In VS2005, both unmanaged and managed initializations are performed - the unmanaged initialization takes place first, and the managed initialization takes place after that. Loader lock can still occur (for example, if the code within DllMain accesses the CLR), but now it occurs deterministically, and is detected. Don't try to skip the unmanaged initialization with a /noentry linker option and call __crt_dll_initialize() in VS2005!!!

To guarantee that the code within DllMain and the static variable constructors don't access the CLR, the new mixed DLL loading process needs a special project configuration. No function in the call tree rooted at DllMain can be compiled to MSIL, because it means access to not initialized CLR. We can resolve the issues here, the object file containing these functions should be compiled without /clr. In our case, we set "No Common Language Runtime support" in project properties and "Common Language Runtime Support (/clr)" in every file with the managed classes' properties. If DllMain attempts to execute the MSIL directly, it will result in a compiler warning (level 1) C4747. However, the compiler cannot detect cases where DllMain calls a function in another module, which in turn attempts to execute the MSIL. Static variables with MSIL instructions in the initialization code have to be placed in the managed modules, and should not be initialized and used by unmanaged initialization.

Then, we have to resolve a linker problem - the LNK2005 error. Microsoft does not support CWinApp derived classes in a MFC extension DLL - so may be it is not a linker bug. In any case, if DllMain from the CRT library is linked before the DllMain from the MFC libraries, there will be a link error: "mfcs80ud.lib(dllmodul.obj) : error LNK2005: _DllMain@12 already defined in msvcrtd.lib(dllmain.obj)". I resolve this problem with a small trick: add macro AFX_MANAGE_STATE (AfxGetStaticModuleState()) to the static constructor or at the beginning of InitInstance.

The problem with InitApplication has the same solution as in VS2003.

We don't need to call a special function to create and initialize the static MFC objects, and we don't need to call the termination function! But we have to close the MFC application (use the same code as in VS2003).

In contrast to VS2003, if the MFC application makes an activation of the MainFrame window after the WinForms Main Form activation, then the Managed Debugging Assistants throw the LoaderLock exception. But as will be discussed later, we don't need two activated main windows, so we don't activate the MFC MainFrame window.

We write all the managed classes from the mixed DLL in the new C++/CLI. Use "Managed Extensions for C++ Syntax Upgrade Checklist" for migrating managed VC++ to C++/CLI, or use VS2003 MC++. In this case, simply set the project option /clr:oldSyntax instead of /clr. All other project settings would be the same as for C++/CLI.

Now, we can check this solution with the UMLEditor (or with the simple wizards MFC/Dialog application).

After auto-updating the UMLEditor in VS2005, make the following changes in the project configuration:

Configuration Type Dynamic Library (.dll)
Character Set Use Unicode Character Set
Use Managed Extensions No
Output File .dll

Update options which come against the compiler option /clr for the project configuration file and for other source files:

Basic Runtime Checks Default
Enable Minimal Rebuild No
Enable C++ Exceptions With SHE Exceptions (/EHa)
Debug Information Format Program Database (/Zi)
Create/Use Precompiled Headers Not Using Precompiled Headers
Entry Point  

Create the MfcAppAdapter.cpp file, from the picture below, with a simple managed class. We removed __crt_dll_initialize(), __crt_dll_terminate() from the VS2003 version and used the new C++/CLI syntax. Set CLR support in the MfcAppAdapter.cpp file property:

Compile with Common Language Runtime support Common Language Runtime Support (/clr)
MC++
#include "stdafx.h"
using namespace System;
using namespace System::Windows::Forms;

public ref class MfcAppAdapter
{
  private:
    static MfcAppAdapter^ m_Instance;
    MfcAppAdapter()
    {
      Application::ApplicationExit += 
        gcnew EventHandler(this, 
           &MfcAppAdapter::OnApplicationExit);
    }

    void OnApplicationExit(Object^ sender, EventArgs^ e)
    {
      AFX_MANAGE_STATE(AfxGetStaticModuleState());

      CMDIFrameWnd* pMainFrame = 
          dynamic_cast <CMDIFrameWnd*>(AfxGetMainWnd());

      ::SendMessage(pMainFrame->GetSafeHwnd(),WM_CLOSE,0,0);

      //change DLL flag in AFX_MODULE_STATE to careful call 
      //ExitInstance as for application context
      AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
      pModuleState->m_bDLL = NULL;

      delete m_Instance;

      m_Instance=nullptr;
    } 

  public:
    static MfcAppAdapter^ GetInstance()
    {
      if(m_Instance==nullptr)
      {
        m_Instance = gcnew MfcAppAdapter();
      }
      return m_Instance;
    }

    bool OnCmdMsg( int nID)
    {
      AFX_MANAGE_STATE(AfxGetStaticModuleState());

      CMDIFrameWnd* pMainFrame = 
          dynamic_cast <CMDIFrameWnd*>(AfxGetMainWnd());

      return pMainFrame->OnCmdMsg(nID, 0, NULL, NULL) != FALSE;
    }
};

Add InitApplication() to UMLEditorDemo::InitInstance in UMLEditorDemo.cpp. Build the DLL and test it in a simple WinForms 2005 application.

Architecture of the MfcAdapter

Where are we? So far, we have discussed only the technical problems involved with creating mixed MFC with a CWinApp-derived class and a managed C++ DLL. But, how can we host MFC windows in WinForms or WPF Framework windows? Let's discuss the architecture of the MfcAdapter wrapper:

The MfcAdapter is a managed component that consists of MfcAppAdapter and a suitable WndManager:

  • MfcAppAdapter (C++/CLI) is a wrapper to an unmanaged legacy MFC application. This is the kern of the MfcHost. The general task involves the initialization of the encapsulated MFC application, the command interface to this application, and its windows hosting. The command interface means an interface to the command handlers in the legacy MFC application. To access the Framework, first of all, for attaching the MFC windows to the Framework, MfcAppAdapter uses an external IWndManager interface. This solution allows us to use only one MfcAppAdapter for any Framework type. MfcAppAdapter classes use only MFC based classes of the legacy MFC application, so it's independent of the specific classes of UMLEditor and can be used for wrapping other applications.
  • WndManager is a facade to the Framework which allows us to realize MfcAppAdapter as a component of the used Framework - WinForms component for the MDI Forms Demo, or WPF FrameworkElement for the WPF Page Demo. For other managed Frameworks, suitable WndManagers can be implemented. The common part of WndManagers is the IWndManager interface. WndManager implements this interface for the specific Framework. The remaining part of WndManager depends on the Framework and on the MFC application functionality that we need. For example, the MdiFormsWndManager realizes the handling and update of the Framework's ToolStripMenuItems and ToolStripButtons (MenuItems and ToolBarButtons for VS2003) in the MFC application, and supports the Visual Studio Designer in design-time. WpfPageWndManager realizes the OpenDocument method, and supports the WPF Autolayout manager. In this article, we will discuss MfcAppAdapter and WpfPageWndManager. MdiFormsWndManager is supplementary, in our case, and won't be discussed in this article, but you can use the source code.

Hosting of MFC Views and Dialogs in a WinForms Application

We discuss here the most important issues of the MfcAppAdapter implementation. Use the source code for a detailed understanding.

Changing of the Encapsulated MFC Code

Before we can use the created mixed DLL, we have to resolve some problems.

Unicode Set

Unicode Character Set to simplify string message marshalling.

Hosting Dialogs, Creation, and Activation of the MFC Main Window (UmlEditorDemo.cpp)

MFC dialog windows use the main window of the legacy MFC application as the default parent window. We can keep the modal properties of the hosted dialogs, if we set the WinForms Framework MainForm window as a parent for the legacy MFC application main window. We can resolve this with the following solution:

  1. Add the m_ExtFramework member, AttachApplication method, and the AttachApplication static function to our application class. m_ExtFramework saves the interface to the WinForms Framework (IFramework). AttachApplication method is a setter for m_ExtFramework, and the AttachApplication static function is a simple interface to access the AttachApplication method (we keep the wrapper classes MfcAppAdapter and ViewCtrl independent of the MFC application). The WinForms Framework calls the AttachApplication method after a mixed DLL initialization but before using any of the legacy MFC applications.
  2. Remove the creation of the main window from InitInstance to the AttachApplication method.
  3. Override CMainFrame::PreCreateWindow, and set the MainForm window handle from IFramework in cs.hwndParent.
  4. By closing the MainForm window, the child's windows are destroyed. The legacy main window receives the WM_DESTROY message. We have to be careful to close the legacy MFC application in this case. But the normal close scenario occurs when a client calls dispose on the MfcAppAdapter and closes the legacy MFC application before the WinForms Framework MainForm is closed. So, we write a warning to the output window if the client forgets this.
  5. The message loop implementation is different for .NET 1.x and .NET 2.0. In .NET 1.x, the message loop tries to preprocess some common control messages (WM_NOTIFY, WM_COMMAND) from the modeless dialog controls, first of all, in the MainForm. We resolve this problem by explicitly pre-translating these messages in the legacy MFC application.

We also use CNotifyHook as the COleFrameHook interface realization, to enable the WinForms Framework windows to synchronize with the legacy MFC application windows.

We keep the legacy MFC application main window invisible. So, some source code has to be removed from the application InitInstance:

C++
//// The main window has been initialized, so show and update it. 
//pMainFrame->ShowWindow( m_nCmdShow );
//pMainFrame->UpdateWindow();

The command line processing is in the WinForms Framework now. So, remove this code too:

C++
//// Dispatch commands specified on the command line
// if (!ProcessShellCommand( cmdInfo ))
//     return FALSE;
Hosting View

For hosting MFC views in WinForms controls, we have to change the view classes and the dependent classes, as well:

  1. First of all, the view's content has to be invisible when creating and initializing. It would be activated only after attaching to the WinForms control. In the source code, we set bMakeVisibleparameter to false in the overridden MultiDocTemplate::OpenDocumentFile, CDocManagerEx::OpenDocumentFile, and CMainFrame::OnWindowNew.
  2. We have to save the pointer to the last opened document, which we will use in the WinForms control. It's realized by the SetOpenedDocument method of our IFramework interface.
  3. Don't use CView::OnCreate. Creating and initializing of our view class' members inherited from CWnd, Control, DropTarget.Register, ToolTip, and so on, should be removed from the OnCreate method to the OnInitialUpdate. In this case, these members will be created after the view is attached to the WinForms control.
  4. After the view is attached to the WinForms control by the WndManager, the CView::GetParent method will return the parent hWnd of the WinForms control. So, the entire code uses the CView::GetParent; for example SetTitle, has to be changed to the GetNativeParent method of our IFramework. We make this for CDocManagerEx::OpenDocumentFile, CUMLEditorDemoDoc::CanCloseFrame, and CUMLEditorDemoDoc::UpdateFrameCounts. By closing the control container, WndManager has to dispose the control with the attached view!!! In this case, the view would be detached, the MFC CFrame parent of the view would be restored, and then the view would be closed in the MFC application. We have to check that all views are detached from the WinForms controls before we close the MFC application. UMLEditorDemo::DetachApplication method does this check.
  5. For auto-layout support, we have to implement some methods in our hosted view: AutoSize, GetPreferredSize, and ScaleControl. We use the base class CLayoutView with these methods as the interface for the MfcAppAdapter from the hosted view. The view has to inherit this class. All custom properties of the view which are used in the control have to be implemented in this fashion.
  6. In this article, we don't describe the changes of the view classes inherited from the controls and views with m_dropTarget (support of drag & drop), because the UmlEditor does not have these views.

Wrapper Classes

MfcAppAdapter

The command interface of the legacy MFC application is wrapped by two methods: OnCmdMsg (CCmdTarget::OnCmdMsg) and OnUpdateCmdMsg. We also use the class CCmdUIHandler for the user interface update. This class realizes the CCmdUI interface for the framework menu items and toolbar buttons. The GetMfcMenuString method realizes the access to the MFC CMenu to support "Recent" menu items in the WinForms menu.

In the VS2003 version, the TranslateMsg method supports message dispatching for common controls.

MfcAppAdapter is a singleton. The method CreateInstance can be used to access a private static instance. The MfcAppAdapter constructor calls UMLEditorDemo::AttachApplication to initialize the legacy MFC application. The MfcAppAdapter's Dispose checks if all the hosted views are closed, and then closes the legacy application with the UMLEditorDemo::DetachApplication method.

ViewCtrl

ViewCtrl supports MFC view hosting in WinForms controls. It is a helper class of MfcAppAdapter.

  1. As a child of a WinForms control, ViewCtrl inherits the Control interface. For a view hosting, we have to detach the CView object from the "CView" window and attach the CView object to the "ViewCtrl" window by creating a "ViewCtrl" window.
  2. We keep dispatching messages from the "CView" window to the CView object, by replacing CViewWndProc with StubWndProc. StubWndProc dispatches these messages from the "CView" window to the "ViewCtrl" window and then to the CView object.
  3. To keep the CWnd interface for the "CView" window, we have to attach a StubWnd (CWnd based) to the "CView" window. Now, our CView object is connected with the "ViewCtrl" window and with the "CView" window! It's not so bad. See the picture:

  4. We can make these operations by creating the "ViewCtrl" window, if we change the "Windows Class" in the overridden ViewCtrl::CreateParams. Then, we call the method ViewCtrl::AttachView in ViewCtrlWindowProc by using the WM_CREATE message. When the CView object is attached to the "ViewCtrl" window, we call CView::OnInitialUpdate; and the view content is rendered in the WinForms form, instead of the MFC CFrame.
  5. We have to synchronize our ViewCtrl frame (WinForms form) with the MFC CFrame for supporting the MDI logic of the legacy MFC application. We make this with the following methods:
    • OnGotFocus
    • ProcessDialogKey
    • SetText
    • GetParent
  6. Overriding the OnHandleDestroed method detaches the view from the control to the MFC CFrame and closes the view using dispose.
  7. For autolayout support, the methods AutoSize, GetPreferredSize, and ScaleControl of the base control (VS2005) or of the special base class LayoutControl (VS2003) are overridden. The hosted view, connected through the CLaoutView interface, implements the necessary functionality for these methods.
  8. The MfcAppAdapter uses the IWndManager interface for attaching the created ViewCtrl controls to the external Framework and for activating the main window when the control receives the GotFocus event. Otherwise, the external Framework gets the OnClosing cancel event handler of the MfcAppAdapter. The Framework can use this handler to support the "CanClose" function of the legacy application. In the WinForms Framework, we simply connect the OnClosing cancel event handler with the Closing event of the MainForm.
UmlEditorCommand

The UmlEditorCommand enumeration contains all the command IDs which can be handled in the legacy MFC application.

WPF Autolayout In Hosted WinForms Controls

The WPF class WindowsFormsHost allows us to realize the MfcAppAdapter as a WPF FrameworkElement. Moreover, the VS2005 Visual Designer does not support WPF, and it makes the integration of the MfcAdapter and the WFP Framework easier! The only problem that has to be resolved is that the current version of WindowsFormsHost does not support the WPF Autolayout manager. In this simple version of WPF WndManager, we show how it can be realized.

UmlEditorHost

UmlEditorHost is an extended WindowsFormsHost with auto-layout support for the hosted control. The standard solution involves overriding the WindowsFormsHost base methods: MeasureOverride and ArrangeOverride.

In the MeasureOverride, we get the preferred size from the hosted control (method Control.GetPreferredSize), and decrease it to the desired size, if it's necessary, by keeping the width/height ratio constant.

In the ArrangeOverride, we scale the hosted control to the preferred size, decreased to the final size, if it's necessary, and multiply it with the zoom factor. Zoom factor is kept in the added dependency property Zoom, which can be bound with an external layout manager.

WpfPageWndManager

WpfPageWndManager is the WPF realization of the IWndManager interface. In the AttachCtrl method, we simply add the control created by MfcAppAdapter to UmlEditorHost. In our case, we don't edit the hosted UML diagrams, so we disable the hosted control and don't use its OnClosing event handler.

WpfPageWndManager is responsible for the creation and disposal of MfcAppAdapter and others created by the MfcAppAdapter controls. It's is a singleton, and it creates the MfcAppAdapter instance in its own constructor. In the DisposeContainer, it saves all the hosts with the created controls. Using the Application.Current.MainWindow.Unloaded event, we dispose the DisposeContainer and then the MfcAppAdapter.

.NET 2.0 Control Layout Adapter to .NET 1.x Control

We need MfcHost1Adapter between WpfPageWndManager and MfcAppAdapter if the last one is a .NET 1.x component. The reason for this solution is that .NET 1.x WinForms controls don't have the new layout methods AutoSize, GetPreferredSize, and ScaleControl, and cannot directly support WPF auto-layout. Using a composite control, the .NET 2.0 LayoutControlAdapter, as the container and the .NET 1.x LayoutControl as a constituent control, the MfcHost1Adapter allows us to realize the .NET 1.x WinForms control as a .NET 2.0 WinForms control for the WpfPageWndManager. The standard methods of the LayoutControlAdapter: AutoSize, GetPreferredSize, and ScaleControl would be overridden by the custom methods of the contained LayoutControl. The LayoutControlAdapter can be used in other projects for hosting .NET 1.x controls in the .NET 2.0 Framework.

Extended MfcAppAdapter and WndManagerAdapter allow us to substitute the attached LayoutControl for the composite LayoutControlAdapter.

Demo Framework

You can create simple demo applications yourself or use the downloaded demos. Try using other managed Frameworks instead of these demos (it's more interesting). Here, I'll describe two demo applications: an MDI forms demo and a WPF page demo.

MDI Forms Demo

Create a C# Windows application project and add collections of ToolStripMenuItems and ToolStripButtons (MenuItems and ToolBarButtons for VS2003) to the main form. Add MdiFormsWndManager.dll to your toolbox. Then, drag UmlEditorComponent from the toolbox to your main form. Now, ToolStripMenuItems and ToolStripButtons have become the CommandName extended properties, where you can set the suitable command for the connected UmlEditorComponent from the listbox as shown in the picture. Simply compile and run the application:

WPF Page Demo

Create a WinFX Windows application. Add these references - MfcAppAdapter.dll, WpfPageWndManager.dll, and WindowsFormsIntegration.dll. If you use the VS2003 version of the MfcAppAdapter.dll, add MfcHost1Adapter.dll also.

Create a page instead of the window in the picture. We have a simple FlowDocument with AS:UMLEditorHost and FrameworkElements. The FileSource property of the AS:UMLEditorHost defines the path to the UML diagram which has to be added to the document. For the SinglePageViewer zoom support, the property Zoom of the AS:UMLEditorHost is bound with SinglePageViewer.Zoom in the SinglePageViewer resource. Simply compile and run the application:

XML
<!--WPF Page Demo-->
<?Mapping XmlNamespace="AS" ClrNamespace="AS.MfcHost2" 
                            Assembly="WpfPageWndManager"?>
<Page xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
      xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
      xmlns:AS="AS" Width="800" Height="540">
    <SinglePageViewer Name="ZoomSource">
        <!--Bind AS:UMLEditorHost.Zoom and SinglePageViewer.Zoom 
            for the Zoom support-->
        <SinglePageViewer.Resources>
            <Style TargetType="{x:Type AS:UMLEditorHost}">
                 <Setter Property="Zoom" 
                         Value="{Binding ElementName=ZoomSource, 
                                        Path=Zoom, Mode=OneWay}"/>
            </Style>
        </SinglePageViewer.Resources>
        <FlowDocument TextAlignment="Left" Background="AliceBlue">
            <Paragraph>
                <Bold>Hosting MFC application in WPF Page</Bold>
            </Paragraph>
            <Paragraph KeepTogether="True">
                Document - <Italic>UMLEDI1.uml</Italic>
                <!--use hosted MFC application as FrameworkElement-->
                <AS:UMLEditorHost FileSource="../../UMLEDI1.uml"/>
            </Paragraph>
            <Paragraph KeepTogether="True">
                Document - <Italic>UMLEDI2.uml</Italic>
                <!--use hosted MFC application as FrameworkElement-->
                <AS:UMLEditorHost FileSource="../../UMLEDI2.uml"/>
            </Paragraph>
        </FlowDocument>
    </SinglePageViewer>
</Page>

Deployment and Installation

See "Deployment and Installation" from "MfcAdapter - not OLE .Net/WPF container of MFC applications".

  1. Hosted simple wizards MFC/Dialog application:

    Programming Guide with Source Code for VS 2005 and VS2010

    I offer the programming guide and source code of a simple wizards MFC/Dialog application DialogWiz as a starting point for your MFC application hosting. For simplicity, I removed the hosting MFC views support from the MfcAppAdapter component.

    Simply change your MFC application as described in the programming guide. Using MfcAppAdapter.OnCmdMsg(), you can send commands from a .NET Form to your MFC command handlers and show your dialogs.

    Code is compatible with VS 2003-VS 2012, you need only update the project settings.

  2. MfcAdapter VS2003\VS2005 with hosted UMLEditor:

    Source Code

    After we unzip, we have two solutions - UMLEditor2003 (VS2003) and UMLEditor2005 (VS2005). The legacy MFC source code (directory Legacy) is common for both solutions. Please don't convert the UMLEditor2003 solution to VS2005 or UMLEditor2005 solution to VS2010 - you need some source code changes in this case.

    The UMLEditor2003 solution contains the MfcAppAdapter, MdiFormsWndManager, and the MdiFormsDemo projects. Set the MdiFormsDemo as the startup project, and then build and run.

    The UMLEditor2005 solution has additional WpfPageWndManager, WpfPageDemo, and MfcHost1Adapter projects. WpfPageDemo and WpfPageWndManager are compatible with the WinFX Runtime Components 3.0 - Beta 2 (Jan CTP assemblies, version 6.0.5070.0).

    Check the reference to WindowsFormsIntegration.dll (normally it's ..\Program Files\Reference Assemblies\Microsoft\WPF\v3.0\WindowsFormsIntegration.dll). Set MdiFormsDemo or WpfPageDemo as the startup project, and then build and run.

    If you want to use the VS2003 version of the MfcAppAdapter with WPF, add a reference to the VS2003 MfcAppAdapter.dll in MfcHost1Adapter. Build MfcHost1Adapter, and add references to the VS2003 MfcAppAdapter.dll and MfcHost1Adapter.dll in WpfPageWndManager and WpfPageDemo, instead of the VS2005 MfcAppAdapter.dll. Rebuild and run.

    I tried to keep the encapsulated MFC code as short as possible, so I have not fixed some of the native problems of this code, for example, an unhandled exception when opening a recent file that does not exist.

  3. MfcAdapter 3.0. Demo VS2019:

MfcAdapter 3.0 does not need a special installation. After extraction from the archive, You will have source code and executable programs of 5 MFC applications:

  • "DialogWiz" - Simple MFC application without views (only dialogs) created with VS 2005 wizard
  • "DialogEditor" - DIY vector and dialog editor by Johan Rosengren (http://www.codeproject.com/)
  • "UMLEditor" - Revisiting the vector editor by Johan Rosengren (http://www.codeproject.com/)
  • "Mesh" - A small VRML viewer using OpenGL and MFC (http://www.codeproject.com/)
  • "SimpleCtrl" - MFC simple of CLayoutView, CView

Hosted in WinForms:

  • FormsDemo_UmlEditor.exe - Container with hosted MFC applications "UmlEditor" and "DialogEditor"
  • MdiFormsDemo.exe - Full version of the hosted "UmlEditor"
  • SdiFormsDemo.exe - Full version of the hosted "DialogEditor"
  • FormDemo_SimpleCtrl.exe - Simple test for the CScrollView, CListCtrl
  • FormsDemo_DialogWiz.exe - hosted "DialogWiz"

Hosted in WPF:

  • WpfDemo_UmlEditor.exe - Container with hosted MFC applications "UmlEditor" and "DialogEditor"
  • WpfPageDemo_UmlEditor.exe - WPF FlowDocument. Container with hosted MFC applications "UmlEditor" and "DialogEditor"
  • WpfPageDemo_WrlViewer.exe - hosted VRML viewer

Simply run the necessary application.

Source Code

NOTE: MfcAdapter 3.0 demo supports only one configuration: x86 Debug, Unicode, .NET 4.0-4.7

After unzip, we have one VS 2019 solution - \Samples\Src\Samples.sln with all demo projects. All projects build outputs will be copied in the common execute directory \Samples\Src\x86\Debug with necessary MfcAdapter DLLs.

By reference error, simply repeat a build.

I tried to keep the encapsulated MFC code as short as possible, so I have not fixed some of the native problems of this code, for example, compiler warnings, an unhandled exception when opening a recent file that does not exist and others.

Host Your MFC Application

For hosting your MFC application, use - x86 Debug Unicode .NET 4.0-4.7 version of MfcAdapter:

\Debug:

  • MfcAppAdapter.dll, ViewControl.dll, ViewFrameworkElement.dll

\Include:

  • HostApp.h, LayoutView.h

Help:

  • MfcAdapterAPI.chm, ProgrammingGuide.htm

Points of Interest

I assume that this article's solution - the MFC extension DLL with the CWinApp derived class - allows you to host MFC applications in any (not only managed WinForms/WPF) Win32 no MFC Framework by changing the MDI classes and using a suitable wrapper. Otherwise, this may be a useful sample for Visual Studio designer programming in a WinForms component (UmlEditorComponent) as it supports setting of the Host in the designer generated code.

History

  • 30th December, 2005: Initial post
  • 31st May, 2022: Article updated

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here