15

There is a great answer on SO about how to set the search directory for DllImport at runtime. Works just fine with two lines of code.

However, many open source projects instead use LoadLibrary function. There are "rumors" that calling native methods via delegates is slower. I call them "rumors" because I have seen this only in two places and this is micro optimization anyway.

The most interesting place is this blog post: http://ybeernet.blogspot.com/2011/03/techniques-of-calling-unmanaged-code.html

There, the author measured performance of different techniques:

  • C# (informative) 4318 ms
  • PInvoke - suppressed security 5415 ms
  • Calli instruction 5505 ms
  • C++/CLI 6311 ms
  • Function delegate - suppressed security 7788 ms
  • PInvoke 8249 ms
  • Function delegate 11594ms

NNanomsg uses function delegates but mentions the blog post with a comment "The performance impact of this over conventional P/Invoke is evidently not good" on this line.

Kestrel server from MSFT's ASP vNext uses the same technique with Libuv library: here is the code

I think that delegates are more cumbersome to use than simple DllImport, and given the performance difference I wonder why the performance-oriented libraries use delegates instead of setting dll search folder?

Are there any technical reasons like security, flexibility or whatever - or this is just a matter of taste? I do not understand the rationale - is it possible that the authors just didn't search StackOverflow enough!?

Community
  • 1
  • 1
V.B.
  • 6,236
  • 1
  • 33
  • 56
  • The most common reason I am aware of is to cater for different dlls for 32/64-bit. DllImport will autoload the native DLL and thus will require seperate .NET dlls for bitness. – leppie Jan 16 '15 at 10:21
  • 4
    You could use different folders and call `SetDllDirectory` with different path e.g. "native/x64" and "native/x86" and files with the same name inside. I have just tested this with my code and it works perfectly - that is why I asked the question. – V.B. Jan 16 '15 at 10:23
  • 4
    @leppie I handle that by calling `LoadLibrary` before any other p/invoke call passing the full path to the correct bitness DLL. From there on in, the p/invoke calls that refer to the DLL with just the DLL name bind to the module that is already loaded. Best of both worlds. – David Heffernan Jan 16 '15 at 10:27
  • @DavidHeffernan yes, one could still use LoadLibrary and then use plain PInvoke and it will work - it is written in the docs. Why then people use delegates? – V.B. Jan 16 '15 at 10:28
  • 1
    @V.B. Perhaps because they enjoy pain! – David Heffernan Jan 16 '15 at 11:19
  • Learned a bit in correcting myself. But in theory, if you Load a Library manually with LoadLibrary, couldn't you still use [DllImport]? Because DllImport resolves at runtime. So If I already loaded the library then DllImport should see that it's already loaded and use it's export table no? Then you can solve for the lack of (SetDllDirectory) on Linux and OSX. – Ryan Mann Apr 17 '18 at 14:54
  • 1
    Yeah, just tested that. You can call LoadLibrary before using your DllImports and DllImport will use the Module you already loaded. Additionally, you do not need to specify the DllName in your DllImport attribute, you can use the Modules Name (generally the dll name without the extension). So if your module has the same module name on all three platforms, then you only need 1 dll import and you don't need to conditional compile different module names and this also solves x32 vs x64 loading and the lack of SetDllDirectory on Linux/OSX. – Ryan Mann Apr 17 '18 at 15:21
  • @RyanMann yeah, this is how it works even on Core. Haven't tested performance on Core, but still believe that DllImport is much simpler to write. The key assumption is "the same module name"... and the same method names, otherwise you are in your own rabbit hole. But for the happy path MSFT/.NET guys solved the task for us. Big thanks & hug to them :) – V.B. Apr 17 '18 at 22:13
  • If you could do static extern delegates in C# that you don't need to instantiate, I might change my mind though :), but yeah, DllImport with LoadLibrary is much cleaner than instantiating delegates to map pointers to. – Ryan Mann Apr 18 '18 at 03:53
  • kestel source link is expired, could You please update? – valerysntx May 04 '18 at 19:27
  • 1
    @valery.sntx Kestrel is now using DllImport https://github.com/aspnet/KestrelHttpServer/blob/8b1fbad10e12e7ddab98ae7a17891eca6e6bcfd9/src/Kestrel.Transport.Libuv/Internal/Networking/LibuvFunctions.cs#L494 – V.B. May 04 '18 at 19:59
  • @V.B. Thanks, I was too lazy to check – valerysntx May 04 '18 at 20:17

