Interop with existing native libraries with example
Let's create DLL and create a header file (.h) inside it using C++, and we will see how we can consume it in our .NET Core application. In the following example, we created a DLL project for example, calling it ExampleDLL. Create a source .cpp file and a header file. Open the header file and write the following code, which returns the sum of two values. To consume public data and functions in class, we have to add the keyword _declspec(dllexport) before the public member which we want to consume from outside. While creating a DLL, we usually add the header file which contains the function or class prototype and we can export this using the __declspec(dllexport) keyword, while doing a declaration in the header file.
The named header file is Calculate, and the method name is Sum which takes two variables, integer a and integer b. Build this solution and open the project location. We can find DLL inside the debug folder:
#ifndef Calculate
#define Calculate
extern "C"
{
__declspec(dllexport)int Sum(int a, int b)
{
return a + b;
}
}
#endif
Add the preceding code in the Calculate.h file, as shown in the following screenshot:
This DLL is created inside the debug folder of the ExampleDLL project:
Now we have our C++ DLL ready to be consumed from C# code. For example, I am creating a C# console application and we just need three lines of code to consume C++ DLL:
- Add namespace using System.Runtime.InteropServices;
- Add attribute using [DllImport(@"<DLL Location>")]
- Add static extern functions with a compatible signature in your C# code, public static extern int Sum(int a, int b);
The C# script for the same is as follows:
using System.Runtime.InteropServices;
class Program
{
//Insert file path of dll you want to import
[DllImport(@"C:\Users\neshriv\Documents\Visual Studio
2017\Projects\ExampleDLL\Debug\ExampleDLL.dll")]
public static extern int Sum(int a, int b);
static void Main(string[] args)
{
int sumValue = Sum(3, 4);
Console.WriteLine("Sum of 3 and 4 is {0}",sumValue);
Console.ReadKey();
}
}
The result is as follows:
In the preceding section, we saw how to consume native libraries in a DLL. In this section, let's take the example of a Mono library. We will create a .so file in Ubuntu and will consume it from .NET Core. We will see the backward compatibility of .NET Core.
Mono is an open source development platform in light of the .NET Framework; it enables engineers to fabricate cross-platform applications with enhanced designer efficiency. Mono's .NET execution depends on the Ecma (Ecma International—European association for standardizing information and communication systems) norms for C# and the Common Language Infrastructure. Mono incorporates both engineer devices and the foundation expected to run .NET customer and server applications. Mono works cross platform, and runs on Linux, Microsoft Windows, and many others. All Mono dialects take advantage from many highlights of the runtime, similar to programmed memory administration, reflection, generics, and threading. Its highlights enable us to focus on composing your application, as opposed to composing framework foundation code.
In this example, we are creating a simple library which displays an integer value.
The code is as follows:
# include<stdio.h>
int hello()
{
return 15;
}
Run the following command in the Terminal to create the .so file:
gcc -shared -o libHelloSO.so -fPIC HelloSOLib.c
In the preceding command, -shared -o creates an (.o) object file and from the object file creates a .so file. -fPIC is used to declare a flag for the position independent code.-fPIC generates position-independent code which can be loaded from any memory location at runtime, so we can access static or global variables and methods at runtime.
The preceding command can be seen in the following screenshot:
Now we will see how to consume this .so file in C# code. We created a C# file and added the namespace, system.Runtime.InteropServices, and gave the path of the .so file inside the DLLImport attribute.
The code is as follows:
Using System;
Using System.Runtime.InteropServices;
Namespace Hello
{
Class Program
{
[DllImport("/home/neha/Documents/InteroWithMonoLib/libHelloSO.so")]
Private static extern int hello()
Static void Main(string[] args)
{
int a = hello();
Console.WriteLine(a);
}
}
}
Now, when we are done with the code and DLLImport, let's discuss Mono, which we will use to create our executable and run this application. Mono is an open source tool which is created by the Microsoft subsidiary, Xamarin. It is a tool which makes the Linux developer's life easy if they want to run Microsoft .NET applications on Linux or on any other cross platform. The latest version at the time of writing this book is Mono 5.4.0, which was released in October 2017. This version supports the Core API of .NET Framework and also C# 7.0. Mono provides several command line utilities, a few of the main useful commands are:
- Mono: It is a just-in-time (JIT) compiler and it supports both 32-bit and 64-bit types of systems. It also supports multiple platforms such as Microsoft Windows, Sun Solaris, Android, Apple iOS, macOS, Linux, Sony PlayStation, and so on. Mono runtime offers Code Execution, Garbage Collection, Code Generation, Exception Handling, OS interface, Thread management, Console access, and Security System, Program isolation using AppDomain. It allows a project to be extended in C# by reusing all existing C, C++ code libraries. As we said earlier, it is a JIT compiler. Since we have ngen.exe in Microsoft.NET to generate a pre-compiled code, which reduces the start up time, we use the following command with Mono to compile assemblies:
mono -O=all --aot <exe name>
In the preceding command, -O=all instructs to enable all optimizations, then the Mono command tells Mono to compile the code to native code, --aot for a precompiled image
- MCS: It's a C# compiler. Though many versions of compiler are available which are specific to the version, for example, gmcs compiler targets 2.0 mscorelib, smcs targets 2.1 mscorelib and moonlight applications (Silverlight implementation for mainly Linux), while dmcs targets 4.0 mscorlib. Now, the new compiler version MCS is present which takes the latest version by default. We can also specify which version we want to use. MCS runs with Mono runtime on a Linux machine, and with both .NET and Mono runtime on a Windows machine.
- Global Assembly Cache tool (Gacutil): This tool is used for maintaining versions of assemblies in a system's global assembly cache.
- XSP: The web service and web application server of Mono.
- mono-config: The format configuration of Mono runtime.
Install Mono using the following command. When we use mcs, it will install the latest version:
sudo apt install mono-mcs
Compile this C# program in Visual Studio Code using the following command; it creates an .exe file at the same location from where we opened the Terminal:
mcs -out:helloNative.exe InteropWithNativeSO.cs
Now, it's time to run and check if it is displaying the value 15, which we have passed in our .so file, as shown in the following screenshot:
To run helloNative.exe, use the following command:
mono helloNative.exe