如何挂接 COM 事件调度程序



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

因此,我在一个类中实现了该接口,该类旨在捕获事件并引发"正常".net 事件供应用程序的其余部分处理,如下所示:

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

我希望像这样使用类:

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

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

做错了什么,我如何让它工作?我走在正确的轨道上吗?

我走在正确的轨道上吗?

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

VBProjectsVBComponents 接口实现(在很深的地方)IConnectionPointContainer接口 - 您需要使用它来收集IConnectionPoint实例。若要取消注册接收器,需要一个数据结构来记住注册步骤为你提供的int cookie

下面是一个粗略的示例 - 假设您有一个包含以下字段的App类:

private readonly IConnectionPoint _projectsEventsConnectionPoint;
private readonly int _projectsEventsCookie;
private readonly IDictionary<VBComponents, Tuple<IConnectionPoint, int>>  _componentsEventsConnectionPoints = 
    new Dictionary<VBComponents, Tuple<IConnectionPoint, int>>(); 

在构造函数中的某个位置,你将使用 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<VBProject> 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<VBProject> e)
{
    Tuple<IConnectionPoint, int> 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<VBComponent> 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);
    }
}

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<DispatcherEventArgs<IVBComponent>> ComponentAdded;
private void OnComponentAdded(VB.VBComponent vbComponent)
{
    OnDispatch(ComponentAdded, VBComponent);
}
private void OnDispatch(EventHandler<DispatcherEventArgs<IVBComponent>> dispatched, VB.VBComponent component)
{
    var handler = dispatched;
    if (handler != null)
    {
        handler.Invoke(this, new DispatcherEventArgs<IVBComponent>(new VBComponent(component)));
    }
}

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

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

上面的示例对每个问题使用包装类,但如果需要在处理 COM 事件或将其传递给其他侦听器之前将其他功能附加到 COM 事件,则可以从任何位置使用相同的方法。

相关内容

  • 没有找到相关文章

最新更新