1 Answers1

11

Hmya, blog posts, that fundamentally flawed way to distribute technical information. The world would be a better place if we could vote for them. The author is comparing apples and oranges. Well, more like apples and bicycles.

There are two fundamentally different kind of interop scenarios being compared here. The first one is the "normal" one, a managed program calling code in an unmanaged DLL. Using the [DllImport] attribute or C++/CLI are the weapons of choice. Very highly optimized inside the CLR, it dynamically generates machine code that translates the arguments and makes the call. Important, a managed program always runs lots of unmanaged code, given that it runs on top of a purely unmanaged operating system.

What you are talking about, the "slow" version, is going the other way. Calling managed code from an unmanaged program. Some people call this "reverse pinvoke". It is much more convoluted because before you can call managed code, you first have to get the CLR loaded and initialized. And create an appdomain. And locate and load the .NET assembly that contains the code. And JIT compile it.

There are three basic ways to do this:

  • Custom-host the CLR. This is by far the most powerful version. You use the hosting interfaces to create the CLR instance explicitly and have full control over its configuration. The CLRRuntimeHost COM coclass is the primary vehicle to get that ball rolling.

  • Expose .NET classes as COM components by giving them the [ComVisible(true)] attribute. Very simple to get going, the unmanaged code is completely unaware that it is actually using .NET code. The default CLR host gets loaded, the registry entry for the COM component points to mscoree.dll which bootstraps the CLR as necessary. Only disadvantage is the unmanaged code author needs to write COM client code, a skill that's getting lost.

  • What you are talking about, taking advantage of the ability of the C++/CLI compiler to generate DLL exports. Notable also for being used by Robert Gieseke's Unmanaged Exports tool, using the exact same technique but injecting these DLL exports by rewriting the assembly.

There are very significant disadvantages to doing it the 3rd way, beyond the expense of the call. It scales poorly, every single method must be exported explicitly and it must be static so you cannot implement an object model. And the super-duper, horrible, nasty, impossible-to-deal-with problem that you cannot get any diagnostic when the call fails. Managed code likes to throw exceptions, if not from the code itself then from the CLR that tries to tell you that you passed the wrong arguments or cannot get the code prepped. You cannot see those exceptions, there is no way to tell that the function failed nor a way to tell why it failed. If the unmanaged code doesn't catch SEH exceptions with the non-standard __try/__except keywords then the program bombs. No diagnostic whatsoever. Even if it does catch the SEH, you only get an "it didn't work" signal.

Managed code that's invoked this way must be written to deal with this problem. It must contain try/catch-em-all in the public method. And log the exception and provide a way to return an error code so the caller can detect failure. Gross problems, like missing dependent DLLs or versioning issues are however not diagnosable at all. It looks easy for the unmanaged code author, simple LoadLibrary + GetProcAddress, it is a long-term support nightmare however.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thank you for the great explanation! So the bottom line, if only C# calls C, there is no need for delegates and conventional P/Invoke is better? – V.B. Jan 16 '15 at 11:43
  • 3
    Yes, always favor [DllImport]. Having to use Marshal.GetFunctionPointerForDelegate() is a very clumsy way and never necessary in practice. – Hans Passant Jan 16 '15 at 11:46
  • Thanks! DavidHeffernan is probably right with his comment "because they enjoy pain" :) – V.B. Jan 16 '15 at 11:48
  • 2
    I'm not sure what you are getting at here Hans. As I read the question, and looking at the links in the question, the question seems to be about the "normal" scenario. I don't see reverse p/invoke in the question. – David Heffernan Jan 16 '15 at 12:27
  • The blog post is noodling about the C++/CLI exports and compares them unfavorably against pinvoke. Apple and bicycle. – Hans Passant Jan 16 '15 at 12:37
  • I looked at the links below which were not reverse p/invoke. So I don't know what the question is any more. Hey ho. – David Heffernan Jan 16 '15 at 13:38
  • Yeppp' Nuget Bundle also uses GetFunctionPointerForDelegate. That is funny given that Yeppp is optimized at CPU cycles level and very performance focused. Or in reality it doesn't make a big difference, as NNanomsg authors told me. – V.B. Feb 11 '15 at 15:42