C#Native Interop - 为什么大多数库使用LoadLibrary和委托代替SetDllDirectory和简单的DllImport

时间:2015-01-16 10:17:39

标签: c# .net interop pinvoke

关于如何在运行时为DllImport设置搜索目录,有一个great answer on SO。使用两行代码就可以正常工作。

但是,许多开源项目改为使用LoadLibrary函数。有谣言"通过委托调用本机方法的速度较慢。我打电话给他们"谣言"因为我只在两个地方看过这个,这无论如何都是微优化。

最有趣的地方是这篇博文:http://ybeernet.blogspot.com/2011/03/techniques-of-calling-unmanaged-code.html

在那里,作者测量了不同技术的表现:

  • C#(资料性)4318 ms
  • PInvoke - 抑制安全性5415 ms
  • Calli指令5505 ms
  • C ++ / CLI 6311 ms
  • 功能委托 - 抑制安全性7788 ms
  • PInvoke 8249 ms
  • 功能委托11594ms

NNanomsg使用函数代理,但提到了博客文章的评论"这对传统P / Invoke的性能影响显然不太好"在this line

来自MSFT的ASP vNext的

Kestrel server使用与Libuv库相同的技术:here is the code

我认为委托比简单的DllImport使用起来更麻烦,并且考虑到性能差异,我想知道为什么面向性能的库使用委托代替设置dll搜索文件夹?

有任何技术原因,如安全性,灵活性或其他 - 或者这仅仅是品味问题?我不理解其基本原理 - 作者是否可能没有足够的搜索StackOverflow!?

1 个答案:

答案 0 :(得分:10)

Hmya,博客文章,从根本上说是分发技术信息的方式。如果我们能投票支持,世界将会变得更加美好。作者正在比较苹果和橘子。好吧,更像是苹果和自行车。

这里有两种根本不同的互操作方案。第一个是“普通”的,一个托管程序在非托管DLL中调用代码。使用[DllImport]属性或C ++ / CLI是首选武器。在CLR内部进行了高度优化,它动态生成机器代码,用于转换参数并进行调用。重要的是,托管程序总是运行 lot 非托管代码,因为它运行在纯粹的非托管操作系统之上。

你所说的“慢”版本正在走向另一条道路。从非托管程序调用托管代码。有人称之为“反向pinvoke”。它更复杂,因为在您调用托管代码之前,首先必须加载并初始化CLR。并创建一个appdomain。并找到并加载包含代码的.NET程序集。 JIT编译它。

有三种基本方法可以做到这一点:

  • 自定义托管CLR。这是迄今为止最强大的版本。您可以使用托管接口显式创建CLR实例,并完全控制其配置。 CLRRuntimeHost COM coclass是推动该球滚动的主要工具。

  • 通过为其提供[ComVisible(true)]属性,将.NET类公开为COM组件。非常简单,非托管代码完全没有意识到它实际上正在使用.NET代码。加载默认CLR主机,COM组件的注册表项指向mscoree.dll,mscoree.dll根据需要引导CLR。唯一的缺点是非托管代码作者需要编写COM客户端代码,这是一种迷失的技能。

  • 您在谈论什么,利用C ++ / CLI编译器生成DLL导出的能力。值得注意的是Robert Gieseke的Unmanaged Exports工具使用了完全相同的技术,但通过重写程序集注入了这些DLL导出。

除了通话费用之外,第三种方式还有非常明显的缺点。它的扩展性很差,必须明确导出每个方法,并且它必须是 static ,因此您无法实现对象模型。而这种超级糟糕,可怕,令人讨厌,不可能解决的问题是,当呼叫失败时,你无法获得任何诊断。托管代码喜欢抛出异常,如果不是来自代码本身,那么就是试图告诉您传递错误参数或无法准备代码的CLR。您无法看到这些异常,无​​法判断函数是否失败,也无法告诉为什么失败。如果非托管代码没有捕获具有非标准__try / __除关键字的SEH异常,则程序会发生炸弹。没有任何诊断。即使它确实赶上了SEH,你也只会得到一个“它没有工作”的信号。

必须编写以这种方式调用的托管代码来处理此问题。 必须在公共方法中包含try / catch-em-all。并记录异常并提供返回错误代码的方法,以便调用者可以检测到故障。然而,遗漏的依赖DLL或版本问题等严重问题根本无法诊断。对于非托管代码作者来说,简单的LoadLibrary + GetProcAddress看起来很容易,但它却是一个长期的支持噩梦。