May 15, 2012

In this tutorial we will see how to call a C++ library from a Mono for Android app. Out of sheer curiosity and in the face of pragmatism, instead of resorting to P/Invoke we will make use of Cxxi, a framework that is still in development and aims to improve the interoperability between C++ and Mono.

Native library

The native library that we are going to use is Boost.Geometry and, as in the past, we will limit ourselves only to boolean operations on polygons. This choice leads to difficulties due to the heavy use of templates in the library so, in order to export the features that we need, we will use the following class (which is inspired by KBool):


//
// Wrapper.h
//

#if defined(_MSC_VER)
    #define EXPORT __declspec(dllexport)
#else
    #define EXPORT
#endif

namespace ClippingLibrary
{


class EXPORT Wrapper
{
public:
    Wrapper();
    ~Wrapper();
    
    void AddInnerRing();
    void AddOuterRing();
    void AddPoint(double x, double y);

    void ExecuteDifference();
    void ExecuteIntersection();
    void ExecuteUnion();
    void ExecuteXor();
    
    bool GetInnerRing();
    bool GetOuterRing();
    bool GetPoint(double& x, double& y);

    void InitializeClip();
    void InitializeSubject();
    
private:
    class Implementation;
    Implementation* _pImpl;
};


}

In doing so the real and proper implementation of the library is completely hidden and Cxxi will have to deal only with class members that at the most expect two parameters of type double.

Moreover, to build the library for Android, it is necessary to modify the file

$BOOST_ROOT\boost\detail\endian.hpp

in this way:


#ifndef BOOST_DETAIL_ENDIAN_HPP
#define BOOST_DETAIL_ENDIAN_HPP

// GNU libc offers the helpful header <endian.h> which defines
// __BYTE_ORDER

#if defined (__GLIBC__) || defined(ANDROID)
# include <endian.h>

For further details about Boost on Android:

https://github.com/MysticTreeGames/Boost-for-Android

Ndk

Ndk (Native Developement Kit) is a set of tools that allow to build C and C++ libraries so that they can be used on Android. In particular, through Ndk we can carry out a cross-compilation. So, for example, a library built on Windows can be executed on an Android device.

First thing, it is necessary to prepare an Ndk project creating a main directory with any name and then a subdirectory that must be called jni. Then, inside the jni subdirectory, we create two new files that are required to configure the project.

The first file is Application.mk:

APP_STL := gnustl_static

APP_ABI := \
    armeabi \
    armeabi-v7a
  • APP_STL, specifies the C++ runtime to be used.

    Ndk provides several runtimes with different characteristics and licenses. Given that we have Boost as dependency, C++ exceptions support is required. At the moment, the only runtime that has such capability is Gnu Stl, which is under Gpl v3 license. Also, we use the static version since we want to create only one library.

  • APP_ABI, specifies the hardware architecture on which our software will run.

    If omitted the default value is armeabi.

The second file is Android.mk:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := Native
LOCAL_SRC_FILES := Wrapper.cpp

LOCAL_CPPFLAGS += -fexceptions
LOCAL_C_INCLUDES := W:\Boost

include $(BUILD_SHARED_LIBRARY)
  • LOCAL_PATH, specifies the path of the current Android.mk file and must be defined at the beginning. The macro $(call my-dir) returns the path to Android.mk itself.
  • include $(CLEAR_VARS), resets various global variables and must be used in case the project is composed of several modules.
  • LOCAL_MODULE, specifies the module name.
  • LOCAL_SRC_FILES, lists the source files to be built. It must not contain header files.

    Note that in this example, Wrapper.h and its implementation Wrapper.cpp must be inside the jni directory.

  • LOCAL_CPPFLAGS, lists the flags to be passed to the C++ compiler. fexceptions enables exceptions support, otherwise the following error should appear:
    $BOOST_ROOT/boost/numeric/conversion/converter_policies.hpp:162: error: exception handling disabled, use -fexceptions to enable
  • LOCAL_C_INCLUDES, lists the paths where headers of dependencies like Boost can be found.
  • include $(BUILD_SHARED_LIBRARY), specifies that the output must be a shared library.

At this point, the PATH system variable must include the path to the Ndk installation directory. We can open a shell or command prompt and go to the main directory of our Ndk project and, finally, launch the following command:

ndk-build

At the end of the build process, inside the main directory of the project there should be a subdirectory named libs, and this should be its content:

libs
+---armeabi
|       libNative.so
|
\---armeabi-v7a
        libNative.so

For every architecture specified in the Application.mk file there is a subdirectory with its version of the library. Note that the name of the file is libNative.so, where Native is the name of the module specified in the Android.mk file and lib is a prefix added for convention.

For further details it is advisable to refer to the documentation provided with the Ndk, expecially the html files OVERVIEW, CPLUSPLUS-SUPPORT, APPLICATION-MK and ANDROID-MK.

Gcc-Xml

Gcc-Xml is a tool that parses C++ code and generates an xml file containing a description of the parsed code. Then, the xml file can be used by other tools that need access to the structure of the C++ code, avoiding to tackle directly the complex parsing phase.

