Interoperability with native code via the .NET platform

Interoperability necessary to use existing libraries written in C, C++ or other languages ​​that perform important or high-performance functions. This opens up the possibility of integrating .NET applications with various system components and devices that can only be accessed through native APIs.

At the heart of interoperability is the interaction of managed code. Managed code runs under the CLR, a .NET virtual machine that provides features such as garbage collection, type safety, and other abstractions. But native code is compiled directly into platform-specific machine code and executed by the OS without intermediate layers, which provides high performance and direct access to system resources…

Basic techniques for calling native functions

Platform Invoke (P/Invoke)

Platform Invoke is a mechanism that Allows .NET code to call functions found in native DLLs. It is used to use existing C/C++ libraries without rewriting them in .NET. To implement P/Invoke, you must use the System and System.Runtime.InteropServices namespaces.

To call a native function via P/Invoke, you must:

  1. Import a function using an attribute DllImport, which indicates which DLL the function is in. This attribute also allows you to set the character encoding method (CharSet), as well as other call parameters.

  2. Define the function signature in accordance with its native declaration.

Example of using P/Invoke to call a function MessageBox from the library user32.dllwhich displays a message box:

using System;
using System.Runtime.InteropServices;

public class Win32 {
    [DllImport("user32.dll", CharSet=CharSet.Auto)]
    public static extern IntPtr MessageBox(int hWnd, String text, String caption, uint type);
}

public class HelloWorld {
    public static void Main() {
        Win32.MessageBox(0, "Hello World", "Platform Invoke Sample", 0);
    }
}

MessageBox called with parameters that specify that the message box has no parent window and contains the text “Hello World” with the title “Platform Invoke Sample”.

P/Invoke automatically handles many standard data types, but to work with them correctly, it is important to correctly declare the function parameters. For strings and structures that require special attention when transferred between managed and native code, use special. data marshaling attributes.

Despite its usefulness, P/Invoke has a number of limitations and potential problems:

  • The need for an exact match between data types and function signatures.

  • Possible run-time errors due to data inconsistencies or misuse of resources.

C++/CLI

C++/CLI is a C++ extension, designed for creating and managing managed code within the .NET Framework and .NET Core. It allows you to combine managed and native code in one application, making it easier to work with existing C++ libraries and .NET assemblies.

To get started with C++/CLI, you need to install the C++/CLI support component through the Visual Studio installer. After installing Visual Studio and selecting a C++ workload, the C++/CLI component can be added through the menu Modify in the Visual Studio installer. For Visual Studio 2019 and later, select support for build tools v142 and later.

C++/CLI projects can be created using Visual Studio project templates. For example, you can create a CLR Console App that already includes the necessary references to assemblies and source code files. This is the most ideal way for beginners to learn programming without having to deal with a graphical user interface.

C++/CLI creates managed classes with keyword ref class, which allows classes to be part of the .NET managed heap. Such classes can contain both managed and native data types, making C++/CLI a powerful tool for cross-language integration.

An example of creating a wrapper for a native class in C++/CLI:

#pragma once
using namespace System;

namespace CLI {
    template<class T>
    public ref class ManagedObject {
    protected:
        T* m_Instance;

    public:
        ManagedObject(T* instance) : m_Instance(instance) { }

        virtual ~ManagedObject() {
            if (m_Instance != nullptr) {
                delete m_Instance;
            }
        }

        !ManagedObject() {
            if (m_Instance != nullptr) {
                delete m_Instance;
            }
        }

        T* GetInstance() {
            return m_Instance;
        }
    };
}

C++/CLI allows you to use Windows Forms and WPF to create a graphical user interface in .NET applications. To do this, you need to add the file to the project .vcxproj links to relevant Windows Desktop libraries such as Microsoft.WindowsDesktop.App.WindowsForms or Microsoft.WindowsDesktop.App.WPF.

Memory management when interacting with native code in .NET

SafeHandle

SafeHandle is an abstract base class in the namespace System.Runtime.InteropServices, which provides a wrapper for working with operating system resources such as files, the registry, or network connections. primary goal SafeHandle — ensure secure management of such resources, automatically closing them when they are no longer needed.

An example of a class that uses SafeHandle for safe work with files:

using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.IO;

public class MyFileReader : IDisposable
{
    private MySafeFileHandle _handle;

    public MyFileReader(string fileName)
    {
        _handle = NativeMethods.CreateFile(
            fileName,
            FileAccess.Read,
            FileShare.Read,
            IntPtr.Zero,
            FileMode.Open,
            0,
            IntPtr.Zero
        );

        if (_handle.IsInvalid)
            throw new System.ComponentModel.Win32Exception();
    }

    public void Dispose()
    {
        _handle.Dispose();
    }
}

internal static class NativeMethods
{
    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern MySafeFileHandle CreateFile(
        string fileName,
        FileAccess access,
        FileShare shareMode,
        IntPtr securityAttributes,
        FileMode creationDisposition,
        int flagsAndAttributes,
        IntPtr templateFile
    );
}

public class MySafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    public MySafeFileHandle() : base(true) {}

    protected override bool ReleaseHandle()
    {
        return CloseHandle(handle);
    }

    [DllImport("kernel32.dll")]
    private static extern bool CloseHandle(IntPtr handle);
}

MySafeFileHandle – derived class from SafeHandle, which determines how to release the resource. In the constructor MyFileReader a file handle is created, which is then managed SafeHandleensuring its correct closure.

GCHandle

GCHandle provides a way to manage managed objects from native code. It is mainly used to prevent an object from being garbage collected while it is being used in native code, and also allows you to “pin” an object in memory to prevent it from being moved by the garbage collector.

Usage example GCHandle to pin an object and prevent it from moving around in memory:

using System;
using System.Runtime.InteropServices;

public class App
{
    public static void Main()
    {
        Run();
    }

    public static void Run()
    {
        byte[] data = new byte[1024];
        GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
        try
        {
            IntPtr ptr = handle.AddrOfPinnedObject();
            // Передача ptr в нативный метод
        }
        finally
        {
            if (handle.IsAllocated)
                handle.Free();
        }
    }
}

Array data is fixed in memory by GCHandle.Alloc. This ensures that the array will not be moved or garbage collected while it is being referenced by native code. Note the use of block try/finally to secure release GCHandle after finishing working with it, which prevents memory leaks.

Blittable and Non-Blittable types

Blittable types in .NET, these are data types that have the same representation in managed and unmanaged memory, allowing them to be transferred between managed and native code without the need for special processing by the interop marshaler. These types do not require conversion when marshaling and provide direct memory access, making them ideal for performance operations that require low latency.

Examples of blittable types include primitives such as int, double, floatand System.IntPtr And System.UIntPtr. Also considered blittable are one-dimensional arrays of these primitive types.

Non-Blittable types in .NET, these are types that require special data conversion when passed between managed and unmanaged code because of the differences in how data is represented in managed and unmanaged memory. These types require more complex processing and may reduce performance due to additional load on the interop marshaler.

Non-blittable types include string, bool, char, as well as all classes, object types, and delegates. For example, type System.String in managed code represents a Unicode string and must be converted to ANSI or another string format in unmanaged code before being passed on.

To control the marshaling behavior of non-blittable types, you can use attributes such as MarshalAsto specify how the data type should be converted when transferred. For example, for strings you can specify whether they should be marshaled as LPStr (ANSI) LPWStr (Unicode), or BStr (used in COM).

Let's work with Blittable and Non-Blittable types in .NET:

using System;
using System.Runtime.InteropServices;

class Program
{
    // Blittable структура
    [StructLayout(LayoutKind.Sequential)]
    struct Point
    {
        public int x;
        public int y;
    }

    // Non-Blittable класс
    class Data
    {
        public int value;
        public string message;
    }

    // метод для маршалинга Blittable структуры
    static void MarshalBlittable()
    {
        Point point = new Point { x = 10, y = 20 };
        IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf<Point>());
        Marshal.StructureToPtr(point, ptr, false);

        // для демонстрации выводим координаты из неуправляемой памяти
        Point marshaledPoint = Marshal.PtrToStructure<Point>(ptr);
        Console.WriteLine($"Marshalled Point: ({marshaledPoint.x}, {marshaledPoint.y})");

        Marshal.FreeHGlobal(ptr);
    }

    // метод для маршалинга Non-Blittable класса
    static void MarshalNonBlittable()
    {
        Data data = new Data { value = 42, message = "Hello, World!" };
        IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf<Data>());

        // код маршалинга Non-Blittable класса будет сложнее,
        // поскольку требуется маршалинг для каждого его поля
        Marshal.WriteInt32(ptr, data.value);
        IntPtr messagePtr = Marshal.StringToHGlobalAnsi(data.message);
        Marshal.WriteIntPtr(ptr + Marshal.OffsetOf<Data>("message").ToInt32(), messagePtr);

        // для демонстрации выводим данные из неуправляемой памяти
        int value = Marshal.ReadInt32(ptr);
        string message = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(ptr + Marshal.OffsetOf<Data>("message").ToInt32()));
        Console.WriteLine($"Marshalled Data: value = {value}, message = \"{message}\"");

        Marshal.FreeHGlobal(ptr);
        Marshal.FreeHGlobal(messagePtr);
    }

    static void Main()
    {
        Console.WriteLine("Marshalling Blittable Types:");
        MarshalBlittable();
        Console.WriteLine();

        Console.WriteLine("Marshalling Non-Blittable Types:");
        MarshalNonBlittable();
    }
}

Created a Blittable structure Point and Non-Blittable class Dataand then demonstrate marshaling these types into unmanaged memory using the methods Marshal.StructureToPtr And Marshal.WriteInt32, respectively. It also shows how to free allocated memory using Marshal.FreeHGlobal.


We looked at various techniques for calling native functions, such as P/Invoke and C++/CLI, as well as memory management techniques. Progress does not stand still, and future versions of .NET may offer even more advanced tools for working with native code.

Finally, I would like to recommend you the free lessons of the C# Developer course. Registration is available via the links below:

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *