松耦合 NativeMethods
我需要使用 C# 的本机 DLL。该 DLL 公开了几种我可以通过 P/Invoke 访问的方法和多种类型。所有这些代码都位于标准 NativeMethods 类中。为了简单起见,它看起来像这样:
internal static class NativeMethods
{
[DllImport("Foo.dll", SetLastError = true)]
internal static extern ErrorCode Bar(ref Baz baz);
internal enum ErrorCode { None, Foo, Baz,... }
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct Baz
{
public int Foo;
public string Bar;
}
}
为了实现松散耦合,我需要提取一个接口并使用对 NativeMethods 的调用来实现它:
public interface IFoo
{
void Bar(ref Baz baz);
}
public class Foo : IFoo
{
public void Bar(ref Baz baz)
{
var errorCode = NativeMethods.Bar(baz);
if (errorCode != ErrorCode.None) throw new FooException(errorCode);
}
}
现在我可以在代码中使用 IFoo 作为依赖项并在测试中模拟它:
public class Component : IComponent
{
public Component(IFoo foo, IService service) { ... }
}
这里似乎有问题。根据 FxCop,NativeMethods
必须是内部的。那么 IFoo
(从 NativeMethods
中提取)也是内部的,这是有道理的。但我不能只是将其设置为内部 b/c 它在公共 ctor 中使用(应该保持公共)。所以:为了实现松散耦合,我必须更改组件的可见性,否则该组件将是内部组件。你们对此有何看法?
另一个问题:组件具有方法 public void DoSomehining(Bar bar)
,它使用 NativeMethods.cs 中定义的 Bar
。我也必须将其公开才能使其发挥作用。或者创建一个新的 Bar 类来包装 NativeMethods+Bar 。如果我采用公共方式,那么 NativeMethods
也会变得公共,并且 FxCop 抱怨“嵌套类型不应该可见”。如果我采用包装方式......好吧,我觉得为所有“本地类型”做这件事太费力了。哦,还有第三种方法:将类型从 NativeMethods 中移走并将其公开。然后,FxCop 开始分析它们,并找到嵌套在 NativeMethods 中时隐藏的所有错误。我真的不知道这里最好的方法是什么......
I need to use a native DLL from C#. The DLL exposes several methods which I can access via P/Invoke, and several types. All this code is in a standard NativeMethods class. To keep things simple it looks like this:
internal static class NativeMethods
{
[DllImport("Foo.dll", SetLastError = true)]
internal static extern ErrorCode Bar(ref Baz baz);
internal enum ErrorCode { None, Foo, Baz,... }
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct Baz
{
public int Foo;
public string Bar;
}
}
To achieve loose coupling I need to extract an interface and implement it using calls to NativeMethods:
public interface IFoo
{
void Bar(ref Baz baz);
}
public class Foo : IFoo
{
public void Bar(ref Baz baz)
{
var errorCode = NativeMethods.Bar(baz);
if (errorCode != ErrorCode.None) throw new FooException(errorCode);
}
}
Now I can just use IFoo as a dependency in my code and mock it in tests:
public class Component : IComponent
{
public Component(IFoo foo, IService service) { ... }
}
Something seems wrong here. NativeMethods
must be internal according to FxCop. It makes sense then for IFoo
(which was extracted from NativeMethods
) to be internal too. But I can't just make it internal b/c it is used in a public ctor (which should stay public). So: in order to achieve loose coupling I have to change the visibility of a component which otherwise would have been internal. What do you guys think about this?
Another problem: Component has method public void DoSomehing(Bar bar)
which uses the Bar
defined in NativeMethods.cs. I have to make that public too for this to work. This, or make a new Bar
class which wraps NativeMethods+Bar
. If I go the public way then NativeMethods
becomes public too and FxCop complains that "Nested types should not be visible". If I go the wrapper way... well, I feel it's too much of an effort to do it for all the "native types". Oh, there is the 3rd way: move the types away from NativeMethods and make them public. Then FxCop gets to analyze them and finds all the errors which were otherwise hidden when they were nested in NativeMethods. I really don't know what's the best way here...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
公共抽象类(而不是接口)可能是您的朋友。
这可以包括内部抽象方法(指的是内部类型),这实际上使得不可能以正常方式从程序集外部进行子类化(但是
InternalsVisibleTo
将允许您创建一个假的用于测试)。基本上,接口的设计并没有真正按照组件方面的方式进行设计。
这正是我在 Noda Time 中为
CalendarSystem
所做的 - 它的 API 使用内部类型,但我想让它成为一个接口或抽象类。我在 博客文章,您可能会觉得有趣。It's possible that a public abstract class is your friend here, instead of an interface.
That can include internal abstract methods (referring to internal types), which actually makes it impossible to subclass in the normal way from outside the assembly (but
InternalsVisibleTo
will let you create a fake for testing).Basically interfaces haven't really been designed as well as they could have been from a component aspect.
This is exactly what I did in Noda Time for
CalendarSystem
- its API uses internal types, but I wanted to make it an interface or abstract class. I wrote about the oddities of access in a blog post which you may find interesting.提取
INativeMethods
怎么样?我们将免费获得的不完整列表:
我展示代码
接口与 WinApi 相同:
实现是静态类的瘦代理:
让我们使用 P\Invoke 导入来完成此
操作 示例用法
单元测试
很难相信,但有可能通过模拟 WinApi 来验证任何期望
模拟
out\ref
参数可能会引起头痛,因此这里是供将来参考的代码:How about extracting
INativeMethods
?Incomplete list of what we will get for free:
Show me the code
Interface is identical to WinApi:
Implementation is a thin proxy to static class:
Let's complete this with P\Invoke imports
Example usage
Unit testing
Hard to believe, but it possible to verify any expectation by mocking WinApi
Mocking
out\ref
params might cause headache so here is the code for future reference: