Platform Invoke (P/Invoke)

P/Invoke is a technology that allows you to call c/c++ function or function from a dll library from your managed code. It’s divided into two namespace System and System.Runtime.InteropServices. you only need these namespaces to call external c/c++ function.

why do we need to call c/c++ function inside c#

  • It’s more efficient than writing your own in c#
  • decreases code repetition

Windows Example

Here is an example of how you can use P/Invoke to call function from user32.dll.

using System;
using System.Runtime.InteropServices;

public partial class Program
{
    // Import user32.dll (containing the function we need) and define
    // the method corresponding to the native function.
    [LibraryImport("user32.dll", StringMarshalling = StringMarshalling.Utf16, SetLastError = true)]
    private static partial int MessageBoxW(IntPtr hWnd, string lpText, string lpCaption, uint uType);

    public static void Main(string[] args)
    {
        // Invoke the function as a regular managed method.
        MessageBoxW(IntPtr.Zero, "Command-line message box", "Attention!", 0);
    }
}

what happening in the snippet:

  • we have imported System and System.Runtime.InteropServices which contains the necessary attributes to call a function in a dll file.
  • the library user32.dll will be loaded at runtime and it will complete the implementation of MessageBoxW function. ( for those of who don’t know what the function do: it’s just showing a prompt to the user). keep in mind that this example is windows specific, it will not work on Linux or MacOS.
  • you have probably noticed that we doing StringMarshalling. ( Marshalling is the process of transforming types when they need to cross between c# to native code (c/c++ or dll))
  • you must define the c# function with the same signature of the c counterpart.

MacOS Example

using System;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
    public static partial class Program
    {
        // Import the libSystem shared library and define the method
        // corresponding to the native function.
        [LibraryImport("libSystem.dylib")]
        private static partial int getpid();

        public static void Main(string[] args)
        {
            // Invoke the function and get the process ID.
            int pid = getpid();
            Console.WriteLine(pid);
        }
    }
}
  • In the example we are loading libSystem.dylib library and calling a getpid() function inside that library.

Linux example

using System;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
    public static partial class Program
    {
        // Import the libc shared library and define the method
        // corresponding to the native function.
        [LibraryImport("libc.so.6")]
        private static partial int getpid();

        public static void Main(string[] args)
        {
            // Invoke the function and get the process ID.
            int pid = getpid();
            Console.WriteLine(pid);
        }
    }
}

Invoking managed code from unmanaged code

c# allows us to pass a callback function to unmanaged code which will be called when a particular event gets triggered. The closes thing to a function pointer in managed code is a delegate. keep in mind that you have to create the delegate which has the same signature as expected in the dll function.

Here is an example:

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    public static partial class Program
    {
        // Define a delegate that corresponds to the unmanaged function.
        private delegate bool EnumWC(IntPtr hwnd, IntPtr lParam);

        // Import user32.dll (containing the function we need) and define
        // the method corresponding to the native function.
        [LibraryImport("user32.dll")]
        private static partial int EnumWindows(EnumWC lpEnumFunc, IntPtr lParam);

        // Define the implementation of the delegate; here, we simply output the window handle.
        private static bool OutputWindow(IntPtr hwnd, IntPtr lParam)
        {
            Console.WriteLine(hwnd.ToInt64());
            return true;
        }

        public static void Main(string[] args)
        {
            // Invoke the method; note the delegate as a first parameter.
            EnumWindows(OutputWindow, IntPtr.Zero);
        }
    }
}

I hope you learned something new today!