Leveraging The .NET Framework Inside An MFC Application

For those of us entering the world of .NET through the eyes of C# and VB.NET, our life is somewhat uncomplicated.  We are safely tucked away inside the .NET environment using managed code.  C# and VB.NET were designed specifically to navigate through the managed environment.  We can access the .NET libraries easily, our code looks uncluttered (if it's well designed), our code compiles in the blink of an eye, and we can leverage all the nice C# tools on the market (such as resharper) to make are coding even easier than it already is.

What about those few of us that are sitting pretty with those big Visual C++ / MFC applications?  You know, the ones that have precompiled headers that supposedly make our compile times faster.  Yeah, you programmers know who you are, and I suspect there are a good majority of you.  If you are still not sure you are that person,  please take a few moments to answer the following simple quiz:

1.  Do you find yourself looking for memory leaks on a daily basis?

2.  Do you occasionally see a GPF (General Protection Fault) when running your application?

3.  Are you busy debugging through 6 layers of inheritance CDocument, COleDocument, CMyOleDocument and CMyGrandmothersOleDocument?

4. Are COM reference counts getting you down?

5.  Are you still trying to figure out what is the real difference between a pointer, a reference, and a stack variable?

6. Are you still debating whether you should have made your application SDI, MDI, or just a plain old dialog?

7.  Are you still thinking of collections in terms of STL (Standard Template Library)?

8.  Does the abbreviation DDX actually mean something to you?

9.  Are you trying to figure out which of the dozen character formats (wchar, BSTR, CString, char, tchar) you are supposed to use?

10.  Are you tired of typing a header declaration for every implementation you write?

11.  Do you still find yourself writing #ifdef, or #pragma once at the top of every header file?

12.  Are you stuck in an interdependency nightmare between your headers and wonder if you should use some more forward class declarations?

If you answered yes to most of or all of these questions, you've been programming with MFC for too long, and it's time to start migrating to .NET.  Fortunately this does not have to be a drastic change, but can be done gradually, because Microsoft had the foresight of building Visual C++ into the .NET framework.   In fact Visual C++ is the only compiler you can use to mix managed and unmanaged code directly in the same files.  You can have a pointer on line 16, and a managed reference on line 17.  You can compile, build, debug, and run your application with mixed managed and unmanaged code, all living happily together.  You can even bring up .NET Windows Forms and Dialogs from inside your MFC application.  In the discussion to follow we will show you how:

Steps to Migrating to using .NET

The first step in migrating to .NET is to get your existing MFC application to compile in Visual Studio.NET 2005.  I advise taking baby steps when migrating, because too many steps at once will lead you to trying to guess at wrong causes for hurdles you'll need to overcome.  The Visual Studio.NET 2005 Framework will attempt to convert your existing project file (if it is in Visual Studio 6.0) to the current Visual Studio.  There may be some errors in your compilation that you didn't have in Visual Studio 6.0.  The reason for the errors is that the Visual Studio .NET compiler is very strict in its rule checking.  You may need to alter some of your code to get the compile to actually work. 

Once you've gotten your unmanaged project to compile under Visual Studio.NET, it's time to add the CLR option into your project through the project properties.  To get to the CLR option, Right-Click on your project, and choose the Properties menu item.  Then click the General node under Configuration Properties shown in figure 1.  Choose the first CLR option: Common Language Runtime Support (/clr).

9.jpg

Figure 1 - Adding Common Language Runtime (CLR) Support to your MFC Application

Now you are ready to use .NET in your application, but not so fast!  First make sure you can compile your existing application.  You may find that some of the class types in .NET conflict with your existing class types.  (You may even find that some of the platform sdk conflicts with the .NET class types.)  For example, if you have a class in your application called System, you have to rename it, because the clr uses System as its root namespace.  Also you may find that some of your variable names conflict with compiler keywords, such as a variable name generic.  Once you sort through these conflicts, and have gotten to the link, you may also find problems.  I found that sometimes method names in Visual C++ 6.0 are not mangled the same way they were with the CLR on as they were off.  So I found, on some occasions, you may have to compile libraries included in your application with the /clr option on as well.

Once you have gotten through your link, you'll need to run your application.  Visual Studio .NET 2005 has a debugging agent called the Managed Debugging Assistant.  If there is a conflict in how you call managed code from the unmanaged application, an error will be triggered through this agent.  Some of the errors will point you to places where you may need to adjust your code.  Some are false alerts, and may need to be turned off.  You can find the options for the Managed Debugging Assistant under the Debug -->Exceptions menu.

Adding Managed Code

To access an unmanaged type in C++ (the types you are already accustomed with), you construct the variable on the stack or use a new to create the variable on the heap.

e.g.     CString x;  // stack
           CString *x  = new CString("hello");   // pointer on the heap

In managed code, you have to construct the variable so it ends up being managed by the garbage collector.  The way to construct a managed variable is by using gcnew and referencing the variable with a handle:

e.g.    System::String^  myString = gcnew System::String("hello");

or

using namespace System;

...
String^  myString = gcnew String("hello C# Corner");

If you are curious about how this handle different than a pointer, check out this MSDN blog.  Because you constructed the .NET System::String inside the auspices of the CLR, you don't need to worry about deleting it out of memory.  It will automatically get garbage collected when the runtime decides to clean it up.  Although declaring a managed type in C++ is a little more painful than it is in C#, you still get the benefit of the .NET framework.  For instance, you now have all the wonderful methods of the immutable String class at your disposal:

