Taking advantage of modern .NET Framework features in legacy MFC applications has obvious productivity benefits on multiple levels. Fortunately, Microsoft has made it possible to integrate .NET Framework functionality in a native VC++ application without having to rewrite the entire codebase in a .NET language.

Common Language Runtime Support

There are multiple ways to integrate managed code in an unmanaged application (for detailed information check the documentation). In this article we will describe the particular approach we took with our application. Our MFC application consisted of a number of projects written in VC++, each producing a native DLL. Using managed functionality from within an unmanaged DLL requires the use of the VC++/CLI language, which is more-or-less a superset of the native VC++ and the managed C#/VB.NET languages (more information on the language at the official documentation). In short, VC++/CLI provides the tools for working both with managed and unmanaged memory as well as for employing libraries and features from both worlds.

In order to use VC++/CLI, Common Language Runtime Support (CLRS) must be enabled. Enabling CLRS can be done either at the project or at the file level. In our case, enabling CLRS at the project level produced a large number of errors, mostly related to legacy third-party libraries that were being included. Consequently, we decided to enable CLRS locally only for the files where managed code was going to be used. If you have a large MFC application with a good number of dependencies, judiciously enabling CLRS at the file level, only when necessary, seems to be the safest and more controlled way to go. Enabling CLRS can be done on the file property pages by right clicking the file in the solution explorer and clicking properties, as shown in the screenshot below.

Enabling CLR

By default, header file pre-compilation is enabled for the .cpp files in an MFC application (as per the official documentation).  Once CLRS is enabled, the native pre-compiled header file will not work anymore. If header pre-compilation is desirable for the C++/CLI file then a new pair of stdafxclr.cpp and stdafxclr.h files must be created. CLRS must be enabled for the .cpp file as shown in the screenshot above. Setting the new precompiled header can be done from the corresponding option on the file property pages, as shown below.

Precompiled CLR Headers

Annexing a pure managed C# project

If we only want to sprinkle a bit of .NET Framework functionality inside a native DLL, using C++/CLI as described in the previous section is sufficient. There are cases, however, where it makes sense to take advantage of the full power of the .NET Framework and integrate UI functionality from Windows Forms or WPF. We may do so by adding a pure C# project in our MFC solution and reference that project as shown in the screenshot below.

References

Having done that, we will be able to bring up Windows Forms/WPF windows or just instantiate UI-less classes implemented in the C# library. The actual instantiation of the external object needs to take place from a VC++/CLI (i.e. CLRS-enabled) intermediate class in the native DLL where the functionality will be needed.

An important point here is that, since the C# library is not able to access the C++ objects of the main application (i.e. the application document in the document/view architecture of MFC), the VC++/CLI intermediate class will need to do marshalling of the data to and from the C# objects. Specifically, data must be read from the VC++ classes, converted to managed datatypes and passed to the library classes. When the execution of the library code finishes, the execution results, if any, must be converted from managed to unmanaged datatypes for use in the core of the application.

There are many tricks to help for near-seamless interoperability of a managed library with the man MFC application. For example, it is possible to pass delegates from the native DLL to the managed library so that the C# code can essentially call native VC++ functions that could update data in the native realm. For this to be done, the managed library does not need to know anything about the structure of the native classes, as long as the signature of the function matches the managed delegate. This was only a high-level overview of how to integrate managed functionality in an MFC application. For more information please contact us.