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



我们有一个本机C++应用程序,它通过COM支持一些不同类型的VBA宏。其中一个类型VBAExtension将自己注册到核心C++应用程序中,从而生成IConnectionPointImpl<Extension, &DIID_IExtensionEvents, CComDynamicUnkArray>的实例(派生自(。这很好;给定适当的VBAExtension对象,core和其他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都是空的。IConnectionPointImpl子类进行的pDispatch->Invoke调用成功(返回S_OK(,但不设置任何返回变量。

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

有人知道发生了什么事吗?

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

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

  • 正如预期的那样,在良好(无模式(的情况下,如果重命名VBA事件处理程序,则不会出现错误。该调用只是不返回任何数据
  • 我在VBA处理程序中放入了一个MsgBox调用;它在非模态情况下显示,但在模态情况下不显示。呃,在模态情况下,处理程序是不执行的
  • 通过使用Err,我可以判断,如果我们在VBA处理程序中遇到异常,我们会得到一个VBA错误对话框。清除后,C++Invoke调用将0x80020009("发生异常"(作为返回代码,pExcepInfo将填充通用故障值(VBA已吞下实际详细信息(
  • 该事件不会在模式对话框的第二次显示中激发,无论是在第一次对话框之后,还是在第二次调用C#插件期间

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

这个问题中几乎没有什么确凿的事实可以作为答案。可能是非常简单的问题,可能是严重的内存损坏问题,也可能是VBA解释器内部对线程状态的模糊依赖。粗略的诊断是VBA事件处理程序根本没有运行。一般来说,这并不是一个罕见的意外,Basic中用于声明事件处理程序的声明性风格几乎没有留下诊断订阅问题的好方法。许多VBA程序员在试图解决像这样的"为什么事件处理程序没有运行"问题时,都会不知所措。

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

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

专注于ShowDialog((。这种方法确实有很多副作用。第一件不再工作的事情是C++代码中的消息循环。现在是.NET消息循环来调度消息。看看你的副作用,比简单的GetMessage/DispatchMessage((做更多的工作。那项工作已无法完成。还要在代码库中搜索PostThreadMessage((,当.NET代码启动时,这些消息就会掉到地板上。

请记住,当C#代码调用ShowDialog((时,您的原生C++代码将失去控制。直到你关上窗户,它才会重新获得控制。这可能会引发一个简单的执行顺序问题,您的C++代码在做了任何事情来运行C#代码之后,都不应该做任何重要的事情。

相关内容

最新更新