String^  firstTwoCharacters = myString->Substring(0,2);

Notice we treat the handle to the string as a pointer to utilize the string's methods and properties.  To access a static method, use the :: as you would in C++.

e.g.        String indexIndicator = String::Format("Next Index = {0}", i);

Declaring Managed Classes

If you wish to create a managed class, you need to create it slightly differently then you did in the unmanaged world.  Below is a typical managed class declaration:

Listing 1 - Managed Class Declaration

#pragma once

class System::Data::OleDb::OleDbConnection;

namespace Reports
{
  public ref class Consumer
 {
    private:
    System::Data::OleDb::OleDbConnection^ _conn;
    int  _consumerID;

    public:

     Consumer (void) 
       {
            _consumerID = 0;
       }

       void CalculateConsumerSurplus();   
  };

}

Notice the ref keyword inserted between the public accessor and the class keyword.  The ref keyword indicates we are using a managed class and that any instance created with this class will be garbage collected.  Below is an example of creating the Consumer class:

Consumer ^  theConsumer = gcnew Consumer();

Note that you can still do everything you've always done in a C++ header using the same rules.  You now have the additional capability of declaring managed handles inside your class.

Mixing Managed and Unmanaged in a class Header

Very often in a legacy application, you want to create classes that will merge your managed classes with your unmanaged classes.  This may involve passing unmanaged types to your managed class methods.  In order to allow access to unmanaged types in your managed class you may need to add the pragma below:

#pragma make_public(MyUnmanagedType)

Let's use the example in listing 1 to illustrate what we mean.  Say we have an unmanaged type IAddress *  that we need to set in the Consumer class.  Then our class would become the code shown in listing 2:

Listing 2 - Using an unmanaged type in a managed class declaration

#pragma once

#include "IAddress.h"
#pragma make_public(IAddress )   // pragma needs to be added to give access to the unmanaged IAddress type

class System::Data::OleDb::OleDbConnection;

namespace Reports
{
  public ref class Consumer
 {
    private:
    System::Data::OleDb::OleDbConnection^ _conn;
    int  _consumerID;

    public:

     Consumer (void) 
       {
            _consumerID = 0;
       }

       void CalculateConsumerSurplus();   

       void SetAddress (IAddress * address);
  };

}

Creating a .NET Windows Form in your Legacy MFC Code

We've gotten to the point where we can create and use simple .NET classes in our MFC application.  Now we want to do something a bit more sophisticated by launching a Windows Form inside the existing code.  The easiest way to create a Windows Form is to use Visual Studio.NET to do it for you.  Simply right click on your Project and choose Add-->New Item.  This will bring up the Add New Item Dialog.  Go into the UI section and choose Windows Form shown in figure 2.  Notice also in the same dialog that you can choose a User Control.  The mechanics in Visual Studio.NET will actually let you host a User Control inside your MFC View or Dialog.  For further information on how to do this, check out this article on MSDN by Microsoft:

Manage3.jpg

Figure 2 - Picking a Windows Form for your MFC App.

Now that we've selected our Windows, we can generate it inside our application and then use the Visual Studio Designer Toolbox to add .NET controls, ActiveX controls, or anything else the toolbox has to offer for our form.  You may not get all the wizard functionality you are used to in the C# Studio Environment.  For example, you can't drag a table from the server explorer to have it generate code for your data adapter.  But you do get a significant amount of functionality from the controls alone.  You get the MenuStrip, StatusStrip, WebBrowser, DataGrid, Print Controls, Split Container, TabControl, Timer, RichTextBox, SerialPort, FolderBrowserDialog, just to name a few (many of which were not available in off-the-shelf Visual Studio 6.0).  In our example, we added a DataGrid and a few text box controls.  The results are shown in figure 3:

Manage4.jpg

Figure 3 - Windows Form designed for an MFC Application 

When we look at the generated code, we notice that it puts all the code in the header file.  I guess Microsoft wanted to make the code generation as easy as possible and mirror the C# code so the resulting code is all in the .h file.  Editing your code here will prepare you old-time C++ developers for future C# development.  (Not sure what the impact on compile time is though, which is a regrettable consideration in C++ programming).  You can still go into the header file and add your implementation in the cpp file.  As you may have noticed, Visual Studio generates an empty cpp file for you.  Just be careful how you add the code and declarations. Remember that the designer in Visual Studio still needs to parse the generated code into visual components and changes to the header file may disrupt the parsing.

Running the Form

You can show the form just as you would in C#, using the Show method.  I've added the example of showing the form into the InitInstance function in the main application file to demonstrate (see listing 3).

#include "ConsumerDataForm.h"

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

//  The managed Windows Form is constructed and shown here
TestMFC::ConsumerDataForm^ form = gcnew TestMFC::ConsumerDataForm();
form->Show();

 

Conclusion

If you have an MFC application and you want to move to .NET, it's not as hard as it may seem.  You may go through some initial pain in bringing the CLR into your application and run into some compile, link,  and runtime conflicts and issues (e.g. I seemed to get LoaderLock Exceptions from the Managed Debugging Assistant once in a while and spent some time resolving them).  Once you've safely got your application running with the CLR, you can easily add the .NET classes and components to your application.  Anyway, although you may have valuable legacy code in your code base, don't be afraid to push forward in the gcnew world of .NET. 

 


Similar Articles