Monday, June 27, 2005 12:29 PM
joewood
Managed C++ "It Just Works" - just not the way you think
A little while ago, I came up against some performance problems in a mixed Managed/Unmanaged C++ assembly. I decided to profile the code and found that it wasn't as I had expected. The functions in unmanaged code, and even the functions from a vendor C++ API, were all appearing as managed .NET functions.
The C++ assembly had some managed classes (marked with '__gc') and some non-managed classes (marked with '__nogc'). My assumption was that the 'It Just Works' marsahling between managed and unmanaged code was working between the '__gc' and '__nogc' classes. The non-managed code included some virtual function overrides, overriding functions from a vendor library.. According to the profiler (and Reflector) even this function was a managed function and was included in the meta-data in the assembly. I could even decode the function and see the disassembly in C# !
So, after digging around the internet I discovered that the __gc and __nogc prefixes didn't work as I had expected. These directives actually apply to the data and not the code. If a class is marked as __gc then this means that the data type is collectible by the garbage collector. It has no impact on the generated code inside the class. If a class is marked as __nogc then the data is native, but the code inside the class is still IL code. This means that all the code included from external header files (inside inline functions etc) is all MSIL. The virtual function that I was running as part of the vendor library was going through a number of unmanaged to managed transitions, although no data was being marshaled across the boundary, this still took time and was impacting the overall speed of the application.
The solution is to tell the compiler to generate native code, instead of MSIL. This can be done using the '#pragma managed' and '#pragma unmanaged' directives. So, in simple terms, interaction with the vendor library should look like this:
#pragma unmanaged
#include "vendor library headers"
__nogc public class OverrideClass : public VendorBaseClass
{
virtual void OnDoAction()
{
....
}
};
#pragma managed
__gc public class ManagedOverrideClass
{
void DoAction() { }
}
To call out of the unmanaged code into the managed code the unmanaged class can hold a reference to a managed type using the template class gcroot<>. You can also include specific managed functions in unmanaged types. It's interesting to notice that if any unmanaged class has any MSIL based functions then the class name is included in the meta-data in the assembly.