【问题标题】:DllImport with out SafeHandle results in MissingMethodException没有 SafeHandle 的 DllImport 导致 MissingMethodException
【发布时间】:2017-12-17 06:17:00
【问题描述】:

我为 QuickUsb 编写了一个包装类来访问 c# 中的非托管库。有关完整的实现,请参阅gist

这个问题的主要兴趣点是以下部分:

public class QuickUsbPort
{
    private class SafeQuickUsbHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        [DllImport("QuickUsb.dll", CharSet = CharSet.Ansi)] static extern
            int QuickUsbClose(IntPtr handle);

        public SafeQuickUsbHandle(IntPtr handle) : base(true)
        {
            SetHandle(handle);
        }

        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        protected override bool ReleaseHandle()
        {
            return QuickUsbClose(handle) != 0;
        }
    }

    private static class NativeLib
    {
        [DllImport("QuickUsb.dll", CharSet = CharSet.Ansi)] static extern
            int QuickUsbOpen(out SafeQuickUsbHandle handle, string deviceName);

        public static SafeQuickUsbHandle Open(string deviceName)
        {
            if (QuickUsbOpen(out SafeQuickUsbHandle handle, deviceName) == 0)
            {
                throw new QuickUsbException("Open", new List<Tuple<string, string>>
                {
                    new Tuple<string, string>("deviceName", deviceName),
                });
            }
            return handle;
        }
    }
}

在调用Open() 时,将handle 编组为SafeQuickUsbHandle 似乎存在问题,此代码会引发MissingMethodException。但是,以下修改不会引发此类异常:

        [DllImport("QuickUsb.dll", CharSet = CharSet.Ansi)] static extern
            int QuickUsbOpen(out IntPtr handle, string deviceName);

        public static SafeQuickUsbHandle Open(string deviceName)
        {
            if (QuickUsbOpen(out IntPtr handle, deviceName) == 0)
            {
                throw new QuickUsbException("Open", new List<Tuple<string, string>>
                {
                    new Tuple<string, string>("deviceName", deviceName),
                });
            }
            return new SafeQuickUsbHandle(handle);
        }

所以我想知道我是否遗漏了 SafeQuickUsbHandle 实现的某些方面,以允许 c# 正确编组和处置句柄。

请注意,在 dll 中,handle 是指向句柄的指针:

/// <param name="handle">
/// A PQHANDLE that points to a QHANDLE in which to place the new device ID.
/// If successful, hDevice will contain the new QHANDLE</param>

【问题讨论】:

  • 任何变量都需要内存。调用方法(或 dll)时,参数列表被放入执行堆栈,当函数返回时,执行堆栈被释放。因此,您丢失了任何返回变量,因为该变量在堆栈上。因此,任何 OUT 声明都必须在调用方法中分配内存。我想查看 c 语言代码,因为我怀疑您使用的是指针,这就是代码使用第二种方法的原因。
  • @jdweng 我无权访问 c 代码,但是是的,它是一个指针。 QuickUsbOpen 创建一个句柄并将其作为输出参数而不是返回值返回。返回值表示成功。通过将 typename 放在函数调用中是预先定义句柄然后将其传递给函数以便它在堆栈上的简写。也许这就是问题所在。除了我做的以外,也许没有办法解决这个问题

标签: c# pinvoke


【解决方案1】:

您必须为派生自 SafeHandle 的类提供公共无参数构造函数,尤其是当您使用 p/invoke 时,如下定义:SafeHandle

你的 SafeHandle 子类只需要提供三个方法

.ctor() – 初始化 SafeHandle 的默认构造函数。这 当 P/Invoke 返回 SafeHandle 给您的 进程

bool IsInvalid { get; } - 一个属性来确定是否 句柄的当前值是否有效

bool ReleaseHandle() – 清理包含的资源

p/invoke 无论如何都会神奇地设置值。也在official documentation:

当您从 SafeHandle 继承时,您必须覆盖以下内容 成员:IsInvalid 和 ReleaseHandle。您还应该提供一个 默认构造函数,它使用一个值调用基本构造函数 表示一个无效的句柄值,一个布尔值表示 本机句柄是否归 SafeHandle 所有,因此 应该在释放该 SafeHandle 后释放。

由于SafeHandleZeroOrMinusOneIsInvalid 没有定义公共无参数构造函数,你必须这样做。

【讨论】:

  • 一定错过了那个细节(很多教程,这是我真正的问题哈哈)。很好的调用,尽管我相信即使SafeHandleZeroOrMinusOneIsInvalid 提供了一个公共无参数构造函数,我仍然需要实现自己的构造函数,因为构造函数不是在 c# 中继承的
  • 对于任何经过这里的未来用户:事实证明 SafeHandleZeroOrMinusOneIsInvalid 是用于 QuickUsb 的错误链,因为零是有效值。因此,我直接从 SafeHandle 继承并添加了 public override bool IsInvalid { get { return handle.ToInt32() &lt; 0; } }
  • 显然构造函数不必是publicprivate 也适用于我(.NET 4.5.1 / .NET Standard 2.0),太棒了,因为它不会弄乱公共接口。
猜你喜欢
  • 1970-01-01
  • 2018-12-07
  • 2011-03-31
  • 1970-01-01
  • 1970-01-01
  • 2012-09-06
  • 1970-01-01
  • 2018-12-26
  • 1970-01-01
相关资源
最近更新 更多