如何连接COM事件调度程序?

VBIDE API公开了奇妙的神秘_dispVBComponentsEvents接口(以及其他),它看起来像我可以用来捕获VBE中的各种有趣事件。

所以我在一个类中实现了接口,该类打算捕获事件并为我的应用程序的其余部分引发“正常”.net事件,如下所示:

 public class VBComponentsEventDispatcher : _dispVBComponentsEvents { public event EventHandler<DispatcherEventArgs> ComponentAdded; public void ItemAdded(VBComponent VBComponent) { OnDispatch(ComponentAdded, VBComponent); } public event EventHandler<DispatcherEventArgs> ComponentRemoved; public void ItemRemoved(VBComponent VBComponent) { OnDispatch(ComponentRemoved, VBComponent); } public event EventHandler<DispatcherRenamedEventArgs> ComponentRenamed; public void ItemRenamed(VBComponent VBComponent, string OldName) { var handler = ComponentRenamed; if (handler != null) { handler.Invoke(this, new DispatcherRenamedEventArgs(VBComponent, OldName)); } } public event EventHandler<DispatcherEventArgs> ComponentSelected; public void ItemSelected(VBComponent VBComponent) { OnDispatch(ComponentSelected, VBComponent); } public event EventHandler<DispatcherEventArgs> ComponentActivated; public void ItemActivated(VBComponent VBComponent) { OnDispatch(ComponentActivated, VBComponent); } public event EventHandler<DispatcherEventArgs> ComponentReloaded; public void ItemReloaded(VBComponent VBComponent) { OnDispatch(ComponentReloaded, VBComponent); } private void OnDispatch(EventHandler<DispatcherEventArgs> dispatched, VBComponent component) { var handler = dispatched; if (handler != null) { handler.Invoke(this, new DispatcherEventArgs(component)); } } } 

我希望像这样使用这个类:

 var componentsEvents = new VBComponentsEventDispatcher(); componentsEvents.ComponentAdded += componentsEvents_ComponentAdded; componentsEvents.ComponentActivated += componentsEvents_ComponentActivated; //... 
 void componentsEvents_ComponentAdded(object sender, DispatcherEventArgs e) { Debug.WriteLine(string.Format("Component '{0}' was added.", e.Item.Name)); } void componentsEvents_ComponentActivated(object sender, DispatcherEventArgs e) { Debug.WriteLine(string.Format("Component '{0}' was activated.", e.Item.Name)); } 

但它不起作用,我没有调试输出,并且没有命中断点。 显然我不知道我在做什么。 MSDN在这个问题上完全无用,找到关于这个的文档比找到亨利八世的第三任妻子的婚前姓更难。

我做错了什么,如何让它发挥作用? 我是在正确的轨道上吗?

System.Runtime.InteropServices命名空间公开了一个静态ComEventsHelper类,用于将托管委托连接到非托管调度源。 这基本上与另一个答案完全相同,但连接点在运行时可调用包装器中处理,而不必从调用代码显式管理(从而使其更加健壮)。 我怀疑这就是PIA在内部处理源接口的方式(对有问题的Microsoft.Vbe.Interop进行反编译已经足够严重,这很难说)。

在这种情况下,由于某些不可思议的原因,有问题的接口未声明为源接口,因此PIA构建未连接运行时包装器中的事件处理程序。 所以…你可以在包装器类中手动连接处理程序并将它们作为包装事件转发,但仍然需要处理与RCW连接点的繁重(和线程安全管理)。 请注意,您需要来自引用类型库的2条信息_dispVBComponentsEvents接口的guid以及您有兴趣收听的非托管事件的DispId

 private static readonly Guid VBComponentsEventsGuid = new Guid("0002E116-0000-0000-C000-000000000046"); private enum ComponentEventDispId { ItemAdded = 1, ItemRemoved = 2, ItemRenamed = 3, ItemSelected = 4, ItemActivated = 5, ItemReloaded = 6 } 

然后,将它们连接到类包装器的ctor(为简洁起见,只显示一个)…

 private delegate void ItemAddedDelegate(VB.VBComponent vbComponent); private readonly ItemAddedDelegate _componentAdded; public VBComponents(VB.VBComponents target) { _target = target; _componentAdded = OnComponentAdded; ComEventsHelper.Combine(_target, VBComponentsEventsGuid, (int)ComponentEventDispId.ItemAdded, _componentAdded); } 

