Head-Spinning Interoperability between Managed and Native C++

Recently, at Tech Ed USA in Dallas and Tech Ed Europe in Barcelona, I delivered a talk on the various ways you can reuse old C++ code in new Managed C++ projects. For the next few columns, I'm going to present that information to you along with my conclusions about the direction you should go in your own reuse projects. My goal isn't so much to make your head spin around in confusion as to try to tame the confusion that naturally arises when you see how many different ways there are to reuse old C++ code in a new managed project. It can be intimidating to juggle all the options and try to make a decision.

Let's start by thinking about the kind of C++ code you are likely to have available to you. It might be business logic, proprietary algorithms, or some sort of third-party library. Maybe you have the source code for it—and maybe you don't. It might be packaged in many different ways:

As a static library: a .LIB file and accompanying .h files. You #include the .h files and link to the .lib file

As a dynamic library, a DLL, with .h files and possbly a .LIB file

As a COM component or ActiveX control

As source code, .cpp and .h files that compile to one of the above

In this series of columns, I'm going to assume you have the source code and can do whatever you like to it—make it a COM component, for example. I'll discuss the pros and cons of various approaches. If you don't have the source, or if it's already packaged in a particular way, then you will have less choice, but at least you'll know the situation in which you find yourself.

Millions of lines of tested, accurate, useful C++ code are out there in general use. This code is of tremendous value. You know how it works—heck, you know for sure that it does work. It's been tested and debugged, and everyone's trained on it. The firms that invested time and money in this code don't want to walk away from it. But they do want to move to the "new world" of managed code, the CLR, Web Services, and all the other new goodies of the .NET Framework. I'm here to tell you how you can have your cake and eat it too, by moving to the new world and reusing your legacy at the same time.

The Legacy

In this column, I'm going to set the stage by showing you the "legacy" application that I am going to reuse in a variety of different ways. I've chosen a really simple legacy so that the techniques themselves show through, rather than my actual underlying code. So, my legacy consists of a single class with a single method in it.

While the example is trivial, the techniques I am going to demonstrate work just as well on real examples. In my research for this work, I reused code that I last touched in 1994. It does exact arithmetic on integers of arbitrary length (using strings to hold the digits) and fractions (using two long integers.) The only parts of the library I had trouble with were the sections of 286 assembly code—I couldn't find an old assembler to deal with them and ended up replacing them with C++. The good news is that processors have come a long way in nine years and I didn't need to write parts of that library in assembler any more to get decent performance.

Just for the sake of completeness, I wrote a little unmanaged application that uses the "legacy library." It comes in handy when we compare the various reuse techniques later.

Because ArithmeticClass is an unmanaged class, and I'm writing an unmanaged application, there's no problem creating an instance of the class on the stack, and calling its methods with the . operator.

What Are Your Choices and Tradeoffs?

Let's say for the sake of argument that you've got a module written in unmanaged C++, consisting of a whole pile of classes, each class with a whole pile of methods. And now you're going to write a Managed C++ application (maybe a Windows Forms application because that technology is now available) that needs to reuse that code. You have a variety of choices available to you:

COM Interop. You can take your code and wrap it up into a COM Component, then call it through COM Interop from any managed language.

PInvoke. You can wrap your code into a DLL and call it through PInvoke from any managed language.

It Just Works. If your code is in a LIB (or a DLL with a companion LIB), you can just link to it from Managed C++.

It Just Works II. You can take your old legacy code, full of calls to ATL, MFC, STL, and heaven knows what else, and just compile it as managed code. I call this the XCopy port and you have to see it to believe it.

Mixing Managed and Unmanaged C++ in the Same EXE. This produces an assembly of managed code with native code inside it along with intermediate language.

Writing a Managed Wrapper. This exposes your unmanaged legacy to less-fortunate languages such as VB and C#—but watch out for the Mixed DLL problem.

In my next few columns, I'm going to show you each of these approaches, and discuss their pros and cons on matters such as performance, developer convenience, maintenance, and the like. But there's one issue—security—I want to talk to first.

Code Access Security and Verifiable Code

If you've read anything about Code Access Security, you know that the .NET Framework has a very granular set of permissions for applications: you can control whether an application can make a network connection, access a SQL Server, look at the user's environment variables, and so on. In general, you want your applications to be able to work with as few permissions as possible: Just because it works with Full Trust doesn't mean you should insist on Full Trust if there's a way to write it so it needs less than that.

Code Access Security includes a permission to access unmanaged code. So, you might think that there's a security consequence to the choice you make about how to get to your old code. Specifically, if you bring it over to managed code, as in my XCopy port, you should be able to get away with a lower permission set, right? Well actually, no.

By default, all Managed C++ applications demand the SkipVerification permission. And all means all—even the ones that are 100% Managed C++, compiling to intermediate language, no calls to anything unmanaged. SkipVerification is a very powerful permission, enabling the assembly to be loaded into the CLR without being verified. (As an example of how powerful it is, consider this: The Everything permission set doesn't include SkipVerification. You need Full Trust for that.)

The verification process checks to ensure that an assembly accesses only the memory locations it is authorized to access. Until Visual C++ .NET 2003, Managed C++ could not compile to verifiable code. Now, in this release, you can create verifiable code, but it's quite difficult, and I'm willing to bet noone will write a production Managed C++ application that will be verifiable. However, for the sake of completeness, I'll tell you what you have to do before you can settle for less than SkipVerification, meaning less than FullTrust.

No pointers to unmanaged data, e.g. char* or int*

No classes that are not garbage-collected

No pointer arithmetic

No static_cast<> downcasts, no reinterpret_cast<>

No It Just Works or #pragma unmanaged to unmanaged code (PInvoke COM Interop are okay, but the assembly will need permission to access unmanaged code)

And finally, you use a little utility called SetILOnly.exe to edit your executable headers to indicate it is verifiable code. Not that I think you'll ever get this far. Turn off the optimizer, don't use the C Runtime Library, no pointer arithmetic, no unmanaged classes? I'm presuming you're using C++ for a reason—and this laundry list of tasks to make yourself verifiable forces you to give up most of the advantages that led you to choose C++.

If you accept the fact you're not going to write verifiable code no matter how you reuse your legacy code in C++, you remain free to choose your reuse technique based on performance, convenience, maintenance, and similar criteria. That's what I'll be discussing in the columns to come.

About the Author

Kate Gregory is a founding partner of Gregory Consulting Limited (www.gregcons.com). In January 2002, she was appointed MSDN Regional Director for Toronto, Canada. Her experience with C++ stretches back to before Visual C++ existed. She is a well-known speaker and lecturer at colleges and Microsoft events on subjects such as .NET, Visual Studio, XML, UML, C++, Java, and the Internet. Kate and her colleagues at Gregory Consulting specialize in combining software develoment with Web site development to create active sites. They build quality custom and off-the-shelf software components for Web pages and other applications. Kate is the author of numerous books for Que, including Special Edition Using Visual C++ .NET.

Please enable Javascript in your browser, before you post the comment! Now Javascript is disabled.