6

我们有一个本地 C++ 应用程序,它通过 COM 支持一些各种类型的 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);
}

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

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

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

有谁知道发生了什么?

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

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

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

作为下一步,我将尝试深入研究我们的消息循环。

4

1 回答 1

4

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

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

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

稍微关注一下 ShowDialog()。这种方法确实有很多副作用。不再起作用的第一件事是 C++ 代码中的消息循环。现在是 .NET 消息循环来分派消息。看看你的副作用,比简单的 GetMessage/DispatchMessage() 做更多的工作。这项工作不再完成。还要在您的代码库中搜索 PostThreadMessage(),当 .NET 代码运行时,这些消息会掉到地上。

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

于 2015-10-17T10:02:46.793 回答