3

我需要使用 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) { ... }
}

这里似乎有些不对劲。NativeMethods根据 FxCop,必须是内部的。IFoo那么(从 中提取的NativeMethods)也是内部的也是有道理的。但我不能只将其设置为内部 b/c,它用于公共 ctor(应该保持公开)。所以:为了实现松散耦合,我必须改变一个组件的可见性,否则它本来是内部的。对此你们怎么看?

另一个问题:组件具有public void DoSomehing(Bar bar)使用BarNativeMethods.cs 中定义的方法。我也必须将其公之于众才能正常工作。这个,或者创建一个新的Bar类来包装NativeMethods+Bar. 如果我采用公开的方式,那么NativeMethods也会公开,FxCop 抱怨“嵌套类型不应该是可见的”。如果我采用包装方式......好吧,我觉得为所有“本机类型”做这件事太费劲了。哦,还有第 3 种方法:将类型从 NativeMethods 中移开并公开。然后 FxCop 开始分析它们并找到所有在嵌套在 NativeMethods 中时隐藏的错误。我真的不知道这里最好的方法是什么......

4

2 回答 2

3

在这里,公共抽象类可能是您的朋友,而不是接口。

这可以包括内部抽象方法(指内部类型),这实际上使得不可能从程序集外部以正常方式进行子类化(但InternalsVisibleTo会让您创建一个假的进行测试)。

基本上,接口并没有像从组件方面那样真正设计好。

这正是我在Noda Time中所做的CalendarSystem——它的 API 使用内部类型,但我想让它成为一个接口或抽象类。我在一篇博文中写了关于访问的奇怪之处,您可能会觉得很有趣。

于 2011-11-17T07:42:35.257 回答
0

怎么提取INativeMethods

我们将免费获得的不完整清单:

  • 完全支持 TDD
  • 您不需要运行您的应用程序来验证大多数情况
  • 真的很容易模拟和添加对不同环境\操作系统的支持
  • 分析性能,测量对 WinApi 的调用计数

给我看代码

接口与 WinApi 相同:

 internal interface INativeMethods    
 {
    IntPtr SendMessage(IntPtr hwnd, uint msg, IntPtr wparam, StringBuilder lparam);

    bool GetWindowRect(IntPtr hwnd, out Rect rect);

    IntPtr GetWindow(IntPtr hwnd, uint cmd);

    bool IsWindowVisible(IntPtr hwnd);

    long GetTickCount64();

    int GetClassName(IntPtr hwnd, StringBuilder classNameBuffer, int maxCount);

    int DwmGetWindowAttribute(IntPtr hwnd, int attribute, out Rect rect, int sizeOfRect);

    bool GetWindowPlacement(IntPtr hwnd, ref WindowPlacement pointerToWindowPlacement);

    int GetDeviceCaps(IntPtr hdc, int index);
}

实现是静态类的瘦代理:

internal class NativeMethodsWraper : INativeMethods
{       
    public IntPtr SendMessage(IntPtr hwnd, uint msg, IntPtr wparam, StringBuilder lparam)
    {
        return NativeMethods.SendMessage(hwnd, msg, wparam, lparam);
    }

    public bool GetWindowRect(IntPtr hwnd, out Rect rect)
    {
        return NativeMethods.GetWindowRect(hwnd, out rect);
    }

    public IntPtr GetWindow(IntPtr hwnd, uint cmd)
    {
        return NativeMethods.GetWindow(hwnd, cmd);
    }

    public bool IsWindowVisible(IntPtr hwnd)
    {
        return NativeMethods.IsWindowVisible(hwnd);
    }

    public long GetTickCount64()
    {
        return NativeMethods.GetTickCount64();
    }

    public int GetClassName(IntPtr hwnd, StringBuilder classNameBuffer, int maxCount)
    {
        return NativeMethods.GetClassName(hwnd, classNameBuffer, maxCount);
    }

    public int DwmGetWindowAttribute(IntPtr hwnd, int attribute, out Rect rect, int sizeOfRect)
    {
        return NativeMethods.DwmGetWindowAttribute(hwnd, attribute, out rect, sizeOfRect);
    }

    public bool GetWindowPlacement(IntPtr hwnd, ref WindowPlacement pointerToWindowPlacement)
    {
        return NativeMethods.GetWindowPlacement(hwnd, ref pointerToWindowPlacement);
    }

    public int GetDeviceCaps(IntPtr hdc, int index)
    {
        return NativeMethods.GetDeviceCaps(hdc, index);
    }
}

让我们用 P\Invoke 导入来完成这个

internal static class NativeMethods
{
    [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
    public static extern int GetDeviceCaps(IntPtr hdc, int index);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool IsWindowVisible(IntPtr hWnd);

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, [Out] StringBuilder lParam);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

    [DllImport("kernel32.dll")]
    public static extern long GetTickCount64();

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetWindowRect(IntPtr hWnd, out Rect lpRect);

    [DllImport(@"dwmapi.dll")]
    public static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out Rect pvAttribute, int cbAttribute);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);
}

示例用法

internal class GetTickCount64TimeProvider : ITimeProvider
{
    private readonly INativeMethods _nativeMethods;

    public GetTickCount64TimeProvider(INativeMethods nativeMethods)
    {
        _nativeMethods = nativeMethods;
    }

    public Timestamp Now()
    {
        var gtc = _nativeMethods.GetTickCount64();

        var getTickCountStamp = Timestamp.FromMilliseconds(gtc);
        return getTickCountStamp;
    }
}

单元测试

难以置信,但可以通过模拟 WinApi 来验证任何期望

[Test]
public void GetTickCount64_ShouldCall_NativeMethod()
{
    var nativeMock = MockRepository.GenerateMock<INativeMethods>();            
    var target = GetTarget(nativeMock);

    target.Now();

    nativeMock.AssertWasCalled(_ => _.GetTickCount64());
}



[Test]
public void Now_ShouldReturn_Microseconds()
{
    var expected = Timestamp.FromMicroseconds((long) int.MaxValue * 1000);
    var nativeStub = MockRepository.GenerateStub<INativeMethods>();
    nativeStub.Stub(_ => _.GetTickCount64()).Return(int.MaxValue);
    var target = GetTarget(nativeStub);

    var actual = target.Now();

    Assert.AreEqual(expected, actual);
}


private static GetTickCount64TimeProvider GetTarget(INativeMethods nativeMock)
{
    return new GetTickCount64TimeProvider(nativeMock);
}

模拟out\ref参数可能会让人头疼,所以这里是供将来参考的代码:

[Test]
public void When_WindowIsMaximized_PaddingBordersShouldBeExcludedFromArea()
{
    // Top, Left are -8 when window is maximized but should be 0,0
    // http://blogs.msdn.com/b/oldnewthing/archive/2012/03/26/10287385.aspx
    INativeMethods nativeMock = MockRepository.GenerateStub<INativeMethods>();

    var windowRectangle = new Rect() {Left = -8, Top = -8, Bottom = 1216, Right = 1936};
    var expectedScreenBounds = new Rect() {Left = 0, Top = 0, Bottom = 1200, Right = 1920};
    _displayInfo.Stub(_ => _.GetScreenBoundsFromWindow(windowRectangle.ToRectangle())).Return(expectedScreenBounds.ToRectangle());

    var hwnd = RandomNativeHandle();
    StubForMaximizedWindowState(nativeMock, hwnd);
    StubForDwmRectangle(nativeMock, hwnd, windowRectangle);
    WindowCoverageReader target = GetTarget(nativeMock);

    var window = target.GetWindowFromHandle(hwnd);


    Assert.AreEqual(WindowState.Maximized, window.WindowState);
    Assert.AreEqual(expectedScreenBounds.ToRectangle(), window.Area);
}

private void StubForDwmRectangle(INativeMethods nativeMock, IntPtr hwnd, Rect rectToReturnFromWinApi)
{
    var sizeOf = Marshal.SizeOf(rect);
    var rect = new Rect();

    nativeMock.Stub(_ =>
    {
        _.DwmGetWindowAttribute(
            hwnd, 
            (int)DwmWindowAttribute.DwmwaExtendedFrameBounds,
            out rect,  // called with zeroed object
            sizeOf);
    }).OutRef(rectToReturnFromWinApi).Return(0);
}

private IntPtr RandomNativeHandle()
{
    return new IntPtr(_random.Next());
}

private void StubForMaximizedWindowState(INativeMethods nativeMock, IntPtr hwnd)
{
    var maximizedFlag = 3;
    WindowPlacement pointerToWindowPlacement = new WindowPlacement() {ShowCmd = maximizedFlag};
    nativeMock.Stub(_ => { _.GetWindowPlacement(Arg<IntPtr>.Is.Equal(hwnd), ref Arg<WindowPlacement>.Ref(new Anything(), pointerToWindowPlacement).Dummy); }).Return(true);
}
于 2015-09-26T01:27:29.597 回答