The development version of Gcc-Xml is still being updated. The source code can be checked out from the git repository according to the instructions on the download page, and it is ready to be used with CMake.

Note that Gcc-Xml requires the presence of a C++ compiler, the PATH system variable must include the path to the compiler, and if you choose to download pre-built binaries you must be sure that they support such compiler.

Once obtained the binaries for Gcc-Xml, we can execute the following command:

gccxml --gccxml-compiler gcc -fxml=Wrapper.xml Wrapper.h
  • gccxml-compiler, specifies the compiler to be used.

    We opted for gcc but, for example, on Windows we could have used msvc10.

  • fxml, sets the name of the xml file to be generated.
  • Wrapper.h, in the end, is the name of the source file to be parsed.

Cxxi

Cxxi is a tool that enables the use of C++ libraries from managed code. In this regard, if you have not yet done so, it could be interesting to read this introduction that also lists some of the features that make Cxxi different from other solutions such as Swig:

CXXI: Bridging the C++ and C# worlds.

Here you can find the master branch:

http://github.com/mono/cxxi

However we are going to use this fork:

https://github.com/kthompson/cxxi

This is because some problems still present in the master have been solved in the fork, in particular with parameters passed by reference as in the GetPoint method seen in our Wrapper class.

The source code comes with a solution file (.sln) that can be opened with Visual Studio 2010 or MonoDevelop. As the result of the build process, we get two files:

  • Generator.exe
  • Mono.Cxxi.dll

But presently, in release mode, the name of the dll is CPPInterop.dll.

At this point we can feed Generator.exe with the xml file previously created by Gcc-Xml:

generator -abi=ItaniumAbi -ns=CxxiGeneratedNamespace -lib=Native -o=OutputFolder Wrapper.xml
  • abi, specifies the abi used by our native library.

    The abi used on Arm seems to be based on the Itanium abi and moreover Ndk uses gcc as compiler so the value of the abi option must be set to ItaniumAbi. If we were trying to use a library built with Visual C++ from a Windows application, we should specify MsvcAbi.

  • ns, specifies the namespace that encloses the files created by Generator.exe
  • lib, is the name of the native library without the lib prefix.
  • o, is the directory where Generator.exe puts the output files.
  • Wrapper.xml, in the end, is the name of the xml file generated by Gcc-Xml

The first file obtained in our example is Lib.cs:


using System;
using Mono.Cxxi;
using Mono.Cxxi.Abi;

namespace CxxiGeneratedNamespace {

    public static partial class Libs {
        public static readonly CppLibrary Native = 
            new CppLibrary ("Native", 
                ItaniumAbi.Instance, 
                InlineMethods.NotPresent);
    }
}

The namespace is the same one specified in the previous command, and the name of the library and the choosen abi are passed to the CppLibrary constructor.

The second file is Wrapper.cs (slightly reformatted):


using System;
using Mono.Cxxi;

namespace CxxiGeneratedNamespace.ClippingLibrary {
    public partial class Wrapper : ICppObject {

        private static readonly IWrapper impl = 
            Libs.Native.GetClass<IWrapper,_Wrapper,Wrapper> (
                "Wrapper");
        public CppInstancePtr Native { get; protected set; }

        public static bool operator!=(Wrapper a, Wrapper b)
        { 
            return !(a == b);
        }
        public static bool operator==(Wrapper a, Wrapper b)
        {
            if (object.ReferenceEquals(a, b))
                return true;
            if ((object)a == null || (object)b == null)
                return false;
            return a.Native == b.Native;
        }
        public override bool Equals(object obj)
        {
            return (this == obj as Wrapper);
        }
        public override int GetHashCode() 
        { 
            return this.Native.GetHashCode();
        }

        [MangleAs ("class ClippingLibrary::Wrapper")]
        public partial interface IWrapper :
        ICppClassOverridable<Wrapper>
        {
            [Constructor] CppInstancePtr Wrapper (
                CppInstancePtr @this);
            [Destructor] void Destruct (CppInstancePtr @this);
            void AddInnerRing (CppInstancePtr @this);
            void AddOuterRing (CppInstancePtr @this);
            void AddPoint (CppInstancePtr @this,
                double x, double y);
            void ExecuteDifference (CppInstancePtr @this);
            void ExecuteIntersection (CppInstancePtr @this);
            void ExecuteUnion (CppInstancePtr @this);
            void ExecuteXor (CppInstancePtr @this);
            bool GetInnerRing (CppInstancePtr @this);
            bool GetOuterRing (CppInstancePtr @this);
            bool GetPoint (CppInstancePtr @this, 
                [MangleAs ("double  &")] ref double x, 
                [MangleAs ("double  &")] ref double y);
            void InitializeClip (CppInstancePtr @this);
            void InitializeSubject (CppInstancePtr @this);
        }
        public unsafe struct _Wrapper {
            public IntPtr _pImpl;
        }




        public Wrapper (CppTypeInfo subClass)
        {
            __cxxi_LayoutClass ();
            subClass.AddBase (impl.TypeInfo);
        }

        public Wrapper (CppInstancePtr native)
        {
            __cxxi_LayoutClass ();
            Native = native;
        }

        public Wrapper ()
        {
            __cxxi_LayoutClass ();
            Native = impl.Wrapper (impl.Alloc (this));
        }
        public void AddInnerRing ()
        {
            impl.AddInnerRing (Native);
        }
        public void AddOuterRing ()
        {
            impl.AddOuterRing (Native);
        }
        public void AddPoint (double x, double y)
        {
            impl.AddPoint (Native, x, y);
        }
        public void ExecuteDifference ()
        {
            impl.ExecuteDifference (Native);
        }
        public void ExecuteIntersection ()
        {
            impl.ExecuteIntersection (Native);
        }
        public void ExecuteUnion ()
        {
            impl.ExecuteUnion (Native);
        }
        public void ExecuteXor ()
        {
            impl.ExecuteXor (Native);
        }
        public bool GetInnerRing ()
        {
            return impl.GetInnerRing (Native);
        }
        public bool GetOuterRing ()
        {
            return impl.GetOuterRing (Native);
        }
        public bool GetPoint (ref double x, ref double y)
        {
            return impl.GetPoint (Native, ref x, ref y);
        }
        public void InitializeClip ()
        {
            impl.InitializeClip (Native);
        }
        public void InitializeSubject ()
        {
            impl.InitializeSubject (Native);
        }


        partial void BeforeDestruct ();
        partial void AfterDestruct ();

        public virtual void Dispose ()
        {
            BeforeDestruct ();
            impl.Destruct (Native);
            Native.Dispose ();
            AfterDestruct ();
        }

        private void __cxxi_LayoutClass ()
        {
            impl.TypeInfo.CompleteType ();
        }

    }
}

Here, again, we can note the namespace to which has been added ClippingLibrary, the namespace of the native library. Furthermore, the class takes the name of the respective native class, Wrapper. If there were more classes, there would be a file for each class.

Mono for Android

Through Mono for Android, C# developers can write apps for Android without the need to learn Java. The trial version has no time limits but only allows to deploy to the Android emulator. The installer downloads all the necessary, Android Sdk and MonoDevelop included.

To test our library we can execute the following steps (the description refers to MonoDevelop but the steps for Visual Studio are similar):

  1. Create a new Solution called TestMonoDroid. Select a Mono for Android project type (actually only available under C#).
  2. Add a new project to the solution and name it Cxxi. Select a Mono for Android Library Project type (only available under C#).

    We can't use the previously created Mono.Cxxi.dll, the project must be rebuilt to target Mono for Android.

  3. Inside the Cxxi project add the same files found in the Mono.Cxxi project, in the original Mono.Cxxi source code.
  4. Under Project Options, Build, General, check Allow 'unsafe' code.
  5. Inside the TestMonoDroid project add a reference to the Cxxi project.
  6. Add the files Lib.cs and Wrapper.cs, previously obtained with Generator.exe, to TestMonoDroid.
  7. Add the libs folder, previously obtained with Ndk, to TestMonoDroid.
  8. For every libNative.so file go to Properties, Build, and set Build action to AndroidNativeLibrary.
  9. Modify Activity1.cs and add code for testing the library (see below for an example).
  10. Build All.
  11. From the Run menu, select Upload to Device.
  12. Start an emulator and once the upload is done launch the TestMonoDroid app.

    Some problems could arise in this phase, in that case these resources may be of help:

    MonoDroid: Not connecting to Emulator

    MonoDroid: Running out of space on fresh Virtual Device?

    Unsupported configuration: ... armeabi-v7a-emu.so in Mono for Android v4.0

This is an example of how to use the library:


// ...

using CxxiGeneratedNamespace.ClippingLibrary;

// ...

        private static string RunTest()
        {
            var w = new Wrapper();

            w.InitializeSubject();
            w.AddOuterRing();
            w.AddPoint(0, 0);
            w.AddPoint(0, 1000);
            w.AddPoint(1000, 1000);
            w.AddPoint(1000, 0);
            w.AddPoint(0, 0);
            w.AddInnerRing();
            w.AddPoint(100, 100);
            w.AddPoint(900, 100);
            w.AddPoint(900, 200);
            w.AddPoint(100, 200);
            w.AddPoint(100, 100);
            w.AddInnerRing();
            w.AddPoint(100, 700);
            w.AddPoint(900, 700);
            w.AddPoint(900, 900);
            w.AddPoint(100, 900);
            w.AddPoint(100, 700);

            w.InitializeClip();
            w.AddOuterRing();
            w.AddPoint(-20, 400);
            w.AddPoint(-20, 600);
            w.AddPoint(1020, 600);
            w.AddPoint(1020, 400);
            w.AddPoint(-20, 400);
            w.AddInnerRing();
            w.AddPoint(100, 450);
            w.AddPoint(900, 450);
            w.AddPoint(900, 550);
            w.AddPoint(100, 550);
            w.AddPoint(100, 450);

            w.ExecuteDifference();

            return GetOutputString(w);
        }

// ...

Note that rings must be closed and outer rings must be clockwise while inner rings must be anti-clockwise.

Not very choreographic but it runs:

Screenshot of the app being executed.

Conclusion

We have seen how to use a native library from a MonoDroid app. A more pragmatic approach would have excluded Cxxi in favour of a more mature solution (and in this specific case we could have avoided a lot of issues by using the C# version of Clipper). However Cxxi is an interesting project that can offer more than what we have seen here, and in the future it could become the standard solution for interoperability between C++ code and Mono so keep an eye on it.

Download

Support files (also contains Wrapper.h and Wrapper.cpp).

Notes

Tested with:
  • Android 3.1 (Emulated)
  • Android Ndk r7b
  • Boost 1.49.0
  • Cxxi kthompson-cxxi-1a9c3a8
  • GCC_XML cvs revision 1.135
  • Mono for Android 4.0.4.264524889 (Evaluation)
  • MonoDevelop 3.0.1
Feb 18, 2012

Trying to use Cgal in a C++ project with Common Language Runtime support (/clr) you may encounter a few problems.

First ...

If the following message appears when you start your application:

The application failed to initialize properly (0xc000007b).

The problem may be due to Boost.Thread, which is required by Cgal.

According to

http://lists.boost.org/Archives/boost/2009/04/151043.php

modify

$BOOST_ROOT/libs/thread/src/win32/tss_pe.cpp

in this way:


#if (_MSC_VER >= 1400)
//#pragma section(".CRT$XIU",long,read)
#pragma section(".CRT$XCU",long,read)
#pragma section(".CRT$XTU",long,read)
#pragma section(".CRT$XLC",long,read)
        __declspec(allocate(".CRT$XLC")) _TLSCB __xl_ca=on_tls_callback;
        //__declspec(allocate(".CRT$XIU"))_PVFV p_tls_prepare = on_tls_prepare;
        __declspec(allocate(".CRT$XCU"))_PVFV p_process_init = on_process_init;
        __declspec(allocate(".CRT$XTU"))_PVFV p_process_term = on_process_term;
#else

The __declspec lines were added in order to close this ticket (Severity - Cosmetic):

https://svn.boost.org/trac/boost/ticket/2199

Second ...

If the following assertion message appears at runtime:

Wrong rounding: did you forget the -frounding-math option if you use GCC (or -fp-model strict for Intel)?

Define CGAL_DISABLE_ROUNDING_MATH_CHECK and rebuild your project.

However, you should have seen this compiler warning:

warning C4996: '_controlfp_s': Direct floating point control is not supported or reliable from within managed code.

_controlfp_s is used directly by Cgal but, from MSDN:

This function is deprecated when compiling with /clr (Common Language Runtime Compilation) or /clr:pure because the common language runtime only supports the default floating-point precision.

So you should not expect identical results if you run the same project compiled with or without Common Language Runtime support.

Notes

Tested with Visual Studio 2010 using Cgal 3.9 and Boost 1.49.0 beta 1, x86 build, on Windows XP.

Nov 2, 2011

A quick overview of the different ways to call unmanaged APIs from managed code, with .Net and also with Mono.

The inspiration for this post came after reading a couple of articles. The first relating to SharpDX:

A new managed .NET/C# Direct3D 11 API generated from DirectX SDK headers

The second:

Techniques of calling unmanaged code from .NET and their speed

that in substance is on the same topic of this post but doesn't provide enough sample code, in particular for the final benchmark.

Native library

In the following examples we will use a function exported from a phantomatic library called Native.dll (Native.so for Mono on Unix/Linux), written in C and compiled with Cdecl calling convention.


//
// Native.h
//

void DoWithIntPointer(int a, int b, int* r);


//
// Native.c
//

#include "Native.h";

void DoWithIntPointer(int a, int b, int* r) {
    *r = a + b;
}

DoWithIntPointer simply calculates the sum of two integer but having parameters passed by value and by reference it will allow us to see some peculiarities.

Explicit P/Invoke

Let's start with a classic P/Invoke example:


//
// TestPInvoke.cs
//

using System.Runtime.InteropServices;

class TestPInvoke {    
    [DllImport(
        "Native.dll", 
        CallingConvention = CallingConvention.Cdecl
    )]
    private static extern void DoWithIntPointer(
        int a, 
        int b, 
        out int r
    );
    
    public static void Main() {
        int result = 0;
        DoWithIntPointer(1, 2, out result);
    }
}

The extern keyword tells the compiler that DoWithIntPointer is defined elsewhere while the DllImport attribute provides directions to trace it.

Implicit P/Invoke - C++/Cli

With C++/Cli we can write wrappers for native libraries with relative ease but it cannot be used with Mono. Here we have the C++/Cli wrapper for our Native library:


//
// NativeCppCliWrapper.cpp
//

#include "Native.h";

namespace NativeCppCliWrapper
{

using namespace System;
using namespace System::Runtime::InteropServices;

public ref class Wrapper {
public:
    static void CallDoWithIntPointer(
        Int32 a, 
        Int32 b, 
        [Out] Int32% r
    ) {
        int tmp;
        DoWithIntPointer(a, b, &tmp);
        r = tmp;
    }
};

}

After compiling, the wrapper can be used as any other assembly:


//
// TestCppCli.cs
//

using NativeCppCliWrapper;

class TestCppCli {
    public static void Main() {
        int result = 0;
        Wrapper.CallDoWithIntPointer(1, 2, out result);
    }
}

If we dig through the IL code generated by C++/Cli we can see that CallDoWithIntPointer invokes:


 IL_0004: call void modopt(
      [mscorlib]System.Runtime.CompilerServices.CallConvCdecl
   )  '<module>'::DoWithIntPointer(int32, int32, int32*)

And DoWithIntPointer is described by the following metadata:


.method assembly static pinvokeimpl("" lasterr cdecl)
 void modopt(
    [mscorlib]System.Runtime.CompilerServices.CallConvCdecl
    ) DoWithIntPointer (
      int32 '',
      int32 '',
      int32* ''
    ) native unmanaged preservesig 
{
 .custom instance void
 [mscorlib]System.Security.SuppressUnmanagedCodeSecurityAttribute::.ctor()
   = ( 01 00 00 00 )
}

Converted in C# (with IlSpy):


[SuppressUnmanagedCodeSecurity]
[DllImport("", 
    CallingConvention = CallingConvention.Cdecl, 
    SetLastError = true
)]
[MethodImpl(MethodImplOptions.Unmanaged)]
internal unsafe static extern void DoWithIntPointer(
    int, 
    int, 
    int*
);

Does this remind us of anything? Yes, it is very similar to the extern declaration that we have seen previously but among the differences we can note an attribute called SuppressUnmanagedCodeSecurity. MSDN tells us that:

This attribute is primarily used to increase performance; however, the performance gains come with significant security risks.

Security risks apart it can be used with explicit P/Invoke, it is not an exclusive of C++/Cli.

In other situations the native code is called in a more sophisticated way, for example if we poke inside IL code of SlimDX we can find things like this:


.method public hidebysig 
 instance valuetype SlimDX.Result Optimize () cil managed 
{
 // Method begins at RVA 0xd0824
 // Code size 25 (0x19)
 .maxstack 3

 IL_0000: ldarg.0
 IL_0001: call instance valuetype 
  IUnknown* SlimDX.ComObject::get_UnknownPointer()
 IL_0006: dup
 IL_0007: ldind.i4
 IL_0008: ldc.i4.s 68
 IL_000a: add
 IL_000b: ldind.i4
 IL_000c: calli System.Int32 modopt(
   System.Runtime.CompilerServices.IsLong
  ) modopt(
   System.Runtime.CompilerServices.CallConvStdcall
  )(System.IntPtr)
 IL_0011: ldnull
 IL_0012: ldnull
 IL_0013: call valuetype SlimDX.Result 
  SlimDX.Result::Record
  <class SlimDX.Direct3D11.Direct3D11Exception>(
   int32, object, object
  )
 IL_0018: ret
}

The calli instruction is used to invoke a native method given the address of the method itself. We will see how to take advantage of calli without C++/Cli in the last example.

Dynamic P/Invoke

In order to employ the previos techniques tha native library must be know at compile time, while it must be located in a certain path at runtime. With dynamic P/Invoke we can obtain a greater degree of flexibility.

In the next example we will benefit by an assembly called CSLoadLibrary but slightly modified, in particular to run on Unix/Linux via Mono (see the download link at the end of this post for the modified version). CSLoadLibrary contains an UnmanagedLibrary class that provides access to native libraries through standard Windows APIs (LoadLibrary, GetProcAddress, FreeLibrary) or Unix/Linux counterparts (dlopen, dlsym, dlclose).


//
// TestDelegate.cs
//

using System.Runtime.InteropServices;
using CSLoadLibrary;

class TestDelegate {
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void DelegateWithIntPointer(
        int a, 
        int b, 
        out int r
    );
    
    public static void Main() {
        UnmanagedLibrary nativeLib = 
            new UnmanagedLibrary(
                "Native"
            );
        
        DelegateWithIntPointer doWithIntPointer = 
            nativeLib.GetUnmanagedFunction
                <DelegateWithIntPointer>(
                "DoWithIntPointer"
            );

        int result = 0;
        doWithIntPointer(1, 2, out result);
    }
}

In practice, given the name of the native library to load, an UnmanagedLibrary object is instantiated. Then, with GetUnmanagedFunction we obtain a delegate pointing to our native function, DoWithIntPointer. Naturally the signature of the delegate must match the signature of the native function.

Dynamic P/Invoke – Explicit P/Invoke

This time, instead of using CSLoadLibrary, the delegate is created via Reflection, replicating the extern declaration shown in the Explicit P/Invoke example.


//
// TestDynamicS.cs
//

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Security;

class TestDynamicS {
    delegate void DelegateWithIntPointer(
        int a, 
        int b, 
        out int r
    );
    
    public static void Main() {
        DelegateWithIntPointer doWithIntPointer = 
            GetDynamicSDelegate
                <DelegateWithIntPointer>(
                "Native", 
                "DoWithIntPointer", 
                CallingConvention.Cdecl
            );
            
        int result = 0;
        doWithIntPointer(1, 2, out result);
    }

    private static TDelegate GetDynamicSDelegate
        <TDelegate>(
        string libraryName, 
        string entryPoint, 
        CallingConvention callingConvention
    ) where TDelegate : class 
    {
        Type delegateType = typeof(TDelegate);
        MethodInfo invokeInfo = delegateType.GetMethod("Invoke");
        // Gets the return type for the P/Invoke method.
        Type invokeReturnType = invokeInfo.ReturnType;
        // Gets the parameter types for the P/Invoke method.
        ParameterInfo[] invokeParameters = 
            invokeInfo.GetParameters();
        Type[] invokeParameterTypes = 
            new Type[
                invokeParameters.Length
            ];
        for (int i = 0; i < invokeParameters.Length; i++) {
            invokeParameterTypes[i] = 
                invokeParameters[i].ParameterType;
        }

        // Defines an assembly with a module and a type.
        AssemblyName assemblyName = 
            new AssemblyName(
                "TestAssembly"
            );
        AssemblyBuilder assemblyBuilder = 
            AppDomain.CurrentDomain.DefineDynamicAssembly(
                assemblyName, 
                AssemblyBuilderAccess.Run
            );
        ModuleBuilder moduleBuilder = 
            assemblyBuilder.DefineDynamicModule(
                "TestModule"
            );
        TypeBuilder typeBuilder = 
            moduleBuilder.DefineType(
                "TestDynamicS"
            );
            
        //Defines a P/Invoke method called Invoke.
        MethodBuilder methodBuilder = 
            typeBuilder.DefinePInvokeMethod(
                "Invoke", 
                libraryName + ".dll", 
                entryPoint, 
                MethodAttributes.Public | 
                    MethodAttributes.Static | 
                    MethodAttributes.PinvokeImpl,
                CallingConventions.Standard, 
                invokeReturnType, 
                invokeParameterTypes, 
                callingConvention, 
                CharSet.Ansi
            );
        methodBuilder.SetImplementationFlags(
            methodBuilder.GetMethodImplementationFlags() | 
            MethodImplAttributes.PreserveSig
        );
        
        // Adds SuppressUnmanagedCodeSecurityAttribute to 
        // the method.
        Type attributeType = 
            typeof(
                SuppressUnmanagedCodeSecurityAttribute
            );
        ConstructorInfo attributeConstructorInfo = 
            attributeType.GetConstructor(
                new Type[] {}
            );
        CustomAttributeBuilder attributeBuilder = 
            new CustomAttributeBuilder(
                attributeConstructorInfo, 
                new object[] {}
            );
        methodBuilder.SetCustomAttribute(attributeBuilder);

        // Finishes the type.
        Type newType = typeBuilder.CreateType();
        
        object tmp = 
            (object)Delegate.CreateDelegate(
                delegateType, 
                newType.GetMethod("Invoke")
            );
        return (TDelegate)tmp;
    }
}

Though we are adding the SuppressUnmanagedCodeSecurity attribute, it is not essential.

Dynamic P/Invoke - Emit Calli

The last example is more complicated. Here again, we make use of UnmanagedLibrary to get the native function's address. Then, through Reflection, we create a dynamic method which internally passes the address of the native function to calli, the instruction seen previously.


//
// TestCalli.cs
//

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using CSLoadLibrary;

class TestCalli {
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void DelegateWithIntPointer(
        int a, 
        int b, 
        out int r
    );
    
    public static void Main() {        
        UnmanagedLibrary nativeLib = 
            new UnmanagedLibrary(
                "Native"
            );
        IntPtr nativeMethodAddress = 
            nativeLib.GetUnmanagedFunctionAddress(
                "DoWithIntPointer"
            );
        DelegateWithIntPointer doWithIntPointer = 
            GetCalliDelegate
                <DelegateWithIntPointer>(
                nativeMethodAddress
            );
        int result = 0;
        doWithIntPointer(1, 2, out result);
    }
    
    private static TDelegate GetCalliDelegate
        <TDelegate>(
        IntPtr methodAddress
    ) where TDelegate : class
    {
        Type delegateType = typeof(TDelegate);
        MethodInfo invokeInfo = delegateType.GetMethod("Invoke");
        // Gets the return type for the dynamic method and calli.
        // Note: for calli, a type such as System.Int32& must be
        // converted to System.Int32* otherwise the execution 
        // will be slower.
        Type invokeReturnType = invokeInfo.ReturnType;
        Type calliReturnType = 
            GetPointerTypeIfReference(
                invokeInfo.ReturnType
            );
        // Gets the parameter types for the dynamic method 
        // and calli.
        ParameterInfo[] invokeParameters = 
            invokeInfo.GetParameters();
        Type[] invokeParameterTypes = 
            new Type[
                invokeParameters.Length
            ];
        Type[] calliParameterTypes = 
            new Type[
                invokeParameters.Length
            ];
        for (int i = 0; i < invokeParameters.Length; i++) {
            invokeParameterTypes[i] = 
                invokeParameters[i].ParameterType;
            calliParameterTypes[i] = 
                GetPointerTypeIfReference(
                    invokeParameters[i].ParameterType
                );
        }

        // Defines the dynamic method.
        DynamicMethod calliMethod = 
            new DynamicMethod(
                "CalliInvoke", 
                invokeReturnType, 
                invokeParameterTypes, 
                typeof(TestCalli), 
                true
            );
            
        // Gets an ILGenerator.
        ILGenerator generator = calliMethod.GetILGenerator();   
        // Emits instructions for loading the parameters into 
        // the stack.
        for (int i = 0; i < calliParameterTypes.Length; i++) {
            if (i == 0) {
                generator.Emit(OpCodes.Ldarg_0);
            } else if (i == 1) {
                generator.Emit(OpCodes.Ldarg_1);
            } else if (i == 2) {
                generator.Emit(OpCodes.Ldarg_2);
            } else if (i == 3) {
                generator.Emit(OpCodes.Ldarg_3);
            } else {
                generator.Emit(OpCodes.Ldarg, i);
            }
        }
        // Emits instruction for loading the address of the
        //native function into the stack.
        switch (IntPtr.Size) {
            case 4:
                generator.Emit(
                    OpCodes.Ldc_I4, 
                    methodAddress.ToInt32()
                );
                break;
            case 8:
                generator.Emit(
                    OpCodes.Ldc_I8, 
                    methodAddress.ToInt64()
                );
                break;
            default:
                throw new PlatformNotSupportedException();
        }
        // Emits calli opcode.
        generator.EmitCalli(
            OpCodes.Calli, 
            CallingConvention.Cdecl,
            calliReturnType, 
            calliParameterTypes
        );
        // Emits instruction for returning a value.
        generator.Emit(OpCodes.Ret);

        object tmp = 
            (object)calliMethod.CreateDelegate(
                delegateType
            );
        return (TDelegate)tmp;
    }
    
    private static Type GetPointerTypeIfReference(Type type) {
        if (type.IsByRef) {
            return Type.GetType(type.FullName.Replace("&", "*"));
        }
        return type;
    }
}

The method GetPointerTypeIfReference converts the type of a parameter like Int32& to Int32*, otherwise calli executes correctly but results slower.

Benchmark

Hardware: CPU Intel Core i3-2310M 2.1 GHz, RAM 4 GB.
Software: VMware Player 3.1.4. on Windows 7 x64.

[ms] x 100,000,000 iterations

SUC means that the test has been executed with the SuppressUnmanagedCodeSecurity attribute.

void DoWithIntPointer(int a, int b, int* r)
.Net 4 Mono 2.10.6 Mono 2.10.5 Mono 2.6.7
Windows Windows Ubuntu Debian
XP x32 XP x32 Oneiric amd64 squeeze i386
Expl. P/I 8117 12001 2346 3657
Expl. P/I SUC 3681 3485 2344 3708
C++/Cli 4760 . . .
Dyn. P/I 19309 68303 2603 4431
Dyn. P/I SUC 7361 59718 2615 4514
Dyn. P/I Expl. SUC 4497 4419 2480 4398
Dyn. P/I Calli 4136 4249 1885 4353
void DoWithDoublePointer(double a, double b, double* r)
.Net 4 Mono 2.10.6 Mono 2.10.5 Mono 2.6.7
Windows Windows Ubuntu Debian
XP x32 XP x32 Oneiric amd64 squeeze i386
Expl. P/I 8215 14328 2194 4819
Expl. P/I SUC 4203 6658 2207 4826
C++/Cli 5576 . . .
Dyn. P/I 22881 68789 3658 7260
Dyn. P/I SUC 7478 60425 3780 7251
Dyn. P/I Expl. SUC 4247 6627 3655 7294
Dyn. P/I Calli 3925 3766 3050 6766

Conclusion

The above results seem a bit weird and the only certain thing appears to be that Dynamic P/Invoke is always faster if we resort to calli. Anyway, we have seen different ways to invoke unmanaged code from managed code, each with its own pros and cons, and if necessary we can use them or conduct further tests.

Download

Benchmark source code.

Other resources

About interoperability:

About calli:

Apr 17, 2011

An experiment about nine open source polygon clipping libraries written in C/C++ that have been benchmarked after being wrapped for use with .Net.

** LAST UPDATE - MAR 04, 2013 **

Note that there could be errors and that no consideration is made about the robustness or optimization of the libraries so this benchmark must be taken with a grain of salt.

The nine libraries

Library Version License
Boost.Geometry Boost 1.53 Boost v1.0
Boost.Polygon Boost 1.53 Boost v1.0
Bop 1.2 (from the site and zip file) Public domain
Cgal 4.1 Various (mainly GPL and LGPL) / Commercial
Clipper 5.1.2 Boost v1.0
Geos 3.3.7 LGPL v2.1
Gpc 2.32 Free for non-commercial use / Commercial
KBool 2.1 GPL v3 / Commercial
TerraLib svn 10190 LGPL v2.1

The guests

For comparative purposes other libraries have been used during the benchmark.

Library Version License
PolyBoolean.NET 2.0.0 (demo) Commercial
SQL Server System CLR Types 2012 2011.110.2100.60 Proprietary

Wrappers

The open source libraries compared in this benchmark are written in C or C++ and are wrapped with a light C++/Cli wrapper that exposes only the polygon set operations (union, difference, intersection, disjoint-union).

C++/Cli provides a relatively easy solution to interoperability with native libraries but, actually, renders the wrappers tied to the Windows platform.

Hardware and software

CPU Intel Core i5 3570k
RAM 8 GB
OS Windows 8 x64
SDK Windows 8 Sdk + Visual Studio 2012 Express
Settings x64 release build, /O2

Notes

  • Boost.Geometry, also known as Ggl, can be used with Mpir (a Gmp fork) or with TTMath, for more precision but with a significant slowdown. The vanilla version is used.
  • Boost.Polygon, also known as Gtl, can be used with Mpir with no speed penalty. The vanilla version is used.
  • Bop has two implementations, one depends on Cgal. The version with no dependencies is used.
  • Cgal is built with Boost (1.53), Mpir (2.6.0), Mpfr (svn 8450). A Simple_cartesian<double> kernel is used given the fact that it seems faster than other kernel types.
  • KBool source has been patched to avoid creating the file keygraphfile.key.
  • PolyBoolean.c20.NET is used instead of PolyBoolean.c30.NET because it is faster but coordinates values are restricted to a 20 bit range, so all tests comply to this constraint. Furthermore the demo version of PolyBoolean throws an exception when the number of vertices returned is a multiple of 7.
  • Boost.Geometry, Geos, TerraLib and SQL Server System Types require closed polygons. The polygons are closed for these libraries only.
  • An intersection operation is executed in all tests.

Notes about the charts

  • In order to show all the results click the grayed out legend items.
  • Linear charts can be zoomed by dragging the mouse on the plot area.

Benchmark - Classic

Classic polygon example

The polygons used in this test are the same ones used to benchmark PolyBoolean (C++ version) and Clipper on their websites (in particular see the test with 174239 vertices) and are extracted from True Type Font contours.

Charts require a modern browser and Javascript enabled.

Clipper is the fastest followed by Boost.Geometry, Sql Server ST and TerraLib.

Clipper is faster than PolyBoolean and PolyBoolean is faster than Gpc. This result seems coherent with the same benchmark on the PolyBoolean and Clipper websites.

Benchmark - Known

Known polygon example

In this test one operand is a polygon that resembles a gear while the other operand is formed by a set of concentric rings. During the test the gear teeth are increased in number and length as well as the number of rings. The polygons are composed of lines with various slopes and at the same time the theoretical number of intersections is known.

Charts require a modern browser and Javascript enabled.

In the long term, Boost.Geometry is the fastest, then Sql Server ST and Boost.Polygon come. However Clipper starts better.

Note that from a certain point onward Bop always crashes. Moreover Cgal was stopped before the end because it was taking too long to finish.

Benchmark - Random

Random polygon example

In this test the operands are polygons obtained subtracting random triangles from a square and Boost.Polygon is used to prepare those operands.

Charts require a modern browser and Javascript enabled.

Bop is the fastest followed by Sql Server ST, Boost.Geometry and TerraLib.

Cgal is excluded from this test because too much susceptible, in particular it requires simple polygons. During other preparatory tests, Bop, PolyBoolean and Sql Server ST have crashed towards the end.

Benchmark - Grid

Grid polygon example

In this test the operands are squares containing a grid of holes. During the test the total number of input polygons and vertices is constant but the holes are positioned in order to obtain an increasing number of intersections.

Charts require a modern browser and Javascript enabled.

The interesting thing to note here is that some libraries such as Boost.Geometry and Geos give the best performance when the number of intersections increases (Boost.Geometry is one of the slowest at the start and one of the fastest at the end). This is a factor to consider in the results of the previous tests, in particular the benchmark with random polygons (in practice, in that test, if the size of the outer square is increased and the other parameters are mantained we can see a very different progression for certain libraries).

x32 vs x64

Benchmark - Classic, a comparison with the same libraries built for an x32 release.
Charts require a modern browser and Javascript enabled.

Certain libraries seem to be faster when built for an x32 system, in particular Cgal gains over 100 seconds. Previous versions of this benchmark, on different hardware and operating system (and with different operands), did not show this aspect.

Gpc - C# wrapper vs C++/Cli wrapper

Gpc has a C# wrapper and here is a comparison with the C++/Cli wrapper. The input polygons are obtained as in Benchmar - Known.

Charts require a modern browser and Javascript enabled.
Charts require a modern browser and Javascript enabled.

Not a big difference between a P/Invoke and a C++/Cli wrapper but P/Invoke is a portable solution that can be used with Mono.

Clipper - C# vs C++/Cli wrapper

Clipper has a C# implementation and here is a comparison with the wrapped C++ implementation. The input polygons are obtained as in Benchmar - Known.

Charts require a modern browser and Javascript enabled.
Charts require a modern browser and Javascript enabled.

The C# implementation is slower but can be used directly with .Net and Mono.

Downloads

Source code (Wrappers - C++/Cli + Benchmark - VB.Net)

Source code for the libraries must be downloaded from the respective sites.

IMPORTANT UPDATES

MARCH 04, 2013

- Added: Bop, Cgal, TerraLib.

AUGUST 14, 2011

- Added: Geos, SQL Server System Types.

JULY 23, 2011

- Added: Boost.Geometry.