Monday, August 13, 2012

C++/CLI compile options

When compiling C++/CLI code there is several different compiler modes which sets the level of interaction between managed and unmanaged code. Depending the on what is should be achieved with the code the correct compiler option can be the difference between a finished component and the compiler throwing a tantrum.

This post will deal with the options /clr, /clr:pure, and /clr:safe. There is also the option /clr:oldSyntax which is used for compiling code that uses the managed C++ extension . The managed C++ extension is a predecessor to C++/CLI and uses a completely different syntax.

clr:safe

This option requires that only verifiably type safe code is included in the component. This means that the code cannot access unsafe arrays, that does not carry out boundary checks, or any unsafe pointers. Any calls to native code must therefore be marshaled and the code cannot contain any instances of native types.

1:  #pragma once  
2:  using namespace System;  
3:  namespace ClrSafe {  
4:       public ref class ClrSafeClass  
5:       {  
6:       private:  
7:            double offset;  
8:       public:  
9:            ClrSafeClass(double offsetParam) : offset(offsetParam) {}  
10:            ~ClrSafeClass() {}  
11:            double Translate(double position) { return position + offset; }  
12:       };  
13:  }  

The code above does not contain any references to native code and can hence be compiled with the /clr:safe option. In comparison to C# this would be similar to compile with the Any CPU flag, this is because the code does not reference any specific architecture. 

clr:pure

The option like the /clr:safe option generate only IL (Intermediate Language) output. The difference is that native types and classes are allowed to exist within the component. The compiler makes it so that the calls to native calls are transformed to IL. There are some limitations to this, such as that the native components cannot be declared to export their methods from the DLL. Neither __delcspec(dllexport) nor .def files will export native calls, this is due to that all exported methods are internally declared with __clrcall.

1:  #pragma once  
2:  #include <cmath>  
3:  #include <iostream>  
4:  #include <msclr\marshal_cppstd.h>  
5:  using namespace System;  
6:  using namespace msclr::interop;  
7:  namespace ClrPure {  
8:       public ref class PureClass  
9:       {  
10:       private:  
11:            double exponent;  
12:       public:  
13:            PureClass(double exponentParam) : exponent(exponentParam) {   
14:                 std::string mess = "Using exponential";   
15:                 Console::WriteLine(marshal_as<String^>(mess));   
16:            }  
17:            ~PureClass() {}  
18:            double Power(double base) { return pow(base, exponent); }  
19:       };  
20:  }  

The above code can be compiled with /clr:pure and the Power method, on row 18, that uses a native function in the cmath library generates the following IL code:

1:  .method public hidebysig instance float64   
2:      Power(float64 base) cil managed  
3:  {  
4:   // Code size    15 (0xf)  
5:   .maxstack 2  
6:   .locals ([0] float64 V_0)  
7:   IL_0000: ldarg.1  
8:   IL_0001: ldarg.0  
9:   IL_0002: ldfld   float64 ClrPure.PureClass::exponent  
10:   IL_0007: call    float64 modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) pow(float64,  
11:                                                    float64)  
12:   IL_000c: stloc.0  
13:   IL_000d: ldloc.0  
14:   IL_000e: ret  
15:  } // end of method PureClass::Power  

On row 10 there is a call to the native method expressed in IL: Code compiled with /clr:pure or /clr:safe have native entry points and are slightly more effective since they both produce a none mixed assembly.

clr

The /clr option allows for complete mixing of unmanaged and managed types. This type of assembly has a mixture of IL and native code. The entry point of the DLL is native code that then loads the CLR when it is needed.

1:  #pragma once  
2:  using namespace System;  
3:  namespace Clr {  
4:       class __declspec(dllexport) NativeClass  
5:       {  
6:       private:  
7:            double scaleFactor;  
8:       public:   
9:            NativeClass(double scaleFactor) : scaleFactor(scaleFactor) {}  
10:            virtual ~NativeClass() {}  
11:            double scale(double factor) { return factor*scaleFactor; }  
12:       };  
13:       public ref class ClrClass {  
14:       private:  
15:            NativeClass* nativeClass;  
16:       public:  
17:            ClrClass(double scaleFactor) { nativeClass = new NativeClass(scaleFactor); }  
18:            ~ClrClass() { delete nativeClass; }  
19:            double Scale(double factor) { return nativeClass->scale(factor); }  
20:       };  
21:  }  

The above code has both the possibility to be called by a managed or unmanaged component, where the managed class is merely a wrapper for the native class.

This concludes the C++/CLI compiler options that can be used to create mixed modes and managed DLL’s.  A MSDN article on the compiler options can be found here

No comments:

Post a Comment