Skip to content

Calling Native Code

RoqueDeicide edited this page Dec 15, 2014 · 1 revision

P/Invoke

Platform Invoke is quite convenient way of invocation since it allows automatic data marshalling. It requires native code to be represented by a function declared with __declspec(dllexport) attribute. In order to expose it to managed code, a wrapper function must be declared in any class as a static function with extern keyword and DllImport attribute that designates calling convention, location (name of the external DLL where native function is defined or __Internal if native function must be looked up inside the process).

It is highly recommended to declare native functions that are to be exported as C functions. This will prevent compiler from changing the name of the function.

Bear in mind that P/Invoke is extremely inefficient with resources, don't use it in situations where high performance is required. Also not that extra managed objects can be created on each call, which means that you need avoid them at all costs when working with loops of indefinite length (game loop for instance).

Sample code:

C++ code:

extern "C"
{
    __declspec(dllexport) const char *GetUtf8Text(const wchar_t *utf16text)
    {
        return DoSomeStuff(utf16text);
    }
}

C# code:

internal static class TextUtils
{
    [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
    [return: MarshalAs(UnmanagedType.LPStr)]
    extern internal static string  GetUtf8Text([MarshalAs(UnmanagedType.LPWStr)] string utf16text);
}

Internal Calls

Internal calls are a feature of Mono embedding interface that allows native functions be invoked with very little overhead.

They provide high performance gain in most situations, however they do require some extra bookkeeping to be done on native side.

  1. Declare native function as a normal global or static function.
  2. Register the pointer to the function as internal call.

Sample Code:

C++ declaration:

// Global function.
__int64 SumUp(int *numbers, int numberCount)
{
    __int64 sum = 0;
    for(int i = 0; i < numberCount; i++)
    {
        sum += numbers[i];
    }
    return sum;
}
// Static function.
struct Util
{
    int CharCount(mono::string text);
};
int Util::CharCount(mono::string text)
{
    const char *ntString = ToNativeString(text);
    int charCount = strlen(ntString);
    delete ntString;
    return charCount;
}
// Using IMonoInterop.
struct ExampleInterop : public IMonoInterop
{
	virtual const char *GetNameSpace();
	virtual const char *GetName();
	virtual void OnRunTimeInitialized();
	static void IndicationPrint();
};
const char *ExampleInterop::GetNameSpace() { return "Internals"; }
const char *ExampleInterop::GetName() { return "NativeMethods"; }
void ExampleInterop::OnRunTimeInitialized()
{
    REGISTER_METHOD(IndicationPrint);
}
void ExampleInterop::IndicationPrint()
{
    CryLogAlways("Internal call was invoked.");
}

C++ internal call registration:

// When setting up a list of listeners to pass to InitializeCryCil.
listeners.Add(new ExampleInterop());

// In GameStartup::Init after initialization of CryCIL
MonoEnv->AddInternalCall("Internals", "NativeMethods", "Sum", SumUp);
MonoEnv->AddInternalCall("Internals", "NativeMethods", "CharCount", Util::CharCount);

C# methods declaration

using System.Runtime.CompilerServices;

namespace Internals
{
    internal static class NativeMethods
    {
        [MethodImpl(MethodImplOptions.InternalCall)]
        internal static extern long Sum(IntPtr numbersPtr, int numberCount);
        [MethodImpl(MethodImplOptions.InternalCall)]
        internal static extern int CharCount(string text);
        [MethodImpl(MethodImplOptions.InternalCall)]
        internal static extern void IndicationPrint();
    }
}

C# methods usage

namespace Internals
{
    internal static class UsageClass
    {
        internal unsafe static void UseNativeMethods()
        {
            //
            // A simple indication print.
            //
            NativeMethods.IndicationPrint();
            //
            // Sum the numbers.
            //
            
            // Get the array.
            int[] numbers = GetNumbers();        // Just some function that gives us some numbers.
            // Marshal the numbers to the unmanaged memory
            // Allocate memory.
            IntPtr numbersPtr = Marshal.AllocHGlobal(numbers.Length * sizeof(int));
            // Copy numbers to unmanaged memory.
            Marshal.Copy(numbers, 0, numbersPtr, numbers.Length);
            // Invoke the method and print the sum to the log.
            Console.WriteLine(NativeMethods.Sum(numbersPtr, numbers.Length));
            //
            // Count the numbers.
            //
            Console.WriteLine(NativeMethods.CharCount("Sample String"));
        }
    }
}
Clone this wiki locally