October 22, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Head-Spinning Continued: The Managed Wrapper

  • September 30, 2003
  • By Kate Gregory
  • Send Email »
  • More Articles »

From Kate Gregory's Codeguru column, "Using Visual C++ .NET".

In my last column I showed how It Just Works can enable you to recompile your old legacy C++ class as managed code with unmanaged data, creating a class that compiles to intermediate language but isn't garbage collected. You can use this class from a new Managed C++ application and reach all your legacy functionality from a shiny new application that runs on the .NET framework.

But let's say your legacy class is so amazing that all the developers in your organization want to use it, even the ones who use C# and VB. The techniques I've shown in earlier columns, COM Interop and P/Invoke, result in a version of your class library that can be used from any managed language. The xcopy port does not. Only C++ can work with unmanaged data.

You could convert ArithmeticClass to a managed class, by adding the __gc keyword. However, this subjects it to all the rules that apply to managed data: for example it couldn't use multiple inheritance. If you need to change the class in any way to make it managed, you would then need to maintain two versions of the class; the managed and the unmanaged. It's a better plan to write a wrapper (in Managed C++, of course) for your unmanaged class. The wrapper class is managed code and managed data, and can be used from any managed language. As for your unmanaged class, you can compile it into intermediate language thanks to It Just Works, or you can leave it as native code and call it from the Managed C++ wrapper class, also thanks to It Just Works.

How can you leave your legacy class as native code? Well, you could leave it wherever it was (in a .lib file, say) and just call it from your managed code. (See Who Needs P/Invoke?) Or you could bring it into the project but turn off the /clr option for that particular file of source code. Just right-click the file name (LegacyArithmetic.cpp) in Solution Explorer and choose Properties. In the C/C++ properties folder, the General section includes a property called Compile As Managed. You can set this to Not Using Managed Extensions, and presto! You get native code in the middle of your .NET assembly.

You'll have to twiddle some other options, for example you must turn off pre-compiled headers for any file that is compiling to native code in a managed project, since the headers will be incompatible, but the compiler errors should lead you through the process pretty smoothly. You'll also probably have to remove any project references (which apply to all files in the project) and use the 2002-style #using approach in the files that are compiling to intermediate language.

That's not to say you will always want to compile your legacy code as unmanaged. It all depends where you want the managed-unmanaged boundary to be. Since there is a performance cost to make that transition, you should give a little thought to putting the boundary in the place that lowers performance costs the most.

For example, say your unmanaged class exposes 5 or 10 properties, and is typically accessed through a "chatty" interface — one call for each property set, followed by a final call that actually does something. While you're writing the wrapper class, you decide to offer a "chunky" interface — one call that takes 5 or 10 parameters and uses them to do the property setting, then calls the action method. If you leave your old class as unmanaged code, this wrapper class will make the managed-unmanaged transition repeatedly as it sets the parameters. Move the old class to managed code and the boundary moves so that all the chatty calls happen entirely in managed code. Alternatively, perhaps your legacy class already has a chunky interface, and it uses some other legacy class in a chatty way. Now you want your class to stay as unmanaged code, so there's only one transition when you call it from the wrapper, and all the chattiness happens entirely in unmanaged code. You can only place this border properly when you understand the way your code operates: there's no simple cookbook solution.

Writing the wrapper class is simple enough. Here's one for good old ArithmeticClass:

public __gc class ArithmeticWrapper
{
private:
    ArithmeticClass* ac;
public:
    ArithmeticWrapper() {ac = new ArithmeticClass();}
    double Add(double num1, double num2) 
    {return ac->Add(num1,num2);}
    ~ArithmeticWrapper() {delete ac;}
};

As you can see, it simply keeps a pointer to an instance of the unmanaged class as a member variable. (It has to be a pointer; managed classes are not allowed to have member variables that are instances of an unmanaged class.) The constructor creates the instance; the destructor cleans it up, and a wrapper function for each member function in the unmanaged class just passes each call and parameters along. Every wrapper you write will be much like this one, except that since real classes have more than one method, you'll have more than one wrapper method. You might also take this opportunity to tweak your interface a little, for example substituting a chunky interface for a chatty one by writing one wrapper method that calls many different methods of the wrapped legacy class.

Once you write this class, the wrapper is now managed data in managed code. C# and VB people can use it. The real guts are in the original legacy class, and both your old and new code are accessing the same guts. You're not paying a performance penalty for COM Interop or PInvoke, though you are paying the managed-unmanaged transition costs. Since you can turn /clr on and off file-by-file, you can minimize those costs by choosing which classes compile to intermediate language and which compile to native code. It all works out.

Well, how's your head? Still attached? I've reached the end of my list of ways to reuse old C++ code from new C++ (or even C# or VB) applications. There are plenty more interop possibilities to work through, of course, but the basic triumvirate of COM Interop, P/Invoke and It Just Works are all there is when it comes to calling old code from new. Now you've seen them all, why not try your hand at a little reuse of your own?

About the Author

Kate Gregory is a founding partner of Gregory Consulting Limited (www.gregcons.com). She is the MSDN Regional Director for Toronto, Canada. Kate's 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.


# # #






Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel