C# and C++ Interop using P/Invoke

Robin Steiner
01/02/2025

Interoperability between C# and C++ is often necessary when leveraging existing C++ libraries or accessing platform-specific functionalities. Platform Invoke (P/Invoke), facilitated by the DllImport attribute, provides a mechanism for C# code to call functions in unmanaged code, such as C++ DLLs or shared libraries. This guide outlines the process and key considerations.

The C++ Library

The C++ library serves as the foundation for interoperability. The following example demonstrates a simple C++ function intended for use from C#:

// my_cpp_library.h
#ifndef MY_CPP_LIBRARY_H
#define MY_CPP_LIBRARY_H

extern "C" { // Crucial for C# interop
    __declspec(dllexport) int Add(int a, int b); // Windows-specific; see below for cross-platform
}

#endif

// my_cpp_library.cpp
#include "my_cpp_library.h"

int Add(int a, int b) {
    return a + b;
}

Key elements of the C++ code:

  • extern "C": This directive is essential for ensuring compatibility with C#. It instructs the compiler to use the C naming convention, which is understood by the .NET runtime.
  • __declspec(dllexport): This attribute (Windows-specific) makes the function available for external use. For a more robust, cross-platform approach, it's recommended to use a .def file (on Windows) or compiler flags (on Linux/macOS) during the build process. For example, using GCC:
g++ -shared -o my_cpp_library.so my_cpp_library.cpp -fPIC # Linux/macOS
g++ -shared -o my_cpp_library.dll my_cpp_library.cpp -Wl,--def,my_cpp_library.def # Windows using a .def file

And my_cpp_library.def would contain:

LIBRARY my_cpp_library

EXPORTS
    Add

The C# Code

The C# code utilizes the DllImport attribute to establish the connection to the C++ library.

using System.Runtime.InteropServices;


public class MyClass
{
    [DllImport("my_cpp_library", EntryPoint = "Add")] // No extension for .NET!
    private static extern int Add(int a, int b);

    public static int AddNumbers(int x, int y)
    {
        return Add(x, y);
    }

    public static void Main(string[] args)
    {
        int result = AddNumbers(5, 3);
        Console.WriteLine($"Result: {result}"); // Output: Result: 8
    }
}

Explanation of the C# code:

  • [DllImport("my_cpp_library", EntryPoint = "Add")]: This attribute specifies the location of the shared library (DLL/SO/DYLIB) and the name of the function to import. The file extension is omitted when targeting .NET. EntryPoint allows mapping between C++ function names and C# function names.
  • private static extern int Add(int a, int b);: This declaration defines the C# function that corresponds to the C++ function. The static and extern modifiers are required. Data types must align with the C++ function signature.

Key Considerations for P/Invoke

  • Shared Library Location: The shared library must be accessible to the C# application. Placing it in the same directory as the executable is a common practice for .NET.
  • Platform Compatibility: Different versions of the shared library are required for different target platforms (x86, x64, ARM, etc.). Conditional compilation in C# is used to manage platform-specific library loading.
  • Data Marshaling: For data types beyond simple integers, data marshaling is necessary to convert data between the managed (.NET) and unmanaged (C++) environments. Attributes like [MarshalAs] are used for this purpose.
  • Error Handling: C++ exceptions are not directly transferable to C#. Error handling should be implemented by checking return values or using other appropriate mechanisms.

Überzeugt? Kontaktieren Sie uns!

Schicken Sie uns einfach eine Email an office@vorstieg.eu oder füllen Sie das folgende Kontakt-Formular aus: