Mix and Match

Integrate Windows Forms Into Your MFC Applications Through C++ Interop

Marcus Heege

This article discusses:
  • How C++ interop works
  • When to use C++ interop in your projects
  • Using Windows Forms controls in MFC apps
  • Moving from Windows Forms to WPF views
This article uses the following technologies:
C++, MFC, .NET Framework, Windows Presentation Foundation

Contents

C++ Interop Is Your Friend
When to Use C++ Interop
Windows Forms Controls in MFC Apps
Handling Control Events
Controls as Dialog Boxes
Windows Forms Views
Bridges to Avalon
Conclusion

Although the Microsoft .NET Framework was first made public almost five years ago, and version 2.0 has just recently been released, many C++ applications are still pure unmanaged code. But interest in the .NET Framework is growing rapidly among C++ developers. And for good reason: many future Windows® APIs will be based on the .NET Framework. This is certainly true for the WinFX® components Windows Presentation Foundation, Windows Communication Foundation, and Windows Workflow Foundation.

Still, many C++ developers want to use native APIs instead of wrappers around existing APIs. Wrappers are often assumed to be buggy, slow, and less flexible. Apart from that, it would be really difficult to map huge APIs like WinFX so that they can be used from native code.

In most cases, extending C++ applications with features from the .NET Framework is easier than most developers believe. Visual C++® includes a feature called C++ interop, sometimes referred to as IJW, or "It Just Works." You can use this feature to seamlessly integrate .NET-based code into your existing C++ source code.

C++ interop is based on two major features. First, you can compile your existing C++ code to Microsoft® intermediate language (MSIL) by using the /clr switch of the C++ compiler. Your code can then use .NET features like garbage collection, sandboxed security, and types from the huge .NET Framework base class library.

The other feature is called mixed-mode, and it is equally important. It refers to the combination of both managed and unmanaged code. When compiling C++ code to MSIL, the compiler creates managed object files containing MSIL code instead of the normal, unmanaged object files containing native assembly code. The linker is able to take both managed and unmanaged object files as input. When the linker detects at least one managed input, it creates a .NET assembly, which can contain both managed code and unmanaged code. That"s why it is called mixed-mode (see Figure 1). This feature is extremely important for performance optimizations because it allows you to drastically reduce the number of transitions between the managed and unmanaged spaces.

Figure 1 MTS

Figure 1** MTS **

C++ Interop Is Your Friend

My experience has shown that C++ interop is very reliable, and while it has some limitations, it"s a worthwhile addition to your developer toolbox. To understand how this feature is possible, you should be aware that C++ interop was a design feature of the .NET Framework. The Common Language Infrastructure (CLI), which is the base specification of the .NET Framework, has been designed to support this feature. In fact, there are many aspects of the CLI that were defined simply because C++ interop required them.

For example, managed metadata supports global functions. Yet C# and most other .NET-targeted languages require all methods to exist within the scope of a class. This feature exists because C++ code that should be mapped to .NET-based code can have global functions, and to map this C++ code to managed code, metadata needs an equivalent concept.

The MSIL instruction set has also been designed with C++ interop in mind. To map C++ code to MSIL code, MSIL must support all common data manipulation operations like the typical Boolean, arithmetic, and floating point operations. MSIL also supports pointer-based access to virtual memory. This feature is key to using native types in managed code. If your C++ code accesses a field of a native object, the C++/CLI compiler can emit MSIL code that pushes the address of the native object on the stack, adds the field"s offset to it, and uses the resulting address to access the field. All of this is possible because the MSIL instruction set has operations that can access virtual memory through addresses.

Another C++-specific feature of the IL language is the CALLI instruction. It can be used to call to native functions through function pointers. This feature is used to call virtual functions of native types. It is important because COM interfaces are native types with virtual functions. This method of COM interoperability has many advantages over the alternative COM interoperability techniques used by other languages.

