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 andanimal.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...
Latest Posts
- A C++ MPI code for 2D unstructured halo exchange
- Essential bash customizations: prompt, ls, aliases, and history date
- Script to copy a directory path in memory in Bash terminal
- What is the difference between .bashrc, .bash_profile, and .profile?
- A C++ MPI code for 2D arbitrary-thickness halo exchange with sparse blocks