Example for SWIG to wrap C++ library in .Net 6

Introduction

The goal of this post is to compile a C++ library and use it in a .Net 6 C# project. The setup is cross-platform and tested Linux and Windows. The C# wrapper files are created via SWIG. SWIG uses C# PInvoke technology to interact with natively compiled libraries. Writing PInvoke wrappers is not that difficult but doing so for many classes and functions can be time-consuming. SWIG automates creating C# wrappers.

Code

The final code is on this GitHub repository. The following sections explain how it is created. Section Compile shows how to build and run it.

Installation

We need .Net 6 and SWIG on our machine.

.Net 6

For Windows, I installed Visual Studio Community 2022. It comes with .Net family packages. You can also visit .Net website to install it on Linux, macOS, Docker.

SWIG

Linux systems usually have SWIG ready to be installed by their package manager. For example, on Ubuntu, it is installed with

sudo apt install swig

For MS Windows, download the executable from swig website. Extract the content somewhere easy to find.

Compilers

On windows, Visual C++ is used which is installed by Visual Studio Community 2022.

On Ubuntu, GCC 9.3 is employed.

C++ code: Animal

The C++ code I use for this test is “Animal” class with this header file,

// animal.h file
class Animal {
    std::string name;
public:
    Animal(std::string _name);
    void Walk();
    std::string& GetName();
};

And implementation of

// animal.cpp file
#include <iostream>
#include "animal.h"

Animal::Animal(std::string _name)
{
	name = _name;
}

void Animal::Walk()
{
	std::cout << name << " is walking..." << '\n';
}

std::string& Animal::GetName()
{
	return name;
}

I created animalcpp directory and put these files in it.

SWIG interface

The SWIG interface file is shown below that is also placed in animalcpp directory

/* animal.i file */
%module AnimalModule
%include <std_string.i>
%include <typemaps.i>
%apply const std::string & {std::string &};

%{
  #include "animal.h"
%}

%include "animal.h"

SWIG supports STL/C++ containers, see complete list here. In this code, I used std::string, so I had to include std_string.i at the beginning of the interface.

When interacting C# with the C++ library, the strings are communicated by value. Therefore, typemaps.i and apply rule added to convert std::string& to const std::string&.

CMake

SWIG is supported by CMake (see swig_add_library in the code below). The CMake file for this project is also placed in animalcpp directory:

cmake_minimum_required(VERSION 3.16)

project(AnimalWrapperProj VERSION 1.0 LANGUAGES CXX)

if ( MSVC )
  set(SWIG_DIR "C:/Users/sorus/workspace/swigwin-4.0.2/Lib")
  set(SWIG_EXECUTABLE "C:/Users/sorus/workspace/swigwin-4.0.2/swig.exe")  
endif (MSVC)

find_package(SWIG REQUIRED)
include(${SWIG_USE_FILE})


# Set .Net project directory
set(NET_PROJECT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../dotNetSample")

# Add swig flags here
set(CMAKE_SWIG_FLAGS "")


set_property(SOURCE animal.i PROPERTY CPLUSPLUS ON)
set_source_files_properties(animal.i PROPERTIES SWIG_FLAGS "-includeall")

swig_add_library(animal
  TYPE SHARED
  LANGUAGE CSharp
  SOURCES animal.i animal.cpp
  OUTPUT_DIR ${NET_PROJECT_DIR}
  OUTFILE_DIR ${NET_PROJECT_DIR}
  )

# for copying animal.dll to .Net project dir dir
set_target_properties( animal
    PROPERTIES
    # These copy animal.dll on Windows to .Net project directory
    RUNTIME_OUTPUT_DIRECTORY_RELEASE ${NET_PROJECT_DIR}
    RUNTIME_OUTPUT_DIRECTORY_DEBUG ${NET_PROJECT_DIR}
    
    # This copies animal.so on Linux to .Net project directory
    LIBRARY_OUTPUT_DIRECTORY ${NET_PROJECT_DIR}

    # Set address of C++ headers
    INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}
)
  • SWIG package: Windows (MSVC), we have to set the address of SWIG package that we extracted before. But for Linux, this is not required.

  • .Net project directory: The output of SWIG and the C++ shared library will be created there. Therefore, we can have them in the .Net project.

  • -includeall: This flag makes SWIG wrap every included header in the source. Remove it if you want to wrap only the source itself.

  • Shared library: the code creates animal.dll on Windows and animal.so on Linux.

.Net Project

Now we need to create a cross-platform console .Net project. First, we create a folder named “dotNetSample” (mentioned in CMake file) and then in that folder, we run this terminal command:

dotnet new console

This creates a .Net project with the same name as the folder.

The file Program.cs is edited to use the wrapped C++ library:

var k = new Animal("Kermit");
Console.WriteLine(k.GetName());
k.Walk();

We also need to add the below lines to dotNetSample.csproj file, somewhere between <Project> tags:

<ItemGroup>
    <None Include="animal.dll" Condition="$([MSBuild]::IsOSPlatform('Windows'))">
            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
    <None Include="animal.so" Condition="$([MSBuild]::IsOSPlatform('Linux'))" >
            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
</ItemGroup>

The code automatically copies the shared libraries to binary directories during building.

Compile

Now we are ready to compile and run the code. First, we compile the C++ library and its SWIG wrapper. In the animalcpp folder, run the below commands:

mkdir build
cd build
cmake ..
cmake --build .

The library is compiled, the C# wrapper files are created and copied to the .Net project.

Now we go to .Net project folder and run the program in a terminal:

dotnet run

If everything is set up correctly, you should see

Kermit
Kermit is walking...
Tags ➡ C++ ⋅Net Core

Subscribe

I notify you of my new posts

Latest Posts

Comments

1 comment
Bernardo Bermúdez 13-Jun-2022
Hello, I would like to know how to modify the interface file and the CMakeLists.txt to contain more header files.