简体   繁体   English

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

[英]Problems calling IConnectionPointImpl interface from C++ invoked via modal WinForms

We have a native C++ application which supports some VBA macros of various types over COM. 我们有一个本机C ++应用程序,它通过COM支持一些各种类型的VBA宏。 One of these types, VBAExtension , registers itself with the core C++ application, resulting in an instance of (a class derived from) IConnectionPointImpl<Extension, &DIID_IExtensionEvents, CComDynamicUnkArray> . 其中一种类型VBAExtension向核心C ++应用程序注册,从而导致IConnectionPointImpl<Extension, &DIID_IExtensionEvents, CComDynamicUnkArray>的实例(派生自IConnectionPointImpl<Extension, &DIID_IExtensionEvents, CComDynamicUnkArray> This works fine; 这很好用; both core and other VBA macros can access the methods on IExtensionEvents, given an appropriate VBAExtension object. 给定适当的VBAExtension对象,核心VBA和其他VBA宏都可以访问IExtensionEvents上的方法。

We also have a .NET assembly (written in C#) which is also loaded in to the core application at run-time. 我们还有一个.NET程序集(用C#编写),该程序集也在运行时加载到核心应用程序中。 For historical reasons, the assembly is loaded in by an auto-running VBA macro; 由于历史原因,程序集是通过自动运行的VBA宏加载的; then, when the user presses a particular button, another VBA macro runs the main entry point of the assembly, which brings up a System.Windows.Forms dialog for further interaction. 然后,当用户按下特定按钮时,另一个VBA宏将运行程序集的主入口点,这将弹出System.Windows.Forms对话框以进行进一步的交互。

That's the setup. 这就是设置。 I'm seeing some weird behaviour accessing the VBAExtension methods from within the .NET assembly. 我看到从.NET程序VBAExtension访问VBAExtension方法有一些奇怪的行为。 Specifically, I am running the following code from various locations in the assembly: 具体来说,我正在程序集中的不同位置运行以下代码:

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

If I run it from the constructor of the assembly's main object; 如果我从程序集主对象的构造函数运行它; or from the assembly's main entry point (before the dialog is displayed), everything is fine – I get the names of the VBAExtension s printed out. 或从程序集的主入口点开始(显示对话框之前),一切都很好–我得到了打印出来的VBAExtension的名称。

However, if I run the same code from a command kicked off by a button in the assembly's ( modal - we're calling form.ShowDialog() ) WinForm, the ve.Name s are all blank. 但是,如果我从程序集的( 模态 -我们称为form.ShowDialog() )WinForm中的按钮开始的命令中运行相同的代码,则ve.Name都为空。 The pDispatch->Invoke call made by the IConnectionPointImpl subclass succeeds (returns S_OK), but does not set any return vars. IConnectionPointImpl子类进行的pDispatch->Invoke调用成功(返回S_OK),但未设置任何返回变量。

If I change the dialog to be non-modal (invoked with form.Show() ), then the names work again. 如果我将对话框更改为非模式对话框(由form.Show()调用),则名称将再次起作用。 The modality (modalness?) of the form appears to affect whether the IConnectionPointImpl calls succeed. 表单的形式(模态?)似乎会影响IConnectionPointImpl调用是否成功。

Anyone know what's going on? 有人知道发生了什么吗?

Edit: Since first posting, I've demonstrated that it's not the invoking call stack that matters; 编辑:自从第一次发布以来,我已经证明,重要的不是调用调用堆栈; instead, it's whether the call is made from a modal dialog. 而是通过模态对话框进行调用。 I have updated the main text. 我已经更新了正文。

Edit 2: Per Hans Passant's answer, here are the answers to his diagnostic questions: 编辑2: Per Hans Passant的答案,以下是他的诊断性问题的答案:

  • As expected, in the good (modeless) case there is no error if I rename the VBA event handler. 不出所料,在正常情况下(无模式),如果我重命名VBA事件处理程序,则没有错误。 The call simply returns no data. 该调用仅不返回任何数据。
  • I've put a MsgBox call into the VBA handler; 我已经将MsgBox调用放入VBA处理程序中; it displays in the modeless case, but does not in the modal case. 它在无模式情况下显示,但在无模式情况下不显示。 Ergo, the handler is not executed in the modal case. 因此,处理程序不会在模式情况下执行。
  • By use of Err , I can tell that if we hit an exception in the VBA handler we get a VBA error dialog. 通过使用Err ,我可以告诉我们,如果我们在VBA处理程序中遇到异常,则会出现一个VBA错误对话框。 Once clearing that, the C++ Invoke call has 0x80020009 ("Exception occurred") as return code, and pExcepInfo filled in with generic failure values (VBA has swallowed the actual details) 清除该错误后,C ++ Invoke调用将0x80020009(“发生异常”)作为返回代码,并用通用失败值填充了pExcepInfo(VBA吞噬了实际的详细信息)
  • The event does not fire on the second display of the modal dialog, either immediately following the first dialog or during a second invocation of the C# add-in. 在第一个对话框之后或在C#加载项的第二次调用期间,该事件不会在模式对话框的第二个显示上触发。

I'll try to dig into our message loops as a next step. 下一步,我将尝试深入探讨我们的消息循环。

There are excessively few hard facts in this question to build an answer on. 这个问题上几乎没有什么硬道理可以作为答案。 Could be something very simple, could be a nasty memory corruption problem or an obscure dependency inside the VBA interpreter on the thread state. 可能很简单,可能是令人讨厌的内存损坏问题,也可能是VBA解释器内部对线程状态的模糊依赖。 The rough diagnostic is that the VBA event handler simply did not run. 粗略的诊断是VBA事件处理程序根本没有运行。 That is not an uncommon accident in general, the declarative style used in Basic to declare event handlers leave very few good way to ever diagnose subscription problems. 一般而言,这并不是罕见的事故,Basic中用于声明事件处理程序的声明式样式几乎没有诊断订阅问题的好方法。 Many a VBA programmer has lost clumps of hair trying to troubleshoot a "why did the event handler not run" problem like this one. 许多VBA程序员在解决此类“为什么事件处理程序为什么不运行”的问题时,一头雾水。

Gather some hard facts first and add them to your question: 首先收集一些困难的事实,并将其添加到您的问题中:

  • First verify that your C++ code can actually see that there is no event handler at all. 首先,验证您的C ++代码实际上可以看到根本没有事件处理程序。 Use the good version, rename the event handler. 使用良好的版本,重命名事件处理程序。 Expectation is that you don't, raising an event that the sink doesn't subscribe is not an error. 期望您不这样做,引发接收器未订阅的事件不是错误。
  • Verify that the event handler actually executed in the bad version. 验证事件处理程序实际上是在错误版本中执行的。 Have it do something else than assign the BSTR argument, some you can easily see like a file on disk. 除了分配BSTR参数外,还可以执行其他操作,您可以轻松地看到某些内容,例如磁盘上的文件。
  • Verify that you can properly diagnose an exception in the event handler. 验证您可以在事件处理程序中正确诊断异常。 Assign the Err object and verify that your C++ code generates a proper diagnostic. 分配Err对象,并验证您的C ++代码生成正确的诊断。 Note that your IDispatch::Invoke() call passes NULL for pExcepInfo, not a good way to generate a diagnostic. 请注意,您的IDispatch :: Invoke()调用会为pExcepInfo传递NULL,这不是生成诊断的好方法。
  • Check if the event runs the second time you display a window, if it does then you have an execution order problem. 检查事件是否在第二次显示窗口时运行,如果运行,则说明执行顺序有问题。

Focusing a bit on ShowDialog(). 将重点放在ShowDialog()上。 This method does have plenty of side-effects. 这种方法确实有很多副作用。 The first thing that no longer works is the message loop in your C++ code. 不再起作用的第一件事是C ++代码中的消息循环。 It is now the .NET message loop that dispatches messages. 现在是.NET消息循环分发消息。 Look at yours for side-effects, doing more work than simply GetMessage/DispatchMessage(). 看看您的副作用,除了简单地执行GetMessage / DispatchMessage()之外,还要做更多的工作。 That work no longer gets done. 这项工作不再完成。 Also search your codebase for PostThreadMessage(), those messages fall on the floor when the .NET code pumps. 还要在您的代码库中搜索PostThreadMessage(),当.NET代码启动时,这些消息就会掉在地上。

And keep in mind that your native C++ code loses control when the C# code calls ShowDialog(). 并且请记住,当C#代码调用ShowDialog()时,您的本机C ++代码将失去控制。 It doesn't regain control until you close the window. 关闭窗口后,它才能重新获得控制权。 That can trigger a simple order-of-execution problem, your C++ code should not do anything important after does whatever it does to get the C# code running. 这可能会触发一个简单的执行顺序问题,即在使C#代码运行之后,您的C ++代码不应做任何重要的事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM