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

时间:2016-03-25 02:24:04

标签: c# events com

使用this article,我已设置此COM可见界面来定义我的事件:

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

事件意味着由实现此接口的类触发:

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

    void Parse();
    void BeginParse();

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

以下是实施:

[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();
        }
    }
    //...

_state_StateChanged处理程序正在响应从后台工作线程引发的事件。

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

虽然从对象浏览器

看起来一切正常

object browser looks right

...当VBA代码调用BeginParse时,断点会被C#代码命中,但所有处理程序都是null,因此VBA处理程序不会运行:

all handlers are null in the C# code

我做错了什么?

1 个答案:

答案 0 :(得分:2)

您的COM / VBA集成是正确的,但是您需要记住COM线程模型和在单线程单元中使用COM类的规则。

您在STA线程上创建了Rubberduck.ParserState的实例。 VBA立即看到WithEvents说明符,并将最好的连接事件处理程序连接到COM类实现的连接点。具体来说,COM类接收COM接口指针以接受同一线程上的事件调用 并存储指针以便稍后在事件调用时使用它。

当你举起事件时,服务器(C#)和客户端(VBA)可能会也可能不会检查执行是否发生在正确的线程上(而不是正确的公寓)。使用C ++开发,您可能有机会忽略线程不匹配(这不是一件好事,但让我们假设您知道自己在做什么),而VBA和.NET COM互操作等环境则更加严格地考虑环境的完整性整体而言,如果线程错误,它们可能会失败。也就是说,你必须在正确的线程上举起你的活动!如果你有一个后台工作线程,你不能直接从中提取事件,你需要先将它传递给实际需要调用的公寓线程。

如果您的线程问题仅限于从工作线程调用,则问题将是非空事件接收器调用,您将获得异常或其他方式调用未到达您的VBA。然后你有null,所以很可能线程以另一种方式影响(从工作线程上的某些回调实例化等)。无论哪种方式,一旦违反不在公寓之间传递接口指针的COM规则,指针就会变得无法使用,导致通话失败或无法提供预期演员等等)。解决这个问题后,您可以使用这些活动。

奖金代码:最小的C#项目和XLS文件证明事件以最简单的形式正常工作(Subversion / Trac)。

事件是从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