Finally it"s worth mentioning the design impact that is most obvious to all developers who use .NET: the file format for components. While Java has defined its own file format (the .class file), the .NET Framework uses the standard PE file format. Therefore, .NET-based components have the comfortable file extensions DLL and EXE. The fact that PE files are still used is essential to supporting mixed-mode assemblies.

When to Use C++ Interop

To evaluate whether C++ interop is the way to go for your projects, you should also know about the impact that this feature has on the way you build your projects—compiler settings, for starters—as well as the impact on the output of this build process.

If you compile with the /clr option, your code implicitly depends on the multithreaded DLL version of the C runtime libraries (CRT). This means that all files in your project—even those compiled without /clr—must use the multithreaded DLL version of the CRT. This is also important for MFC projects. The static MFC library depends on the static CRT library, which is incompatible with /clr compilation. Therefore you have to make sure that your MFC projects link the DLL version of MFC.

There can also be issues regarding execution time. Initializing the CLR takes a certain amount of time, and JIT compilation has some overhead, too. Therefore you may experience a somewhat longer startup time. However, for most projects this should not be a big issue.

Method calls with managed-to-unmanaged transitions will take longer than normal method calls. As I have mentioned already, you can significantly reduce the number of transitions between managed and unmanaged code by deciding which specific parts of your app should be compiled to managed code. The calling convention __clrcall is another important optimization feature that can significantly reduce the number of transitions, but this feature is beyond the scope of this article.

In most cases, the just-in-time (JIT)-compiled code itself is not the cause of significant performance penalties. In fact, there are some optimizations that a JIT compiler can do that are not possible for the C++ compiler and the linker. For example, a JIT compiler can optimize code for the processor architecture on the target machine. In contrast to the C++ compiler, a JIT compiler can also provide cross-component inlining. This can be a very effective optimization, because code compiled with cross-component inlining can then be exposed to further optimizations.

Apart from the issues I"ve mentioned here, there are some other areas that will be impacted that I will not cover in detail here. Most of these can be easily resolved by modifying compiler and linker settings in the right way. The optimal compiler settings are not always straightforward. Some of these settings turn on features that are mandatory for C++ interop compilation, while some other settings may turn off features that are incompatible with /clr compilation or that are solved for Microsoft intermediate language in a different manner.

Windows Forms Controls in MFC Apps

All source files that are compiled with the /clr option can use types from the .NET Framework base class library. You can use most parts of the base class library as you have always used them. Some cases require special attention. Integrating Windows Forms controls in MFC applications is such an example.

You cannot pass System::Windows::Forms::Control^ when a CWnd* is expected. However, the Windows Forms API and MFC share a common heritage: good old User32.dll. Both APIs provide some backdoors to the HWNDs. While the MFC CWnd class provides a conversion operator to HWND, the Windows Forms control class implements the interface IWin32Window from the namespace System::Windows::Forms to expose the window handle:

public interface IWin32Window {
  property IntPtr Handle {
    IntPtr get ();
  }
};

However, getting a window handle from a window object is often not enough. MFC provides some classes with support for dealing with Windows Forms controls in different scenarios. These classes are declared in afxwinforms.h.

The most important of these classes is CWinFormsControl, which is defined in the namespace Microsoft::VisualC::MFC. This class allows you to host a Windows Forms control in an MFC Dialog or CDialog-derived class. It is a templated class with one template argument, used to pass the type of the Windows Forms control you want to host. The type can be either the concrete type you want to instantiate later or a base class of the concrete type. The CWinFormsControl template is quite simple. It has only two interesting features: it inherits CWnd and it provides several overloads of a self-explaining method, CreateManagedControl.

If you want to host a Windows Forms control in an MFC Dialog or CDialog-derived class, define a member variable of type CWinFormsControl<TWinFormsCtrl> in your class and pick one of the overloads of CreateManagedControl. If you implement a CDialog-derived class, a good candidate for creating the managed control is OnInitDialog. For most other cases, OnCreate (the message handler for WM_CREATE) is the best option. One of the overloads is especially interesting for dialog classes. This overload expects you to add a static placeholder control to a dialog resource. Place the static control so that it has the position and size that the Windows Forms control should have and give it a unique ID, as shown in Figure 4. To instantiate the hosted control, call CreateManagedControl as shown in Figure 2. As an alternative, you can even use a special variant of Dialog Data Exchange (DDX) like in Figure 3.

Figure 3 Hosting a Control with Dialog Data Exchange

using Microsoft::VisualC::MFC;
using System::Windows::Forms;

class CMyDialog : public CDialog
{
public:
    CMyDialog(CWnd* pParent = NULL): CDialog(CMyDialog::IDD, pParent) {}

    enum { IDD = IDD_MYDIALOG };

private:
    CWinFormsControl<Button> m_wfBtn;

protected:
    virtual void DoDataExchange(CDataExchange* pDX) {
        DDX_ManagedControl(pDX, IDC_WFBTN, this->m_wfBtn);
    }
};

Figure 2 Creating a Hosted Control

using Microsoft::VisualC::MFC;
using System::Windows::Forms;

class CMyDialog : public CDialog
{
public:
    CMyDialog() : CDialog(CMyDialog::IDD, NULL) {}

    enum { IDD = IDD_MYDIALOG };

private:
    CWinFormsControl<Button> m_wfBtn;

public:
    BOOL OnInitDialog() {
        CDialog::OnInitDialog();

        return this->m_wfBtn.CreateManagedControl(
            WS_VISIBLE | WS_CHILD, IDC_WFBTN, this));
    }
}

Figure 4 Using an ActiveX Control

Figure 4** Using an ActiveX Control **

Notice that this way of hosting Windows Forms controls in MFC applications uses yet another common heritage of Windows Forms and MFC: ActiveX® controls. As you can see in the list in Figure 5, System::Windows::Forms::Control implements many interfaces that should be familiar to OLE and ActiveX control veterans. Inside of CreateManagedControl, the Windows Forms control is instantiated via the gcnew operator and in-place activated just like a normal ActiveX control.

Figure 5 Interfaces

System::Windows::Forms::Control
IOleObject
IOleControl
IOleInPlaceObject
IOleInPlaceActiveObject
IOleWindow
IOleViewObject
IOleViewObject2
IPersist
IPersistStreamInit
IPersistPropertyBag
IPersistStorage
IQuickActivate

Also notice that CWinFormsControl overloads the -> operator so that the hosted control is returned. This allows you to initialize the button"s Text property through simple assignement:

m_wfBtn.Text = "Click me!";

This handling is not as clean as modifying a control"s properties in the properties window, but if you can live with the limited designer support that currently exists, the world of Windows Forms controls is open to your trusty old MFC dialog boxes.

Handling Control Events

There is another aspect that should be discussed here. So far the button"s click event is not handled. Since you have a Windows Forms control that is hosted like an ActiveX control, there are two options for event handling: connection points provided by the ActiveX control and event members of the Windows Forms control. The connection point alternative would only be successful if the Windows Forms control was prepared to support COM-based events. For most Windows Forms controls this is not the case, so handling events the .NET way is the only option here. Events of managed classes are a special kind of type member that allows the registration and deregistration of event handler delegates. The following line shows how to register the button"s click event:

m_wfBtn.Click += <an event handler delegate for the click event>

To provide such a delegate, a function with the following syntax is required:

void EventHandler(Object^ sender, EventArgs^ e);

Unfortunately, it is not enough to implement such a method in the CDialog-derived class. Delegate target functions must be member functions of managed classes, and the CDialog derived class is a native one. Visual C++ provides a header file, \Msclr\Event.h, that gives you a generic solution to this problem. The definitions in this header can be used whenever you want to handle a .NET event with a method of a native C++ class. As I will discuss later, you can also use this header when integrating the Visuals features of Windows Presentation Foundation. Since this header is helpful in so many different scenarios, it makes sense to discuss the solution it provides in depth.

To handle the events of the Windows Forms control, you could implement a managed class containing the message handler. C++ interop allows you to do this in an elegant way because this managed class can be nested into the CDialog-derived class:

class CMyDialog : public CDialog {
    ref class nested_managed_class {
        void OnWFBtnClick(Object^ sender, EventArgs^ e) { 
            // handle the button"s click event here 
        }
    };

    // other members 
};

The most convenient way to handle the button"s click event is to forward this method call to a member function of the CDialog-derived class. The method that finally handles the click event can easily access other members of the derived class, just in the way you expect event handlers to work. The managed class containing the delegate target ends up being a simple proxy type. Although such a proxy type and its proxy event handlers can be created with macros defined in Event.h, let"s first have a look at a manual definition (see Figure 6).

Figure 6 CMyDialog

class CMyDialog : public CDialog
{
public:
    ref class delegate_proxy_type;
    gcroot<delegate_proxy_type^> m_gc_managed_native_delegate_proxy;
    delegate_proxy_type^ get_proxy(CMyDialog* pNativeTarget) {
        if((delegate_proxy_type^)m_gc_managed_native_delegate_proxy == 
            nullptr)
        {
            m_gc_managed_native_delegate_proxy = 
                gcnew delegate_proxy_type(pNativeTarget);
        }
        return (delegate_proxy_type^)m_gc_managed_native_delegate_proxy; 
    }

    ref class delegate_proxy_type {
        CMyDialog* m_p_native_target;
    public:
        delegate_proxy_type(CMyDialog* pNativeTarget) :
            m_p_native_target(pNativeTarget) {}
        void OnWFBtnClick(Object^ sender, EventArgs^ e) {
            this->m_p_native_target->OnWFBtnClick(sender, e);
        }
    };

    void OnWFBtnClick(Object^ sender, EventArgs^ e) {
        this->m_wfBtn->Text = "Thanks for clicking";
    }

    // other members 
};

As you can see, the delegate proxy has a constructor that takes a CMyDialog* argument. Such a constructor is necessary to forward method calls to the CDialog-derived class. This delegate proxy type could be used for all event handlers for all Windows Forms controls of the dialog class. It is not possible to add a member variable of the delegate_proxy_type to the derived class, since native classes cannot have handles to garbage-collected objects as fields. Using the gcroot template, you can bypass this restriction here. Therefore, a member variable of type gcroot<delegate_proxy_type> is necessary. However, there is still a remaining question: how could the gcrooted reference to the delegate proxy type be initialized properly? This can be handled by a separate method that is called whenever a handle to delegate_proxy_type is needed.

With the code from Figure 6, a delegate can easily be registered as in the following sample:

virtual BOOL OnInitDialog() {
    CDialog::OnInitDialog();

    this->m_wfBtn->Click += 
        gcnew System::EventHandler(get_proxy(this), 
        &delegate_proxy_type::OnWFBtnClick);

    return TRUE;
}

Writing all this code would be a huge amount of work for just handling events of a Windows Forms control in a CDialog-derived class. Event.h contains macros and templates to achieve the same and even more with many fewer lines, as shown in Figure 7.

Figure 7 Simplified CMyDialog

class CMyDialog : public CDialog
{
public:
    CMyDialog(CWnd* pParent = NULL): CDialog(CMyDialog::IDD, pParent) {}

    enum { IDD = IDD_MYDIALOG };

private:
    CWinFormsControl<Button> m_wfBtn;

protected:
    virtual void DoDataExchange(CDataExchange* pDX) {
        DDX_ManagedControl(pDX, IDC_WFBTN, this->m_wfBtn);
    }

public:
    BEGIN_DELEGATE_MAP(CMyDialog)
        EVENT_DELEGATE_ENTRY(OnWFBtnClick, Object^, EventArgs^)
    END_DELEGATE_MAP()

