C++ At Work
Managed Code in Visual Studio 2005
Paul DiLascia
Code download available at:
CatWork2006_06.exe
(262 KB)
Browse the Code Online

Contents
Many of you are no doubt in the process of upgrading to Visual Studio® 2005, so I thought now would be a good time to relate some of my own experiences with the new compiler. What took me so long? Hey, I'm a retro kind of guy! Better late than never!
The first thing you'll notice about Visual Studio 2005 is that it has a version manager that looks at your project and decides which version of itself to launch. You can install Visual Studio 2005 alongside Visual Studio .NET 2003; the two will cohabit your computer peacefully, which is great if you want to upgrade your projects at your own leisure. If you open an older project, Visual Studio 2005 prompts you to save a copy before converting, then generates an XML report that describes whatever issues it found.
Minor Language Changes
To take Visual Studio 2005 for a spin, I compiled a half dozen or so projects from past columns. They all required minor edits, mostly to accommodate a handful of language-conformance changes that make Visual Studio 2005 a modern C++ compiler. Most of the "new" rules have been in the C++ standard for a while, but only now does Visual Studio enforce them. The two most common language mutations I saw are for-loop scoping and default int types.
Local variables inside for loops no longer have scope outside the loop. Before, it was possible to write:
for (int i=0; i<max; i++) {
// do something
}
if (i>0) {
// do something else
}
In this snippet, the variable i is scoped inside the for statement and used outside it. Officially, C++ doesn't allow this, so now you need to rewrite your code like so:
int i; // move outside for loop
for (i=0; i<max; i++) {
// do something
}
if (i>0) {
// do something else
}
Undeclared static variables, both local and global, no longer default to integer types. Previously, you could write
and the compiler would implicitly assign BUFLEN type int. Implicit ints are now forbidden. You must declare your type like so:
This applies to variables of all kinds—static, global, data members, and function return types. If you leave out the int, you'll get "error C4430: missing type specifier - int assumed. Note: C++ does not support default-int".
Another whole category of C/C++ changes relates to the new Safe C and Safe C++ libraries. These libraries provide more secure versions for many of the oldfangled C runtime (CRT) functions you know and love: strcpy, fopen and others. I plan to write more about Safe C++ in a future column. If you can't wait, read Martyn Lovell's excellent article, "Safe! Repel Attacks on Your Code with the Visual Studio 2005 Safe C and C++ Libraries" in the May 2005 issue (
msdn.microsoft.com/msdnmag/issues/05/05/SafeCandC).
So much for C++, what about MFC? Visual Studio 2005 introduces no major changes to MFC which, as I've said before, is a good thing. It means MFC is stable. I did notice, however, that the return type for CWnd::OnNcHitTest has morphed from UINT to LRESULT. There may be other minor tweaks, but you shouldn't run into anything major that would break your MFC apps.
Migrating to the Managed World
C++ and MFC are stable programming systems, so I didn't expect any upgrade glitches in those areas. The big changes betweenVisual Studio .NET 2003 and Visual Studio 2005 lie in the realm of managed code. There's the new C++/CLI syntax you've been reading about—think of it as Managed Extensions V2. If you're not ready to wrap your brain around tracking handles just yet, you can use /clr:oldSyntax to stick with the old Managed Extensions. That's what you get by default when you upgrade a managed/mixed project from Visual Studio .NET 2003 to Visual Studio 2005.
To test the new compiler on managed code, I used the ManWrap library that I created for my April 2005 article "Wrappers: Use Our ManWrap Library to Get the Best of .NET in Native C++ Code". ManWrap comprises a wrapper DLL (RegexWrap.dll) and three test programs: RegexTest, RegexForm, and WordMess (see Figure 1). ManWrap is a good bellwether because, while small, it's fairly sophisticated in some of the things it does—like mixing managed and unmanaged code in a DLL. RegexWrap is a native DLL that wraps the common language runtime (CLR) Regex class.
Figure 1 Testing Managed Code
So I kept the old syntax and let the compiler rip... In seconds, a flurry of errors scrolled across my screen: "C3395 ... __declspec(dllexport) cannot be applied to a function with the __clrcall calling convention." Say whaaaat?
ManWrap is a library that lets you wrap managed classes in purely native C++ so you can call the CLR from native C++ code that's compiled without /clr. For example, say you have a legacy app that uses the old Visual C++® 6.0 compiler, but you want to add a feature that calls the CLR. You can't call managed classes directly from C++ without throwing the /clr switch (and Visual C++ 6.0 doesn't have one to throw!), so the only way you can reach managed classes is to wrap them in a DLL with native entry points. ManWrap provides a general mechanism for writing such wrappers.
The trick at the heart of ManWrap is to use the special predefined preprocessor symbol _MANAGED to generate different code for internal and external consumption. Each wrapper class holds a single data member, a handle to the managed object:
#ifdef _MANAGED
# define GCHANDLE(T) gcroot<T*>
#else
# define GCHANDLE(T) intptr_t
#endif
Wrapper classes use GCHANDLE to declare their object handles like so:
// wrapper for managed Object
class CMObject {
GCHANDLE(Object) m_handle;
};
The header file with CMObject is compiled two ways. When you build the wrapper DLL, you compile with /clr, so _MANAGED is defined and the compiler sees m_handle as gcroot<Object*>. When you build the native app that calls the wrapper, you compile without /clr, so _MANAGED is undefined and the compiler sees m_handle as intptr_t. This works because gcroot<Object*> is guaranteed to have the same size as intptr_t. Only the wrapper DLL knows what the handle is. To the outside (native) world, m_handle is just a magic cookie—similar to an HWND, HINSTANCE or any other kind of handle. The only catch is that the copy constructors and assignment operator must be true functions, not inline—so they call into the wrapper, which manipulates the handles as the true objects they are. (You can't copy intptr_t handles, you need to go through gcroot.)
So in addition to holding the handles, ManWrap wrappers define the usual methods to construct and copy objects. Each wrapper class also defines a ctor and operator-> so the wrapper can create and access native wrapper objects from their corresponding managed types. For example, there's a constructor to create a native CMRegex from a managed Regex. The wrapper classes use the ctor and operator-> internally. Figure 2 shows a code snippet from ManWrap.h, with the managed methods inside the #ifdef _MANAGED block. Note that the entire class is exported with WREXPORT, which expands to __declspec(dllexport) when building the DLL. This is what causes the C3395 error. You can't export managed methods (methods with managed parameters) using __declspec(dllexport) because native and managed functions use different calling conventions. Well, that makes sense—why would I attempt to export managed functions from a native DLL in the first place? But I'm not really exporting them. They're all defined inline. The managed methods are not required in the native interface, nor are they visible to it; but the compiler doesn't know this. Apparently Visual Studio 2005 isn't as smart as the old compiler, which let me dllexport the entire class. Or else it's too smart, because under most circumstances it makes no sense to export managed methods from native code. Whatever. The point is that Visual Studio 2005 won't let you export a class if it has any managed methods, period.

Figure 2 Exporting a Class Containing Managed Methods
//////////////////////////////////////////////////////////////////
// Snippet from old (Visual Studio .NET 2003) version of ManWrap.h
// Note entire class declared using WREXPORT.
//////////////////
// Macros to control DLL. Use BUILD_MANWRAP_DLL when you’re writing
// your own wrapper. Use USE_MANWRAP_DLL if you’re writing an app that
// links to RegexWrap.dll. Don’t use anything if you link the code
// directly into your app, but then you have to compile this and
// ManWrap.cpp with /clr.
//
#if defined(BUILD_MANWRAP_DLL)
# define WREXPORT __declspec ( dllexport )
#elif defined(USE_MANWRAP_DLL)
# define WREXPORT __declspec ( dllimport )
#else
# define WREXPORT
#endif
//////////////////
// Handle to managed object.
// Appears as gcroot<T> wrapper to managed code, intptr_t to native code.
//
#ifdef _MANAGED
# define GCHANDLE(T) gcroot<T>
#else
# define GCHANDLE(T) intptr_t
#endif
class WREXPORT CMObject {
protected:
// handle to underlying managed object
GCHANDLE(Object*) m_handle;
public:
#ifdef _MANAGED
// visible to managed clients only: anything that deals with __gc
// objects
CMObject(Object* o) : m_handle(o) { }
Object* ThisObject() const { return (Object*)m_handle; }
Object* operator->() const { return ThisObject(); }
#endif
// visible to all clients
CMObject();
CMObject(const CMObject& o);
CMObject& operator=(const CMObject& r);
...
};
So what to do? What I'd like to do is tell the compiler, "export the entire class except these three methods." That is, I'd like a way to suppress __declspec(dllexport) for specific methods. Alas, there's no such option. Absent that, there are only two solutions I can think of: remove WREXPORT from the class declaration and add it to each native method or remove the offending methods entirely. Plan A is simpler, and that's the path I chose. Moving WREXPORT to the method declarations is a bore and somewhat error prone because it's easy to forget WREXPORT when you add new methods. If you do, though, the compiler will remind you.
Plan B, if you really, really want to dllexport the entire class, is to remove the offending methods: the managed copy constructor and dereference operator->. Then instead of writing
// inside wrapper class for MClass
(*this)->ManagedMethod();
which silently invokes the now-defunct operator->, you'd have to write:
(static_cast<MClass*>((Object*)m_handle))->ManagedMethod();
What a mouthful! You could introduce a macro to save typing:
THISOBJ(MClass*)->ManagedMethod();
But it's kind of clunky and you still have to deal with the constructor. If you're totally lost (and I expect many readers will be), don't worry, I chose Plan A, the simple approach. Figure 3 shows the revised code with WREXPORT on all the methods. After I made this change, ManWrap compiled just fine.

Figure 3 Exporting Managed Methods Individually
////////////////////////////////////////////////////////////////
// New version of ManWrap.h for Visual Studio 2005.
// Now only the native methods get WREXPORT.
//////////////////
// Macros to control DLL. Use BUILD_MANWRAP_DLL when you’re writing
// your own wrapper. Use USE_MANWRAP_DLL if you’re writing an app that
// links to RegexWrap.dll. Don’t use anything if you link the code
// directly into your app, but then you have to compile this and
// ManWrap.cpp with /clr.
//
#if defined(BUILD_MANWRAP_DLL)
# define WREXPORT __declspec ( dllexport )
#elif defined(USE_MANWRAP_DLL)
# define WREXPORT __declspec ( dllimport )
#else
# define WREXPORT
#endif
//////////////////
// Handle to managed object.
// Appears as gcroot<T> wrapper to managed code, intptr_t to native code.
//
#ifdef _MANAGED
# define GCHANDLE(T) gcroot<T*>
#else
# define GCHANDLE(T) intptr_t
#endif
//////////////////
// Wrapper for Object, base of .NET hierarchy.
// CMObject doesn’t use DECLARE_WRAPPER because it has no base class.
// For Visual Studio 2005, must explicitly export each method--can’t use
// WREXPORT on entire class.
//
class CMObject {
protected:
GCHANDLE(Object) m_handle; // handle to underlying managed object
public:
#ifdef _MANAGED
// visible to managed clients only: anything that deals with __gc
// objects
CMObject(Object* o) : m_handle(o) { }
Object* ThisObject() const { return (Object*)m_handle; }
Object* operator->() const { return ThisObject(); }
#endif
// visible to all clients
WREXPORT CMObject();
WREXPORT CMObject(const CMObject& o);
WREXPORT CMObject& operator=(const CMObject& r);
WREXPORT BOOL operator==(const CMObject& r) const;
WREXPORT BOOL operator!=(const CMObject& r) const { return ! operator==(r); }
WREXPORT CString ToString() const;
WREXPORT CString TypeName() const;
WREXPORT BOOL IsNull() const;
};
Running ManWrap
Getting past the compiler is one thing, getting the code to run is something else. This is where I hit a wall—and almost hit my computer, too—in the form of an ASSERT bomb way down in dbgheap.c:
ASSERTE(_CrtIsValidHeapPointer(pUserData));
Yikes! The stack trace was mostly useless, full of system DLLs with no debug symbols. This is the worst kind of bug, the hardest to track down: your program dies somewhere deep in the system and you have no clue why. Well, almost no clue. Closer examination of the stack trace revealed about 50 frames back that my code was attempting to access a static ATL variable called g_Allocator. Aha! That was the subtle clue that broke the case.
g_Allocator is a static global. Initializing C++ static variables is always a delicate affair, particularly in DLLs. The compiler has to generate code to call the CRT initialization function that initializes your static variables before calling DllMain. Everything works fine in native land, but if your DLL calls managed classes, you can end up with loader-lock problems: Windows® attempts to load your DLL, which attempts to load the CLR, which in turn attempts to load your DLL—and, your app grinds to a halt in what's known as loader lock. In general, you can't load (require) a DLL while your DLL is loading. A common example of this occurs when you try to call ::MessageBox to display a diagnostic from your DllMain or static object constructor—it doesn't work.
To avoid loader lock, Visual Studio .NET 2003 mandated that managed DLLs should be /NOENTRY DLLs (DLLs with no DllMain entry point). That insures against loader lock, but it's like throwing the proverbial baby out with the bathwater: now static variables don't get initialized because your DLL doesn't get _DllMainCRTStartup, the magic CRT function that initializes them. My February 2005 column describes the conundrum in gory detail (
msdn.microsoft.com/msdnmag/issues/05/02/CATWork). Not having static objects is a big deal because both ATL and MFC need them. Without DllMain you couldn't write a mixed DLL that uses ATL or MFC. Since that's unacceptable, the friendly Redmondtonians provided a special file <_vcclrit.h> with functions __crt_dll_initialize and __crt_dll_terminate() that you could call to initialize your static variables.
If all that sounds like a Rube Goldberg machine, it is. You'll be happy to learn that Visual Studio 2005 fixes the mixed assembly loader-lock problem. You don't need _vcclrit.h or /NOENTRY, as you can compile mixed-mode DLLs the normal way. For details, see "Initialization of Mixed Assemblies" at
msdn2.microsoft.com/ms173266.aspx.
So why did ManWrap crash in dbgheap.c? Because I still had /NOENTRY left over from my old project. Of course my program croaked. It didn't have a DllMain. My static objects, including ATL's g_Allocator, never got initialized. Once I removed /NOENTRY, ManWrap ran just fine. Whew!
Donning a New Hat
One of the blessings of bogglesome bugs is how great you feel when you finally fix them. After successfully compiling and running all three ManWrap test programs (RegexTest, RegexForm, and WordMess), I was quite the happy managed camper. Why not shoot for the moon, embrace the new syntax? In case you've been hibernating for the past year, the heart of C++/CLI is the introduction of a new type called tracking handle that's identified by the symbol ^ (hat). So I got rid of /clr:oldSyntax (see
Figure 4) and changed one character in ManWrap.h, from * to ^:
#ifdef _MANAGED
# define GCHANDLE(T) gcroot<T^>
#else
# define GCHANDLE(T) intptr_t
#endif
Figure 4 Using the New Syntax
After making this tiny change, I fed ManWrap to the compiler and fixed the errors one-by-one as they came belching out. Mostly it's a matter of changing Mumble* to Mumble^ everywhere there's a managed Mumble. Of course, there are other syntactical slings and arrows to dodge. Here's an idiosyncratic list of syntax snafus I encountered with ManWrap. They'll seem old-hat (pun intended) to readers already fluent with C++/CLI, so feel free to skim if you're a CLI-savant.
•Managed classes must now be declared with ref or value instead of __gc or __value. In general, all __managed keywords have been replaced with contextually sensitive keywords.
•Default indexers are now called "default" instead of Item. Instead of writing:
This works even when you have multiple indexers, as with the MatchCollection in the Regex library.
MatchCollection* mc;
mc->default[0]; // int
mc->default["alpha"]; // string
•Managed objects must be allocated with gcnew. Anywhere you allocate a managed object, change new to gcnew.
•Certain conversions are no longer implicit. Consider the following snippet:
// native entry
void Foo(LPCTSTR lpsz)
{
// managed ctor takes a String
Mumble *m = new Mumble(lpsz);
}
With the old Managed Extensions, the compiler would implicitly create a new managed String initialized from lpsz in order to construct Mumble. With Visual Studio 2005, you must allocate the String explicitly, like so:
void Foo(LPCTSTR lpsz)
{
Mumble ^m = gcnew Mumble(gcnew String(lpsz));
}
It's more typing, but it's also more intelligible. I like gcnew because it forces you to know when you're allocating from the managed heap. Implicit conversions make the code appear clean and simple—deceptively simple. C++ is a relatively low-level language (I count that a blessing), so better to keep things visible and not perform too much magic behind the curtain. Since RegexWrap creates Strings all over, I wrote a macro to save typing.
#define S(s) (gcnew String(s))
This is intended to resemble the S modifier for managed strings, as in S"Hello, world." Now I can write:
Mumble ^m = gcnew Mumble(S(lpsz));
C++/CLI has a new syntax for managed arrays. Instead of
you now must write:
array <ManagedType^>^ myarray;
The second hat threw me at first. If you leave it out, you'll get "error C3149: cannot use this type here without a top-level '^'", which seems cryptic (top-level of what?), but makes perfect sense once you know the rule. I'm not sure why the top-level hat (top hat?) is required, since the compiler knows the array is managed by virtue of the "array" keyword—but I'm sure the language cops have a reason and besides, it does make sense. Managed things wear hats. You need the top hat for managed arrays of primitive types, too. For example:
// managed array of ints
array<int>^ foo;
•Instead of Count, use Length to get the length of an array.
•If you need to check for managed NULL pointers, use nullptr instead of NULL.
•Templates work better with managed classes. This is one of the most important reasons to use C++/CLI. Because managed and native classes each have their own private syntax (instead of sharing the overloaded *), the template generator can easily tell them apart.
There are more syntax changes I haven't mentioned. No doubt you'll discover them on your own. For an overview, see "Hello C++/CLI" by Stan Lippman in our special Visual Studio 2005 issue of
MSDN Magazine (see
msdn.microsoft.com/msdnmag/issues/06/00/PureC). I also recommend Stan's article, "A Baker's Dozen: Thirteen Things You Should Know Before Porting Your Visual C++ .NET Programs to Visual Studio 2005" (see
msdn.microsoft.com/library/en-us/dnvs05/html/BakerDozen.asp).
Don't let the new syntax scare you. As soon as I changed * to ^ and /clr:oldSyntax to /clr, it was merely a matter of fixing each error as the compiler complained. Once ManWrap got the compiler's seal of approval, it ran without a hitch. The Redmondtonians should take pride in that—any time the compiler can ensure correctness, it's a win. The smooth switch to hats made me so cheerful I thought of renaming GCHANDLE to MANHANDLE.
Nah.
Two Minor Gripes
Overall, the transition from Visual Studio .NET 2003 to Visual Studio 2005 was fairly painless. I don't have much to say about the IDE since I'm a dyed-in-the-wool text hacker who wouldn't consider editing my code with anything less than Emacs. That said, I have two small gripes with Visual Studio 2005. First, it insists on polluting "My Documents" with all sorts of folders for stuff I never use or need. By spelunking, I was able to discover some registry keys to remap all but a couple of folders to a TEMP directory where out of sight is out of mind. I take great care to organize my folders the way I like, so it irks me when authoritarian programs take over my disk. Let this be a warning: if your app needs folders, make sure you let users decide where they go.
My other complaint is that Visual Studio 2005 no longer supports sound schemes. Boo hoo! I'm not a big fan of noisy apps, but this is one place where sounds are really useful. With Visual Studio .NET 2003, I'd start a build, then go off to work in another window. When the compiler finished, I'd know instantly from the happy chirp or grumpy bonk whether my build was a success or failure. With Visual Studio 2005, I have to actually read the output screen—what a bore! There's another lesson here: never remove features.
Aside from these minor bumps and the /NOENTRY snafu, upgrading to Visual Studio 2005 was a smooth ride. If you haven't already made the switch, go for it—especially if you're writing mixed/managed assemblies. The new syntax is just better. As with any change, it takes a little adjustment. But once you get the hang of hats, they're way cool.
You can download the latest ManWrap from the MSDN Magazine Web site. The download comprises three complete versions: the original one for Visual Studio .NET 2003 and new versions for Visual Studio 2005 using both old and C++/CLI syntax. Happy programming!
.
Paul DiLascia is a freelance software consultant and Web/UI designer-at-large. He is the author of
Windows++:
Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). In his spare time, Paul develops PixieLib, an MFC class library available from his Web site,
www.dilascia.com.