F# Reactive 从 COM 订阅非标准事件

F# Reactive subscribe to non-standard events from COM

我有一个本机 COM 库(我可以对其进行修改),并且我有一个 F# 应用程序试图使用项目中引用的 Interop 库中的 Rx 使用事件。

let events = obj :?> _IInteropEvents_Event
let disposable = 
    Observable.take 1 events.ComEventArrived
    |> Observable.subscribe (fun sender data -> ()) // sender : ISender, data : IData

这里的错误信息,我没有完全理解,是:

The event 'ComEventArrived' has a non-standard type. If this event is declared in another CLI language, you may need to access this event using the explicit add_ComEventArrived and remove_ComEventArrived methods for the event. If this event is declared in F#, make the type of the event an instantiation of either 'IDelegateEvent<>' or 'IEvent<,_>'.

我不介意使用 add_ComEventArrived,但我不知道如何使用 Observable

奇怪的是,ComEventArrived 有 2 个内部互操作类型的参数,如果我尝试订阅其他仅编组 IUnknown 的事件,它会起作用并且我没有收到 "non-standard type" 错误:

let events = obj :?> _ISnapshotEvents_Event
let disposable = 
    Observable.take 1 events.SnapshotEventArrived
    |> Observable.subscribe (fun sender -> ()) // sender : IUnknown (unit)

我怎样才能解决问题?

  1. 修改 COM 库以修复非标准事件类型错误。
  2. 对观察者使用显式 add_/remove_ 函数。take/Observer.subscribe.
  3. 取消订阅前触发事件 n 次的其他方法,无需使用 mutable/lock.

我目前已阅读:

我不太了解 COM 互操作,但我可以举一个将非标准事件转换为 IObservable 的示例,这应该涵盖您的第二点。

非标准事件的一个示例是 AppDomain.CurrentDomain.AssemblyResolve,其中事件处理程序需要 return Assembly 而不是 returning unit。要使用 add_remove_ 函数将其包装到 IObservable 中,您可以这样写:

let assemblyResolve = 
  { new IObservable<_> with
      member x.Subscribe(observer) =
        let handler = ResolveEventHandler(fun o a -> 
          let mutable res = None 
          observer.OnNext((o, a, fun a -> res <- Some a))
          res.Value )
        AppDomain.CurrentDomain.add_AssemblyResolve(handler)
        { new IDisposable with
            member x.Dispose() = 
              AppDomain.CurrentDomain.remove_AssemblyResolve(handler) } }

我们使用 F# 对象表达式创建 IObservable 的新实现。在 Subscribe 成员中,我们创建一个 ResolveEventHandler 并使用 add_AssemblyResolve 添加它。 Subscribe 的结果是 IDisposable 实现,然后使用 remove_AssemblyResolve.

注销事件处理程序

这里的丑陋之处在于观察者的 OnNext 函数不能 return 任何东西,因此我们改为给它一个带有参数的三元素元组以及一个设置 return 值(这是 AssemblyResolve 特有的,所以我不怀疑你会需要这样的东西)。