When a cpp file is compiled with /clr, a managed object file is created. In most scenarios, such a managed object file contains only managed code. However, there are two scenarios that end up in a managed object file with managed and native code:
- When #pragma unmanaged is used
- When the C++ file contains a function with C++ constructs that are not mappable to IL code.
Managed object files with native code imply a certain danger: They can end up in uninitialized state, as shown in the following sample:
In the code below, i is a global variable initialized with the return value of getValue(). There are two exported functions that simply return i. One is the managed function fManaged and the other one is the unmanaged function fUnmanaged.
<code language=”C++/CLI” filename=”MixedLib.cpp” compileWith=”CL /LD /clr MixedLib.cpp”>
#pragma unmanaged
__declspec(noinline) int getValue() {
return 42;
}
int i = getValue();
__declspec(dllexport) int fUnmanaged()
{
return i;
}
#pragma managed
__declspec(dllexport) int fManaged()
{
return i;
}
</code>
Before fManaged is executed the first time, the module constructor is called. The module constructor calls getValue to initialize the global variable i. However, if fUnmanaged is called before fManaged is called the first time, the variable i will be returned before it is initialized. To reproduce this scenario, you can use the client application below.
<code language=”C++/CLI” filename=”TestApp.cpp” compileWith=”CL /clr TestApp.cpp”>
#include <stdio.h>
#pragma comment(lib, "testlib.lib")
__declspec(dllimport) int fUnmanaged();
__declspec(dllimport) int fManaged();
int main()
{
printf("fUnmanaged returns %d\n", fUnmanaged());
printf("fManaged returns %d\n", fManaged());
printf("fUnmanaged returns %d\n", fUnmanaged());
}
</code>
If you start TestApp.exe, you will get the following output:
fUnmanaged returns 0
fManaged returns 42
fUnmanaged returns 42
The easiest way to avoid these problems is to make sure that files compiled with /clr contain only managed code and to leave the native code in cpp files compiled without /clr. If you consider using #pragma unmanaged or unmappable C++ constructs in files compiled with /clr, you should be aware that all global variables and static member variables of that file, are initialized by the module initializer. This is even true if the variable is of a native type.
The compiler's ability to automatically compile a function with unmappable C++ constructs to native code can cause a scenario where you get mixed code object files without even realizing it. Fortunately a compiler warning C4793 can be emitted in this scenario. C4793 is a level 2 warning. To get it, you either have to set the warning level to 2, or you have to use a compiler switch to make it a level 1 warning, as shown in the following code.
<code language="C++/CLI"
filename=" UnmappableConstructs.cpp"
compileWith="cl /c /clr /w14793 UnmappableConstructs.cpp">
void f()
{
__asm int 3;
}
</code>
When you compile this code, the C++ compiler will emit the warning C4793 with the following message:
UnmappableConstructs.cpp(3) : warning C4793: '__asm' : causes native code generation for function 'void f(void)'
UnmappableConstructs.cpp(1) : see declaration of 'f'