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

时间:2015-10-07 15:57:45

标签: c# c++ vba com iconnectionpoint

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

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

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

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

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

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

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

任何人都知道发生了什么事?

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

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

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

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

1 个答案:

答案 0 :(得分: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#代码运行。