问题 将.NET P / Invoke代码组织到Win32 API的最佳实践


我在.NET中重构了一个庞大而复杂的代码库,它大量使用P / Invoke到Win32 API。项目的结构并不是最好的,我发现DllImport语句遍布整个地方,经常为同一个函数复制,并且还以各种方式声明:

import指令和方法有时被声明为public,有时是private,有时是静态的,有时也是实例方法。我担心重构可能会产生意想不到的后果,但这可能是不可避免的。

是否有可以帮助我的最佳实践记录?

我的instict是组织一个静态/共享的Win32 P / Invoke API类,它在一个文件中列出了所有这些方法和相关的常量...... 编辑 user32 DLL有超过70个导入。

(代码库由20多个项目组成,包含大量的窗口消息传递和跨线程调用。如果有所不同,它也是从VB6升级的VB.NET项目。)


1830
2018-03-12 16:56


起源



答案:


您可以考虑在.NET框架中完成它的方式。它总是声明一个名为NativeMethods的静态类(VB.NET中的Module),它包含P / Invoke声明。您可能比Microsoft程序员更有条理,有许多重复声明​​。不同的团队致力于框架的不同部分。

但是,如果要在所有项目中共享此项,则必须将这些声明声明为Public而不是Friend。这不是很好,它应该是一个实现细节。我想你可以通过在每个需要它的项目中重用源代码文件来解决这个问题。通常禁忌但在这种情况下没关系,我想。

我个人在需要它们的源代码文件中根据需要声明它们,使它们成为私有的。当说谎参数类型时,这也很有帮助,特别是对于SendMessage。


5
2018-03-12 17:21



谢谢大家的精彩讨论。太糟糕了我可以。只挑选一名获胜者。非常感谢nobugz的实际考虑。 - Paul Sasik


答案:


您可以考虑在.NET框架中完成它的方式。它总是声明一个名为NativeMethods的静态类(VB.NET中的Module),它包含P / Invoke声明。您可能比Microsoft程序员更有条理,有许多重复声明​​。不同的团队致力于框架的不同部分。

但是,如果要在所有项目中共享此项,则必须将这些声明声明为Public而不是Friend。这不是很好,它应该是一个实现细节。我想你可以通过在每个需要它的项目中重用源代码文件来解决这个问题。通常禁忌但在这种情况下没关系,我想。

我个人在需要它们的源代码文件中根据需要声明它们,使它们成为私有的。当说谎参数类型时,这也很有帮助,特别是对于SendMessage。


5
2018-03-12 17:21



谢谢大家的精彩讨论。太糟糕了我可以。只挑选一名获胜者。非常感谢nobugz的实际考虑。 - Paul Sasik


将它们组织成一个 [Safe|Unsafe]NativeMethods 类。将班级标记为 internal static。如果需要将它们暴露给自己的程序集,则可以使用 InternalsVisibleTo  - 尽管如果你能将相关的组合成每个组件更合适。

每种方法都应该是 static  - 老实说我不知道​​你甚至可以标记实例方法 DllImport

作为第一步 - 我可能会将所有内容移动到Core程序集(如果有的话),或者创建一个 Product.Native 部件。然后,您可以轻松找到欺骗和重叠,并寻找管理的等价物。如果你的p / invokes是一团糟,我不怀疑你在引导你的分组的其他程序集中有很多分层的方法。


3
2018-03-12 17:22





为什么不创建一个名为Win32.vb的单个文件,并在该逻辑组中将pinvokes分成不同的名称空间,例如GDI名称空间可以使用所有GDI pinvokes,User32名称空间可以使用驻留在User32内核中的所有pinvoke,依此类推。 ..起初可能会很痛苦,但至少你会在该文件中包含一个集中的命名空间?看一看 这里 看看我的意思......你怎么看?


2
2018-03-12 17:04



+1很棒的链接!绝对是我可能想要做的事情的一个很好的例子。 - Paul Sasik
链接断了。 - Brian Reichle


您的P / Invoke是否从VB6调用了迁移的工件?我已经将300,000行代码从VB6迁移到C#(Windows.Forms和System.EnterpriseServices),并且除了少数几个P / Invokes调用之外几乎没有 - 几乎总是有一个托管等价物。如果您正在重构,您可能需要考虑做类似的事情。生成的代码应该更容易维护。


2
2018-03-12 17:10



不仅更容易维护长期运行,而且性能更高。 - Nick
一些调用是工件,有些则不是。我确实正在替换w /托管代码,但仍会有大量的p / invoke。 - Paul Sasik
这无助于perf,托管方法仍然是P / Invokes。 - Hans Passant
甚至原始VB6代码(Declares)中的P / Invokes通常也可以被托管等效代替。 - Polyfun
@nobugs - 这取决于方法。此外......不断地来回编组必须要有与之相关的成本。 - Nick


建议的方法是每个程序集都有一个NativeMethods类,其中包含所有DllImported方法,并具有内部可见性。通过这种方式,您始终知道导入函数的位置,并避免重复声明。


2
2018-03-12 17:16



听起来不错。您能否为某些来源提供一两个链接。 - Paul Sasik
FxCop建议这样做..看看 blogs.msdn.com/fxcop/archive/2007/01/14/... - munissor


在这种情况下,我通常尝试做的是做你正在谈论的事情,创建各种类,静态或非静态,提供功能,这样它可以根据需要重新使用。根据调用的性质,我不喜欢静态类实现,但这取决于您的具体实现。

根据要求扩展到上方。

鉴于P / Invoke的性质,特别是如果需要一些调用并且具有不同的实现区域,我发现将类似项目组合在一起更好,这样你就不会引起很多其他混乱,或者其他DLL导入什么时候不需要。

希望远离静态方法,是由于调用非托管资源和内存泄漏等可能性。


2
2018-03-12 16:58



米切尔,你介意扩大你的答案吗?为什么要创建各种类而不是一类?为什么静态实现不那么优选? - Paul Sasik
根据请求更新 - Mitchel Sellers