簡體   English   中英

為什么COM事件處理程序始終為空?

[英]Why are COM event handlers always null?

使用本文 ,我已經設置了這個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#代碼中被BeginParse ,但所有處理程序都為null ,因此VBA處理程序不會運行:

所有處理程序在C#代碼中都為空

我究竟做錯了什么?

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM