Calling native code from C#
If you are like me, you like optimizing and profiling your code making it as fast as possible. However, at one point you might realize the C# language does not give you the best performance for whatever the purpose of your code is. Since C# is running in the Common Language Runtime (CLR), there is a larger overhead between what the machine is doing and what your code is doing. It is therefore necessary to go a language of lower level. In my case, I went to C++. C++ is compiled into assembly code when compiled, making it a low-level language. You can also go deeper if the C++ code does not give the desired performance. However, if the code you want to have in C++ contains just a few lines of code, it could in fact be a better way to keep it in C#.
This post will create a basic function returning the result of x to the power of n given that n is larger than or equal to 0. A normal C# function would this would be as follows: (I avoid using library functions to make my examples more clear)
public static void Main()
{
Console.WriteLine("5^4 is {0}", Power(5, 4));
Console.ReadKey();
}
private static int Power(int x, int n)
{
int value = 1;
for (int i = 0; i < n; i++)
value = value * x;
return value;
}
We want to move this code to C++.
First, I add a empty C++ project to my solution file in Visual Studio (DLL Win32 application). Now comes the ugly part. My source file in the C++ project would look like this:
#include <windows.h>extern “C” __declspec(dllexport) int __stdcall Power(const int x, const int n);
BOOL __stdcall DllMain(HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved) { return TRUE; }
extern “C” __declspec(dllexport) int __stdcall Power(const int x, const int n) { int value = 1; for (int i = 0; i < n; i++) value = value * x;
return value;}
The DllMain method just indicates where the main part of the DLL file is. It works a bit like a constructor in classes. All the "extern "C" __declspec(dllexport) int __stdcall" says it is a int function that is used outside the DLL file by other programs.
In order to call my native C++ function, I would have to add more code to my C# project.
[DllImport("NativePower.dll")]
private static extern int Power(int x, int n);
public static void Main()
{
Console.WriteLine(“5^4 is {0}”, Power(5, 4));
Console.ReadKey();
}
It is important to place the NativePower.dll file in the same folder as the debug folder for our C# application unless we want the application to die in a DllNotFoundException.
This method of calling native code is called P/invoke (Patform invoke). It is often used by the .NET framework itself to call different methods to the Windows kernel or other system DLL files. It can technically be used by any DLL file coded in any programming language as long as the CLR manages to read the DLL file. I miss some debugging functionality in Visual Studio for importing DLL files since this function could be hard to debug. In some situations you might want to pass strings, arrays or pointers as an argument in the DLL file. It is then needed to use marshaling. Marshalling means you take a data type and its values and makes it compatible with another programming language by converting it.
|
Description |
Windows Type |
C/C++ Keyword |
Managed Type |
C# Keyword |
|
8-bit signed integer |
CHAR |
char |
System.SByte |
sbyte |
|
8-bit unsigned integer |
BYTE |
unsigned char |
System.Byte |
byte |
|
16-bit signed integer |
SHORT |
short |
System.Int16 |
short |
|
16-bit unsigned integer |
WORD and USHORT |
unsigned short |
System.UInt16 |
ushort |
|
32-bit signed integer |
INT, INT32, LONG, and LONG32 |
int, long |
System.Int32 |
int |
|
32-bit unsigned integer |
DWORD, DWORD32, UINT, and UINT32 |
unsigned int, unsigned long |
System.UInt32 |
uint |
|
64-bit signed integer |
INT64, LONGLONG, and LONG64 |
__int64, long long |
System.Int64 |
long |
|
64-bit unsigned integer |
DWORDLONG, DWORD64, ULONGLONG, and UINT64 |
unsigned __int64, unsigned long long |
System.UInt64 |
ulong |
|
Floating-point integer |
FLOAT |
float |
System.Double |
double |
Source: Codeproject
The table describes what the managed data types are in the native world.
It is important to ask yourself if it is really needed to call native DLL files. I was in a situation where I was going to write a faster algorithm for parsing an int from a byte-array in ASCI-encoding. When I stress tested the code in C++ by using P/invoke to call it form my C# project, it turned out the code was 25% slower. This is due to the overhead caused by the marshaling and the P/invoke. Native code may not always be the solution, especially while developing a managed application. However, a larger algorithm (eg. path finding, cryptography, etc.) which demands more CPU-time, could be running more smoothly.