【问题标题】:Any way to get a yield-style IEnumerable from this Win32 function?有什么方法可以从这个 Win32 函数中获得产量风格的 IEnumerable 吗?
【发布时间】:2019-08-16 16:15:22
【问题描述】:

考虑以下实现 Win32 函数的人为设计的最小 LINQPad 示例CertEnumSystemStoreLocation。该概念扩展到 crypt32.dll 中的其他方法,但这是最简单的演示:

void Main()
{
    GetCertificateStoreLocations().Dump();
}

public static IEnumerable<string> GetCertificateStoreLocations()
{
    var list = new List<string>();

    NativeMethods.CertEnumSystemStoreLocationCallback locationCallback = (location, flags, reserved, state) =>
    {
        var name = Marshal.PtrToStringUni(location);
        list.Add(name);
        return true;
    };

    if (!NativeMethods.CertEnumSystemStoreLocation(0u, IntPtr.Zero, locationCallback))
        throw new CryptographicException(Marshal.GetHRForLastWin32Error());

    return list.AsReadOnly();
}

private static class NativeMethods
{
    /// <seealso href="https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certenumsystemstorelocation"/>
    [DllImport("crypt32", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool CertEnumSystemStoreLocation(uint reserved,
                                                          IntPtr stateObject,
                                                          CertEnumSystemStoreLocationCallback callback);

    /// <remarks>Implements PFN_CERT_ENUM_SYSTEM_STORE_LOCATION callback function</remarks>
    /// <seealso href="https://docs.microsoft.com/en-gb/windows/win32/api/wincrypt/nc-wincrypt-pfn_cert_enum_system_store_location"/>
    public delegate bool CertEnumSystemStoreLocationCallback(IntPtr storeLocation,
                                                             uint flags,
                                                             IntPtr reserved,
                                                             IntPtr stateObject);
}

此 Win32 函数的方法是为您枚举证书位置,并为其找到的每个对象运行用户提供的回调函数。它在语义上类似于我经常在跨项目的 Utils.cs 文件中看到的 static void ForEach(this IEnumerable&lt;T&gt;, Action&lt;T&gt;) 扩展名。

在我上面的简单实现中,我使用基于 lambda 的回调作为 List&lt;string&gt; 的闭包来收集 Win32 函数传递给我的 lambda 的值,然后返回整个列表。这满足了我的公共方法的 IEnumerable&lt;string&gt; 签名,但不是特别迭代

我想知道是否有任何合理的 C# 方法来取消整个 List&lt;string&gt; 创建/填充/返回,以支持适当的迭代器,用于枚举对象的数量并非微不足道的情况。

一个天真的尝试可能是从 lambda 内部使用 yield return,而不是 list.Add,但据我所知,yield return 不能从 lambda 内部使用。

我确实注意到 Win32 方法接受一个指向状态对象的指针,它实际上是您定义、初始化和编组的 struct,Win32 方法会将其传递给您的回调。这可能是可用的,但感觉就像它正在为IAsyncResult 风格的编程和管理WaitHandle 设置我,我希望使用异步编程来解决这个特殊问题只是去给我两个问题而不是一个。

所以我问有没有更好的方法来构造示例代码,可能允许更传统的迭代方法? 即更接近满足IEnumerable/yield return 语义的方法。 p>

(请注意,我不是在寻找传递Func&lt;T&gt;Action&lt;T&gt; 或委托给进行迭代的方法的方法——这与 crypt32.dll 选择的方法相同设计师。)

【问题讨论】:

  • yield 调用者想一一拉。 API 想一一推送。除了构建一个复杂的多线程邮箱/队列系统之外,我看不出如何调和这两者,在这种情况下这似乎有点矫枉过正。
  • @SimonMourier;这也是我所知道的,这就是为什么我认为我会向世界开放它,看看我是否遗漏了什么......

标签: c# winapi pinvoke


【解决方案1】:

问题在于,这些 Win32 调用希望通过调用内部自己的循环来驱动对回调的调用,并且 IEnumerator 还希望 C# foreach 循环或 LINQ 循环通过 MoveNext() 驱动控制流所以任何解决方案都必须在单独的线程中调用 Win32 调用——作为一种 kluged 协程。我认为没有办法避免使用两个线程。

下面我将 Win32 调用包装在一个线程中并使用两个同步事件实现回调,首先等待“有人要求下一个值”事件,然后设置“我刚刚更改了字符串值”事件。然后枚举器的 MoveNext() 方法设置第一个事件并等待第二个事件。这似乎可行,但在我看来,这通常比它的价值更麻烦......你可能需要锁定 _current。

class Program
{
    static void Main(string[] args)
    {
        foreach (string location in new CertEnumSystemStoreLocations())
            Console.WriteLine(location);
    }
}

public class CertEnumSystemStoreLocations : IEnumerable, IEnumerator<string>
{
    private EventWaitHandle _eventBeginMoveNext;
    private EventWaitHandle _eventEndMoveNext;
    private string _current;
    private Thread _thread;

    public CertEnumSystemStoreLocations()
    {
        _eventBeginMoveNext = new EventWaitHandle(false, EventResetMode.AutoReset);
        _eventEndMoveNext = new EventWaitHandle(false, EventResetMode.AutoReset);
        _thread = new Thread(new ThreadStart(CertEnumSystemStoreLocationThread));
        _thread.Start();
    }

    private void CertEnumSystemStoreLocationThread()
    {
        NativeMethods.CertEnumSystemStoreLocation(0, new IntPtr(), Callback);
        _eventBeginMoveNext.WaitOne();
        _current = null;
        _eventEndMoveNext.Set();
    }

    private bool Callback(IntPtr storeLocation, uint flags, IntPtr reserved, IntPtr stateObject)
    {
        _eventBeginMoveNext.WaitOne();
        _current = Marshal.PtrToStringUni(storeLocation);
        _eventEndMoveNext.Set();
        return true;
    }

    public string Current
    {
        get
        {
            return _current;
        }
    }

    object IEnumerator.Current
    {
        get
        {
            return Current;
        }
    }

    public void Dispose()
    {
    }

    public bool MoveNext()
    {
        _eventBeginMoveNext.Set();
        _eventEndMoveNext.WaitOne();
        return _current != null;
    }

    public void Reset()
    {
        // TODO ... you'd need to tell the callback in the thread to
        // stop waiting on events etc. and then wait for the whole 
        // thread to run out ... 
        throw new NotImplementedException();
    }

    public IEnumerator GetEnumerator()
    {
        return (IEnumerator)this;
    }
}

public static class NativeMethods
{
    [DllImport("crypt32", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool CertEnumSystemStoreLocation(uint reserved,
                                                          IntPtr stateObject,
                                                          CertEnumSystemStoreLocationCallback callback);

    public delegate bool CertEnumSystemStoreLocationCallback(IntPtr storeLocation,
                                                             uint flags,
                                                             IntPtr reserved,
                                                             IntPtr stateObject);
}

【讨论】:

    猜你喜欢
    • 2012-02-11
    • 1970-01-01
    • 2018-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-19
    • 1970-01-01
    • 2016-05-15
    相关资源
    最近更新 更多