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

Mat*_*don 3 c# com event-handling dispatch

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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

var componentsEvents = new VBComponentsEventDispatcher();
componentsEvents.ComponentAdded += componentsEvents_ComponentAdded;
componentsEvents.ComponentActivated += componentsEvents_ComponentActivated;
//...
Run Code Online (Sandbox Code Playgroud)
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));
}
Run Code Online (Sandbox Code Playgroud)

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

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

Mat*_*don 5

我是在正确的轨道上吗?

是.您在事件接收器中拥有的内容 - 您缺少一些代码来向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>>(); 
Run Code Online (Sandbox Code Playgroud)

在构造函数的某处,您将使用注册接收器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);
Run Code Online (Sandbox Code Playgroud)

然后,当添加项目时,您将使用注册每个组件的接收器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));
}
Run Code Online (Sandbox Code Playgroud)

删除项目后,使用取消注册接收器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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以在你的处理程序中运行你想要的任何代码:

void sink_ComponentAdded(object sender, DispatcherEventArgs<VBComponent> e)
{
    _parser.State.OnParseRequested(e.Item);
}
Run Code Online (Sandbox Code Playgroud)

如果你班上有一个Dispose方法App,这将是一个清理任何残余物的好地方:

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

  • 有关底层COM事件架构的更多详细信息:http://www.codeproject.com/Articles/9014/Understanding-COM-Event-Handling和https://msdn.microsoft.com/en-us/library/windows/desktop /ms686567(v=vs.85).aspx (2认同)