有什么方法可以从这个 Win32 函数中获取产量样式的 IEnumerable?



请考虑以下实现 Win32 函数 CertEnumSystemStoreLocation 的人为的最小 LINQPad 示例。 这个概念扩展到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://learn.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://learn.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<T>, Action<T>)扩展名。

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

我想知道是否有任何合理的 C# 方法来取消整个List<string>创建/填充/返回,转而使用适当的迭代器,用于枚举对象数量不小的情况。

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

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

所以我问是否有更好的方法来构建可能允许更传统的迭代方法的示例代码?即更接近IEnumerable/yield return语义的语义。

(请注意,我不是在寻找一种将Func<T>Action<T>或委托传递给执行迭代的方法的方法——这与 crypt32.dll 设计人员选择的方法相同。

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

下面我将 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);
}

最新更新