简体   繁体   English

为什么COM事件处理程序始终为空?

[英]Why are COM event handlers always null?

Using this article , I've set up this COM-visible interface to define my events: 使用本文 ,我已经设置了这个COM可见界面来定义我的事件:

[ComVisible(true)]
[Guid("3D8EAA28-8983-44D5-83AF-2EEC4C363079")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IParserStateEvents
{
    void OnParsed();
    void OnReady();
    void OnError();
}

The events are meant to be fired by a class that implements this interface: 这些事件是由实现此接口的类触发的:

[ComVisible(true)]
public interface IParserState
{
    void Initialize(VBE vbe);

    void Parse();
    void BeginParse();

    Declaration[] AllDeclarations { get; }
    Declaration[] UserDeclarations { get; }
}

Here's the implementation: 这是实施:

[ComVisible(true)]
[Guid(ClassId)]
[ProgId(ProgId)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[ComDefaultInterface(typeof(IParserState))]
[ComSourceInterfaces(typeof(IParserStateEvents))]
[EditorBrowsable(EditorBrowsableState.Always)]
public class ParserState : IParserState
{
    //...
    public event Action OnParsed;
    public event Action OnReady;
    public event Action OnError;

    private void _state_StateChanged(object sender, System.EventArgs e)
    {
        var errorHandler = OnError; // always null
        if (_state.Status == Parsing.VBA.ParserState.Error && errorHandler != null)
        {
            errorHandler.Invoke();
        }

        var parsedHandler = OnParsed; // always null
        if (_state.Status == Parsing.VBA.ParserState.Parsed && parsedHandler != null)
        {
            parsedHandler.Invoke();
        }

        var readyHandler = OnReady; // always null
        if (_state.Status == Parsing.VBA.ParserState.Ready && readyHandler != null)
        {
            readyHandler.Invoke();
        }
    }
    //...

The _state_StateChanged handler is responding to events raised from a background worker thread. _state_StateChanged处理程序正在响应从后台工作程序线程引发的事件。


The COM client code is a VBA class looking like this: COM客户端代码是一个VBA类,如下所示:

Private WithEvents state As Rubberduck.ParserState

Public Sub Initialize()
    Set state = New Rubberduck.ParserState
    state.Initialize Application.vbe
    state.BeginParse
End Sub

Private Sub state_OnError()
    Debug.Print "error"
End Sub

Private Sub state_OnParsed()
    Debug.Print "parsed"
End Sub

Private Sub state_OnReady()
    Debug.Print "ready"
End Sub

While everything looks right from the Object Browser : 虽然从对象浏览器看起来一切正常:

对象浏览器看起来正确

...when the VBA code calls BeginParse , breakpoints get hit in the C# code, but all handlers are null , and so the VBA handlers don't run: ...当VBA代码调用BeginParse ,断点会在C#代码中被BeginParse ,但所有处理程序都为null ,因此VBA处理程序不会运行:

所有处理程序在C#代码中都为空

What am I doing wrong? 我究竟做错了什么?

Your COM/VBA integration is about right, however you need to keep in mind COM threading model and rules of using your COM class in single threaded apartment. 您的COM / VBA集成是正确的,但是您需要记住COM线程模型和在单线程单元中使用COM类的规则。

You have your instance of Rubberduck.ParserState created on STA thread. 您在STA线程上创建了Rubberduck.ParserState实例。 VBA immediately sees WithEvents specifier and does its best connecting event handlers to the connection point implemented by COM class. VBA立即看到WithEvents说明符,并将最好的连接事件处理程序连接到COM类实现的连接点。 Specifically, COM class receives COM interface pointers to accept event calls on the same thread and stores the pointer to use it later at event invocation time. 具体来说,COM类接收COM接口指针以接受同一线程上的事件调用并存储指针以便稍后在事件调用时使用它。

When you raise the event, both server (C#) and client (VBA) might or might not check if execution takes place on proper thread (rather, proper apartment). 当您举起事件时,服务器(C#)和客户端(VBA)可能会也可能不会检查执行是否发生在正确的线程上(而不是正确的公寓)。 With C++ development you might have a chance to ignore threading mismatch (it is not a good thing but let's assume you know what you're doing), and environments like VBA and .NET COM interop are stricter trying to take care of integrity of environment overall and they are likely to fail if threading is wrong. 使用C ++开发,您可能有机会忽略线程不匹配(这不是一件好事,但让我们假设您知道自己在做什么),而VBA和.NET COM互操作等环境则更加严格地考虑环境的完整性整体而言,如果线程错误,它们可能会失败。 That is, you have to raise your event on the right thread! 也就是说,你必须在正确的线程上举起你的活动! If you have a background worker thread, you cannot raise event from it directly and you need to pass it first to the apartment thread where the call is actually expected. 如果你有一个后台工作线程,你不能直接从它提出事件,你需要先将它传递给实际需要调用的公寓线程。

If your threading issue were limited to invocation from worker thread, the problem would rather be non-null event sinks calling which you get an exception or otheriwse the call not reaching your VBA. 如果您的线程问题仅限于从工作线程调用,则问题将是非空事件接收器调用您将获得异常或其他情况下调用未到达您的VBA。 You have then null however, so it is likely that threading affects in another way (instantiation from certain callback on a worker thread etc.) Either way, once you violate COM rule of not passing interface pointer between apartments, the pointers become unusable, causing failures on calls or being unable to provide expected cast and so on). 然后你有null,所以很可能线程以另一种方式影响(从工作线程上的某些回调实例化等)。无论哪种方式,一旦违反不在公寓之间传递接口指针的COM规则,指针就会变得无法使用,导致通话失败或无法提供预期演员等等)。 Having it fixed you will have the events working. 解决这个问题后,您可以使用这些活动。

Bonus code: minimal C# project and XLS file proving the events work fine in simplest form ( Subversion / Trac ). 奖金代码:最小的C#项目和XLS文件证明事件以最简单的形式工作( Subversion / Trac )。

Event is raised right from Initialize call: 从Initialize调用直接引发事件:

public void Initialize()
{
    if (OnReady != null)
        OnReady();
}
Private Sub Worksheet_Activate()
    If state Is Nothing Then Set state = New ComEvents01.ParserState
    ' Initialize below will have C# raise an event we'd receive state_OnReady
    state.Initialize
End Sub

Private Sub state_OnReady()
    ' We do reach here from Initialize and Worksheet_Activate
End Sub

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM