Wednesday, January 31, 2007

Exporting native C++ in a C++/CLI Project using dllexport

Normally, when compiling C++ code with the /clr compiler flag, you generate a managed assembly that exports all the C++ code through a managed wrapper. Under the hood, this generates a native and a managed wrapper around each function. When an external assembly attempts to call the native function, it will always choose the managed wrapper call, even if the originator was a native function. This leads to a double thunk, where you transition from native to managed, then from managed to native, and back. Ideally, you would want to use the native entry point to the function when calling from a native function.

For example, say we have a C++/CLI project called MyExportProject that contains the class defintion:

ExportClass.h

#ifdef MAKING_DLL
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllimport)
#endif
namespace MyExportProject
{
// this is a native class
class DLLEXPORT NativeClass
{
public:
int GetValue();
};
}

ExportClass.cpp

namespace MyExportProject
{
int NativeClass::GetValue()
{
return 42;
}
}

If you compile this file with the /clr option and then run DUMPBIN /exports on the resulting MyExportProject.dll, you will see that there are in fact exported methods. There will also be a MyExportProject.lib that is a DLL stub library that you must use in order to use the NativeClass that was exported. You can then use the exported class in another project (also compiled with /clr):

UseExportedClass.cpp

#include "ExportedClass.h"
void main()
{
MyExportProject::NativeClass c;
printf("The Meaning of Life is %d\n", c.GetValue() );
}

Although this appears to do what you want (ie, calling a native exported function from a native function), it really adds the double thunk since .NET tries to remain in managed code as long as possible. Since there is a managed wrapper around the exported native function, it will prefer to use the managed wrapper.

The way to avoid this is to force the compiler not to create the managed wrapper. This is done by using #pragma unmanaged around the code that you do not want the wrapper for. So, to make the above code work we would change the ExportedClass.cpp file to look like the following:

ExportClass.cpp

namespace MyExportProject
{
#pragma unmanaged
int NativeClass::GetValue()
{
return 42;
}
#pragma managed
}

This will solve the double-thunk problem. However, you will no longer be able to access this function from managed code directly since the is no managed wrapper. Since the point of this was to only call this code from native code, it is a fair tradeoff.

For more information on this issue, see: http://www.heege.net/blog/PermaLink,guid,d5e2c3da-ff4d-40bc-bea9-9da8ec9e091e.aspx

No comments: