I have external .DLL file with fast assembler code inside. What is the best way to call functions in this .DLL file to get best performance?
Your DLL might be in python or c++, whatever , do the same as follow.
This is your DLL file in C++.
header:
extern "C" __declspec(dllexport) int MultiplyByTen(int numberToMultiply);
Source code file
#include "DynamicDLLToCall.h"
int MultiplyByTen(int numberToMultiply)
{
int returnValue = numberToMultiply * 10;
return returnValue;
}
Take a look at the following C# code:
static class NativeMethods
{
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
[DllImport("kernel32.dll")]
public static extern bool FreeLibrary(IntPtr hModule);
}
class Program
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int MultiplyByTen(int numberToMultiply);
static void Main(string[] args)
{
IntPtr pDll = NativeMethods.LoadLibrary(@"PathToYourDll.DLL");
//oh dear, error handling here
//if (pDll == IntPtr.Zero)
IntPtr pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, "MultiplyByTen");
//oh dear, error handling here
//if(pAddressOfFunctionToCall == IntPtr.Zero)
MultiplyByTen multiplyByTen = (MultiplyByTen)Marshal.GetDelegateForFunctionPointer(
pAddressOfFunctionToCall,
typeof(MultiplyByTen));
int theResult = multiplyByTen(10);
bool result = NativeMethods.FreeLibrary(pDll);
//remaining code here
Console.WriteLine(theResult);
}
}
I think DLLImport and LoadLibrary have different goals. If you use native .dll, you should use DllImport. If you use .NET assembly, you should use LoadAssembly.
Actually, you can dynamically load native assembly too, see this example: dynamically-calling-an-unmanaged-dll-from-.net
Assuming your target platform is the same as said native dll. You can use DLLImport to pinvoke LoadLibrary and use LoadLibrary to load the native dll into your process. Then use DllImport to pinvoke GetProcAddress.
Then you can define delegates for all the methods exported in said dll that you want to call.
Next you use the Marshal.GetDelegateForFunctionPointer to set your delegate from GetProcAddress.
You create a static class that does this stuff once in the constructor. Then you can call your delegates to invoke the native exported functions in the dll, without having DllImport on everything. Much cleaner, and I'm pretty sure it's a lot faster and will probably completely bypass before mentioned parameter checks.
SO you would have a slow initialization, but once loaded, would run fast imo. Haven't tested this.
Here's a blog on it from my source.
The only way to answer this question is to time both options, a task which is trivially easy. Making performance predictions without timing is pointless.
Since we don't have your code, only you can answer your question.
Did a quick test. Scroll down for the conclusion.
Header:
struct Vector2
{
public:
float X;
float Y;
float GetMagnitude() const;
};
extern "C" __declspec(dllexport) float GetMagnitude(const Vector2& InVector);
Source:
#include <cmath>
float Vector2::GetMagnitude() const
{
return sqrt((X * X) + (Y * Y));
}
Managed:
// #define IMPORT // <-- comment/uncomment this to switch
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
namespace InteropTest
{
public struct Vector2
{
public Vector2(float x, float y)
{
(_x, _y) = (x, y);
}
private float _x;
private float _y;
}
[SuppressUnmanagedCodeSecurity]
internal class Program
{
#if IMPORT
[DllImport("InteropLibrary", CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi)]
private static extern float GetMagnitude(ref Vector2 vector);
#else
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(
string path);
[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(
IntPtr libraryHandle,
string symbolName);
[DllImport("kernel32")]
public static extern bool FreeLibrary(
IntPtr libraryHandle);
private static IntPtr LibraryHandle;
[UnmanagedFunctionPointer(CallingConvention.Cdecl,
CharSet = CharSet.Ansi)]
private delegate float GetMagnitudeDelegate(ref Vector2 vector2);
private static GetMagnitudeDelegate GetMagnitude;
#endif
public static void Main(string[] args)
{
#if !IMPORT
LibraryHandle = LoadLibrary("./InteropLibrary.dll");
IntPtr symbol = GetProcAddress(LibraryHandle, "GetMagnitude");
GetMagnitude = Marshal.GetDelegateForFunctionPointer(
symbol,
typeof(GetMagnitudeDelegate)) as GetMagnitudeDelegate;
#endif
var random = new Random(234);
var sw = new Stopwatch();
sw.Start();
{
for (var i = 0; i < 1000000; i++)
{
var vector = new Vector2(random.Next(400), random.Next(400));
GetMagnitude(ref vector);
}
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
sw = null;
random = null;
#if !IMPORT
CloseLibrary(LibraryHandle);
LibraryHandle = IntPtr.Zero;
GetMagnitude = null;
#endif
}
}
}
Conclusion
The one where you manually load/unload DLL is about 20% slower. DllImport took about 99-105 milliseconds on different tries. Marshal.GetDelegateForFuncitonPointer took about 120-125 milliseconds on different tries.
DllImport
attribute lead to almost no difference in results. For simplicity, I would go on using DllImport
–
Allembracing © 2022 - 2024 — McMap. All rights reserved.
DLLImport
produces and that the biggest performance problem was some kind of (unneeded) parameter checking. Due to this fact they usedReflection.Emit()
to generate the same code likeDLLImport
but without the checks, what leads to a performance improvement. I think it was a blog post from one of its creators, but i can't find it at the moment. – Monandrous