……并转发事件:

 public event EventHandler> ComponentAdded; private void OnComponentAdded(VB.VBComponent vbComponent) { OnDispatch(ComponentAdded, VBComponent); } private void OnDispatch(EventHandler> dispatched, VB.VBComponent component) { var handler = dispatched; if (handler != null) { handler.Invoke(this, new DispatcherEventArgs(new VBComponent(component))); } } 

完成后,通过调用ComEventsHelper.Remove取消注册委托:

 ComEventsHelper.Remove(_target, VBComponentsEventsGuid, (int)ComponentEventDispId.ItemAdded, _componentAdded); 

上面的示例根据问题使用包装类,但是如果您需要在处理COM事件或将其传递给其他侦听器之前将附加function附加到COM事件,则可以从任何地方使用相同的方法。

我是在正确的轨道上吗?

是。 您在事件接收器中拥有的内容 – 您缺少一些代码来向COM服务器注册接收器。

VBProjectsVBComponents接口实现(某处非常深) IConnectionPointContainer接口 – 您需要使用它来收集IConnectionPoint实例。 要取消注册接收器,您需要一个数据结构来记住注册步骤为您提供的int cookie

这是一个粗略的例子 – 假设您有一个包含以下字段的App类:

 private readonly IConnectionPoint _projectsEventsConnectionPoint; private readonly int _projectsEventsCookie; private readonly IDictionary> _componentsEventsConnectionPoints = new Dictionary>(); 

在构造函数的某处,您将使用IConnectionPoint.Advise注册接收器,并注册您的自定义事件处理程序:

 var sink = new VBProjectsEventsSink(); var connectionPointContainer = (IConnectionPointContainer)_vbe.VBProjects; Guid interfaceId = typeof (_dispVBProjectsEvents).GUID; connectionPointContainer.FindConnectionPoint(ref interfaceId, out _projectsEventsConnectionPoint); sink.ProjectAdded += sink_ProjectAdded; sink.ProjectRemoved += sink_ProjectRemoved; sink.ProjectActivated += sink_ProjectActivated; sink.ProjectRenamed += sink_ProjectRenamed; _projectsEventsConnectionPoint.Advise(sink, out _projectsEventsCookie); 

然后,当添加项目时,您将使用IConnectionPoint.Advise为每个组件注册一个接收器,然后您可以注册自定义事件处理程序,并在您的字典中添加一个条目:

 void sink_ProjectAdded(object sender, DispatcherEventArgs e) { var connectionPointContainer = (IConnectionPointContainer)e.Item.VBComponents; Guid interfaceId = typeof(_dispVBComponentsEvents).GUID; IConnectionPoint connectionPoint; connectionPointContainer.FindConnectionPoint(ref interfaceId, out connectionPoint); var sink = new VBComponentsEventsSink(); sink.ComponentActivated += sink_ComponentActivated; sink.ComponentAdded += sink_ComponentAdded; sink.ComponentReloaded += sink_ComponentReloaded; sink.ComponentRemoved += sink_ComponentRemoved; sink.ComponentRenamed += sink_ComponentRenamed; sink.ComponentSelected += sink_ComponentSelected; int cookie; connectionPoint.Advise(sink, out cookie); _componentsEventsConnectionPoints.Add(e.Item.VBComponents, Tuple.Create(connectionPoint, cookie)); } 

删除项目后,使用IConnectionPoint.Unadvise取消注册接收器,并删除字典条目:

 void sink_ProjectRemoved(object sender, DispatcherEventArgs e) { Tuple value; if (_componentsEventsConnectionPoints.TryGetValue(e.Item.VBComponents, out value)) { value.Item1.Unadvise(value.Item2); _componentsEventsConnectionPoints.Remove(e.Item.VBComponents); } } 

然后,您可以在处理程序中运行所需的任何代码:

 void sink_ComponentAdded(object sender, DispatcherEventArgs e) { _parser.State.OnParseRequested(e.Item); } 

如果您的App类中有Dispose方法,那么这将是清理任何残余的好地方:

 public void Dispose() { _projectsEventsConnectionPoint.Unadvise(_projectsEventsCookie); foreach (var item in _componentsEventsConnectionPoints) { item.Value.Item1.Unadvise(item.Value.Item2); } }