从C++调用IConnectionPointImpl接口的问题通过模态WinForms调用

Cho*_*ett 6 c# c++ com vba iconnectionpoint

我们有一个本机C++应用程序,它支持一些不同类型的VBA宏.其中一种类型,VBAExtension使用核心C++应用程序注册自己,从而产生(一个派生自的类)的实例IConnectionPointImpl<Extension, &DIID_IExtensionEvents, CComDynamicUnkArray>.这很好用; 在给定适当的VBAExtension对象的情况下,核心和其他VBA宏都可以访问IExtensionEvents上的方法.

我们还有一个.NET程序集(用C#编写),它也在运行时加载到核心应用程序中.由于历史原因,程序集由自动运行的VBA宏加载; 然后,当用户按下特定按钮时,另一个VBA宏运行程序集的主入口点,这会打开一个System.Windows.Forms对话框以进行进一步的交互.

这就是设置.我看到一些奇怪的行为VBAExtension从.NET程序集中访问方法.具体来说,我从程序集中的各个位置运行以下代码:

foreach (VBAExtension ve in app.Extensions)
{
    System.Diagnostics.Debug.Print("Ext: " + ve.Name);
}
Run Code Online (Sandbox Code Playgroud)

如果我从程序集的主对象的构造函数中运行它; 或者从程序集的主入口点(在显示对话框之前),一切都很好 - 我得到了VBAExtension打印出的s 的名称.

但是,如果我从程序集中的一个按钮(模态 - 我们正在调用form.ShowDialog())WinForm 启动的命令中运行相同的代码,则ve.Names都是空白的.子类的pDispatch->Invoke调用IConnectionPointImpl成功(返回S_OK),但不设置任何返回变量.

如果我将对话框更改为非模态(调用方式form.Show()),则名称会再次起作用.表单的模态(模态?)似乎会影响IConnectionPointImpl调用是否成功.

有谁知道发生了什么?

编辑:自从第一次发布以来,我已经证明了重要的不是调用调用堆栈; 相反,它是否是从模态对话框进行调用.我已经更新了正文.

编辑2:根据Hans Passant的回答,以下是他的诊断问题的答案:

  • 正如预期的那样,在良好(无模式)的情况下,如果我重命名VBA事件处理程序则没有错误.该调用只是不返回任何数据.
  • 我已经将一个MsgBox调用放入VBA处理程序中; 它在无模式情况下显示,但在模态情况下不显示.因此,处理程序不会在模态情况下执行.
  • 通过使用Err,我可以告诉我,如果我们在VBA处理程序中遇到异常,我们会得到一个VBA错误对话框.清除后,C++ Invoke调用将0x80020009("异常发生")作为返回码,并且pExcepInfo填入一般故障值(VBA已吞下实际细节)
  • 事件不会在模式对话框的第二个显示上触发,可以紧跟在第一个对话框之后,也可以在第二次调用C#加载项时触发.

我将尝试深入挖掘我们的消息循环作为下一步.

Han*_*ant 4

这个问题中没有什么确凿的事实可以作为答案。可能是一些非常简单的事情,可能是一个令人讨厌的内存损坏问题,或者是 VBA 解释器内部对线程状态的模糊依赖。粗略的诊断是 VBA 事件处理程序根本没有运行。一般来说,这并不是一个罕见的事故,Basic 中用于声明事件处理程序的声明式风格几乎没有什么好的方法来诊断订阅问题。许多 VBA 程序员在尝试解决像这样的“为什么事件处理程序没有运行”问题时都犯了难。

首先收集一些确凿的事实并将其添加到您的问题中:

  • 首先验证您的 C++ 代码实际上可以看到根本没有事件处理程序。使用好的版本,重命名事件处理程序。期望您不会这样做,引发接收器未订阅的事件不是错误。
  • 验证事件处理程序是否实际在错误版本中执行。让它做一些除了分配 BSTR 参数之外的事情,有些你可以很容易地看到,比如磁盘上的文件。
  • 验证您是否可以正确诊断事件处理程序中的异常。分配 Err 对象并验证您的 C++ 代码是否生成正确的诊断。请注意,您的 IDispatch::Invoke() 调用为 pExcepInfo 传递 NULL,这不是生成诊断的好方法。
  • 检查该事件是否在您第二次显示窗口时运行,如果是,则存在执行顺序问题。

重点关注 ShowDialog()。这种方法确实有很多副作用。第一个不再起作用的是 C++ 代码中的消息循环。现在由 .NET 消息循环来调度消息。看看你的副作用,比简单的 GetMessage/DispatchMessage() 做更多的工作。这项工作不再完成。还要在代码库中搜索 PostThreadMessage(),当 .NET 代码泵送时,这些消息会掉到地板上。

请记住,当 C# 代码调用 ShowDialog() 时,您的本机 C++ 代码将失去控制。在您关闭窗口之前,它不会重新获得控制权。这可能会触发一个简单的执行顺序问题,您的 C++ 代码在执行完使 C# 代码运行的所有操作后不应执行任何重要操作。