    void OnWFBtnClick(Object^ sender, EventArgs^ e) {
        this->m_wfBtn->Text = "Thanks for clicking";
    }

public:
    virtual BOOL OnInitDialog(){
        CDialog::OnInitDialog();

        this->m_wfBtn->Click += MAKE_DELEGATE(System::EventHandler, 
            OnWFBtnClick);
        return TRUE;
    }
};

Every new version of MFC brings at least one new map. As you can see, the current version introduces the delegate map. The macro BEGIN_DELEGATE_MAP defines the delegate_proxy_type that contains the delegate targets. Every EVENT_DELEGATE_ENTRY line adds such a target method to the managed class. END_DELEGATE_MAP just finishes the managed class. Finally, MAKE_DELEGATE instantiates a delegate referring to the target method of delegate_proxy_type. In fact, the macros do even more than what I have just discussed. They are even prepared for scenarios where the managed class fires events to be sent to a native object that no longer exists.

Controls as Dialog Boxes

Another simple yet effective helper is the template CWinFormsDialog. You can use it to show a Windows Forms control as a modal or modeless dialog box. This class is fairly simple. It extends CDialog to provide the standard behavior of MFC dialog boxes. Apart from that, it uses a CWinFormsControl member variable to host the Windows Forms control, initializes the dialog box"s caption to the value of the hosted control"s Text property, sets the dialog"s initial size so that the control perfectly fits into it, and ensures that the size of the hosted control is adjusted whenever the size of the parent window changes.

This one-liner instantiates a temporary dialog box object hosting YourWinFormsDlgControl and shows it as a modal dialog box:

CWinFormsDialog<YourWinFormsDlgControl>().DoModal();

You should avoid this, however, since there"s no way to get the dialog"s return value using DoModal. For more realistic scenarios, consider deriving your own class from CWinFormsDialog<YourWinFormsDlgControl>. This allows you to override OnInitDialog to set properties of the nested Windows Forms control or to handle events of the hosted control. The macros from Event.h that I described earlier can be useful in this case, too. For more information, see msdn2.microsoft.com/ahdd1h97.aspx.

Windows Forms Views

Last, but not least, you can host Windows Forms controls as views. CWinFormsView is the little helper you need for this scenario. Unlike CWinFormsDialog, it is not a template class. Nevertheless, there are significant similarities. Both leave the actual hosting of the control up to the CWinFormsControl template and both handle the resizing of the hosted control.

To host a Windows Forms control as an MFC dialog box, you have to inherit your view class from CWinFormsView. The following code shows the declaration of a simple view class that is based on CWinFormsView:

class CMyWinFormsBasedView : public CWinFormsView
{
    CMyWinFormsBasedView ();
    DECLARE_DYNCREATE(CMyWinFormsBasedView)
};

If your view class was generated by a wizard, you should make sure to switch to CWinFormsView not only in the base class list of your view"s class declaration, but also in its implementation. This may cause you to change parameters of macros like IMPLEMENT_DYNAMIC and BEGIN_MESSAGE_MAP. To instantiate the hosted Windows Forms control, the constructor of CWinFormsView (your view"s base class) gets a handle to the System::Type object of the Windows Forms control. The implementation of the view class just shown can be very simple, too:

IMPLEMENT_DYNCREATE(CMyWinFormsBasedView, CWinFormsView)

CMyWinFormsBasedView:: CMyWinFormsBasedView ()
    : CWinFormsView(WinFormsViewControl::typeid) {}

At the first view, this code looks much too simplistic for a real-life scenario, but in many cases this is really all you need. The view class acts as a simple proxy for the real implementation that resides in the view"s control.

Many view implementations override virtual functions of the MFC CView base class. To keep the native view class acting as a simple proxy, you could override these methods in the native view class and forward the calls to equivalent methods in the view"s Windows Forms control. For the three most important overrides of CView, such forwarding has already been implemented in the base class CWinFormsView. These three methods are OnInitialUpdate, OnUpdate, and OnActivateView. The forwarding is based on a managed interface named Microsoft::VisualC::MFC::IView that is defined in the assembly Mfcmifc80.dll. To override these methods, implement this interface in the view"s Windows Forms control class as shown in Figure 8.

Figure 8 Overriding IView

#using <mfcmifc80.dll>
using Microsoft::VisualC::MFC::IView;

public ref class MyWinFormsViewControl : 
    public System::Windows::Forms::UserControl, 
    public IView
{
    ...
protected: // IView implementation
    virtual void OnInitialUpdate() = IView::OnInitialUpdate {
        // implementation 
    }

    virtual void OnUpdate() = IView::OnUpdate {
        // implementation 
    }

    virtual void OnActivateView(bool activate) = IView::OnActivateView {
        // implementation 
    }
};

In addition to overriding virtual functions, views can also implement command handlers. Again, it would be possible to implement such a command handler in the native view class and forward to the Windows Forms control, but since this is a common scenario, MFC provides a more convenient solution. The Mfcmifc80.dll assembly contains some more managed helper types for this scenario. To receive command messages from MFC"s command-routing infrastructure, Windows Forms controls must implement the interface Microsoft::VisualC::MFC::ICommandTarget:

interface class ICommandTarget
{
    void Initialize(ICommandSource^ commandSource);
};

When a view that implements ICommandTarget is created, ICommandTarget::Initialize is called on that view and a handle to a new command source object is passed as an argument. This argument is of the type ICommandSource^. The object that the argument refers to is a container for handlers. These handlers should sound familiar to MFC developers: they are command handlers that are called when a command is issued and command UI handlers that can control whether a control"s UI is enabled, checked, or adorned with a radio button, and what text is displayed in the command"s UI.

To register a handler, methods like AddCommandHandler and AddCommandUIHandler can be called on the command source. These methods expect two arguments: an unsigned integer for the command ID, and a delegate that is used to pass the control"s handler function. Since command handlers and command UI handlers have different signatures, two different delegate types are defined in the namespace Microsoft::VisualC::MFC:

delegate void CommandHandler(unsigned int);
delegate void CommandUIHandler(unsigned int,
    Microsoft::VisualC::MFC::ICommandUI^);

Figure 9 shows how to register a command handler for the ID_EDIT_PASTE command.

Figure 9 Registering Command Handlers

public ref class MyWinFormsViewControl : 
    public System::Windows::Forms::UserControl, 
    public ICommandTarget
{
...
protected: // ICommandTarget implementation
    virtual void RegisterCmdHandlers(ICommandSource^ cmdSrc) = 
        ICommandTarget::Initialize
    {
        cmdSrc->AddCommandHandler(ID_EDIT_PASTE, 
            gcnew CommandHandler(this, &WinFormsView::OnEditPaste));
        cmdSrc->AddCommandUIHandler(ID_EDIT_PASTE, 
            gcnew CommandUIHandler(this, 
            &WinFormsView::OnUpdateEditPaste));

        // register further command handlers ...
    }

    void OnEditPaste(unsigned int) 
    {
        // implement command handler 
    }

    void OnUpdateEditPaste(unsigned int, ICommandUI^ cmdUI)
    {
        cmdUI->Enabled = ...; // your logic here
    }
};

When a view is supposed to handle a command, the delegate is used to call a member function of the view. The command"s ID is passed as an argument. If every command has its own private handler function, this argument is not important, but a single method can also be used as a handler for more than one command. AddCommand[UI]RangeHandler allows you to register one delegate for multiple commands.

The delegate for command UI handlers has an additional argument of type ICommandUI^. The handle that is passed using this parameter is a wrapper for the MFC CCmdUI class that allows you to control the appearance and usability of interface elements like menu items and toolbar buttons.

In addition to adding command handlers in ICommandSource::Initialize, it can also be useful to store the ICommandTarget handle in a member variable of the Windows Forms control. This gives you the option to add or remove further command handlers later, and you can even fire command events either synchronously or asynchronously through ICommandTarget::SendMessage or ICommandTarget::PostMessage.

Bridges to Avalon

Since Windows Presentation Foundation has not yet been released in final form, the current version of MFC does not have helper classes and templates for integrating its Visuals in CWnds and CDialogs. However, this does not mean that integrating Visuals in MFC applications is not possible. In fact, even without these little helper classes, it only takes a few lines of code to host a Visual in a CWnd or a CDialog.

The key feature for this integration comes from Windows Presentation Foundation itself. The assembly Windows.PresentationCore.dll provides a namespace System::Windows::Interop with very powerful class named HwndSource. This class provides the bridge between an HWND and a Visual. To the hosting USER32 window object, it looks like a child window with a normal HWND. Using its RootVisual property, you can switch into the new world of Windows Presentation Foundation. (Note that the information here is based on a prerelease version of Windows Presentation Foundation and may change with the final release.)

The HwndSource constructor expects a HwndSourceParameters value type as an argument. As you can see in Figure 10, this value type contains similar information that you would pass as arguments in a call to the Win32 function CreateWindowEx. Figure 11 shows how to use HwndSource in an OnInitDialog implementation.

Figure 11 Using HwndSource

BOOL CWPFDemoDialog::OnInitDialog()
{
    __super::OnInitDialog();
    
    CRect rectClient;
    this->GetClientRect(&rectClient);

    System::Windows::Interop::HwndSourceParameters hwsPars;
    hwsPars.ParentWindow = System::IntPtr(this->m_hWnd);
    hwsPars.WindowStyle = WS_CHILD | WS_VISIBLE;
    hwsPars.PositionX = 0;
    hwsPars.PositionY = 0;
    hwsPars.Width = rectClient.Width();
    hwsPars.Height = rectClient.Height();
    System::Windows::Interop::HwndSource^ hws;
    hws = gcnew System::Windows::Interop::HwndSource(hwsPars);

    using System::Windows::Controls::MonthCalendar;
    MonthCalendar^ mc = gcnew MonthCalendar;    
    hws->RootVisual = mc;

    return TRUE;
}

Figure 10 CreateWindowEx and HwndSourceParameters

CreateWindowEx Arguments HwndSourceParameters Properties
LPCTSTR lpClassName N/A
N/A WindowClassStyle
LPCTSTR lpWindowName WindowName
DWORD dwStyle WindowStyle
DWORD dwExStyle ExtendedWindowStyle
int x PositionX
int y PositionY
int nWidth Width
int nHeight Height
HWND hWndParent ParentWindow
HMENU hMenu N/A
HINSTANCE hInstance N/A

Conclusion

Using C++ interop, managed code can be seamlessly integrated into existing C++ sources. The MFC support for Windows Forms can be seen as strong evidence that this integration is an intended and supported feature of Visual C++. To simplify this integration, several helper classes and templates exist. This integration layer is neither complex nor difficult to use.

One thing that is missing is a seamless integration of these features into the wizards of Visual Studio. In most scenarios, this is not a big problem since much of the implementation will be left to the hosted Windows Forms control, which has great designer support in Visual Studio. When Windows Presentation Foundation ships, there"s a high probability that equivalent support for integrating Visuals into MFC applications will be provided. Even without such support, though, it is easy to host Visuals in MFC apps.

Marcus Heege works as a course author and trainer for DevelopMentor and provides consulting for different IT companies. He regularly posts his thoughts about C++/CLI and .NET in his blog at www.heege.net.