DllImport or LoadLibrary for best performance
Asked Answered
G

5

12

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?

Gaylene answered 13/5, 2013 at 9:37 Comment(3)
Are these big methods which are called only a few times or are these slim methods which are called very often from managed code?Monandrous
I remember that the guys from SharpDX analyzed the code that DLLImport produces and that the biggest performance problem was some kind of (unneeded) parameter checking. Due to this fact they used Reflection.Emit() to generate the same code like DLLImport 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
@Olivier, these methods for example fills 1024 byte buffers.Gaylene
C
24

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);
    }
} 
Caliban answered 13/5, 2014 at 5:12 Comment(1)
Source and backgroundInsipid
B
3

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

Breath answered 13/5, 2013 at 11:1 Comment(2)
"Actually, you can dynamically load native assembly too". Clearly @zgnilec already knows this.Instructions
Link is broken.Handfast
S
3

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.

http://blogs.msdn.com/b/jonathanswift/archive/2006/10/03/dynamically-calling-an-unmanaged-dll-from-.net-_2800_c_23002900_.aspx

Shippee answered 24/5, 2013 at 21:53 Comment(0)
I
2

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.

Instructions answered 13/5, 2013 at 11:6 Comment(1)
I will check this, maybe I didn't made questuon too clearly, I was asking for different methods of using external DDLs. I "know" only these two. Somewhere I read DllImport is doing some stuff inside, like pinning managed object for garbage colector etc. I thoguht there is a special way to call native assembler methods. If I will make dll thats provide function y = x^2 (just example) and then call it using DllImport which will make 99% cpu on DllImport mechanism, then 1% for my assembler function, then is no point of using external dlls :/Gaylene
B
1

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.

Belorussia answered 23/2, 2021 at 2:47 Comment(1)
I have done some testing myself and in my case loading the method manually and using the DllImport attribute lead to almost no difference in results. For simplicity, I would go on using DllImportAllembracing

© 2022 - 2024 — McMap. All rights reserved.