【问题标题】:Why are COM event handlers always null?为什么 COM 事件处理程序总是为空?
【发布时间】:2016-03-29 01:18:34
【问题描述】:

使用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

虽然从对象浏览器看一切都是正确的:

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

我做错了什么?

【问题讨论】:

  • 您链接到的文章说“我们需要 ClassInterface 属性,我们需要将其设置为 None。”将ParserState 上的属性更改为[[ClassInterface(ClassInterfaceType.None)] 有帮助吗?
  • @RomanR。 Handles 语法是 VB.NET...
  • 我将您的声明复制/粘贴到新项目中并且效果很好 - Excel VBA 确实看到了事件界面并且事件返回 VBA。我能想到的是,你的情况不知何故线程不好,尤其是。如果您使用工作线程并在它们之间传递指针,然后在 COM 单元之间传递指针,使它们在一个线程上不可用,而在另一个线程上正常。代码 sn-p 本身并没有显示假定的线程问题。
  • @Roman 解析器状态确实是从后台线程更新的。如果您可以写一个解释需要从 UI 线程调用 COM 事件的答案(我将编辑问题中的代码以包含 _dispatcher 字段),您将获得简单的代表提升!谢谢一百万!

标签: c# events com


【解决方案1】:

您的 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

【讨论】:

  • 所以.. 确认,“简单案例”确实有效。困难的部分将是在 UI/主线程上引发解析器事件。
  • 在 C++ 开发中,我通常在原始 STA 线程中创建一个工作窗口,然后工作人员将事件信息排入内部集合,并对工作窗口执行 PostMessage。消息处理程序接收消息并处理列表以引发实际事件。我不确定 C# 最合适和/或优雅的等价物是什么(基于窗口的计时器来轮询而不是工作窗口?)。我绝对会避免来自工作线程的任何阻塞调用。
猜你喜欢
  • 2020-03-06
  • 2015-08-29
  • 2010-11-27
  • 1970-01-01
  • 2011-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多