当使用并排清单部署时,.net代码中引发的事件似乎不会发生在COM代码中
本文关键字:代码 事件 COM net 单部署 部署 | 更新日期: 2023-09-27 18:16:27
这是一个使用事件的。net <-> COM互操作示例。
这个例子工作得很好,只要我使用regasm或者在Visual studio的。net库的构建选项中使用注册com interop选项。但我需要部署使用注册免费互操作启用并排清单。
应用程序在并行模式下运行良好,只是事件似乎消失了。我怀疑这是一些线程编组问题,但我似乎找不到正确的解决方案。
这当然是试图复制我在稍微复杂的互操作集成中遇到的问题。与实际问题相比,我在这里遇到的问题有一个不同之处:
两种解决方案都无法正确接收在。net代码中引发的事件,而运行在reg-free部署上,并且当。net dll在注册表中注册时,两种解决方案都按预期工作。然而:在"真正的"项目上,当它从system . reflect . target失败时,我得到一个运行时错误。在这个简化的例子中,它只是无声地失败。
我完全被这个问题困住了,所以任何建议和解决方案都将非常受欢迎。
我已经把完整的代码放在github上,如果有人需要在回答之前玩它:https://github.com/Vidarls/InteropEventTest
.net部分using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace InteropEventTest
{
[Guid("E1BC643E-0CCF-4A91-8499-71BC48CAC01D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComVisible(true)]
public interface ITheEvents
{
void OnHappened(string theMessage);
}
[Guid("77F1EEBA-A952-4995-9384-7228F6182C32")]
[ComVisible(true)]
public interface IInteropConnection
{
void DoEvent(string theMessage);
}
[Guid("2EE25BBD-1849-4CA8-8369-D65BF47886A5")]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(ITheEvents))]
[ComVisible(true)]
public class InteropConnection : IInteropConnection
{
[ComVisible(false)]
public delegate void Happened(string theMessage);
public event Happened OnHappened;
public void DoEvent(string theMessage)
{
if (OnHappened != null)
{
Task.Factory.StartNew(() => OnHappened(theMessage));
}
}
}
}
COM (VB6)部分
Private WithEvents tester As InteropEventTest.InteropConnection
Private Sub Command1_Click()
Call tester.DoEvent(Text1.Text)
End Sub
Private Sub Form_Load()
Set tester = New InteropConnection
End Sub
Private Sub tester_OnHappened(ByVal theMessage As String)
Text2.Text = theMessage
End Sub
我目前有以下文件/文件夹结构用于部署:
Root
|-> [D] Interop.Event.Tester
|-> Interop.Event.Tester.manifest
|-> [D] InteropEventTest
|-> InteropEventTest.dll
|-> InteropEventTest.manifest
|-> InteropEventTest.tlb
|-> tester.exe
|-> tester.exe.manifest
manifest文件的内容:
Interop.Event.Test.manifest
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity name="Interop.Event.Tester" version="1.0.0.0" type="win32" processorArchitecture="x86"/>
</assembly>
InteropEventTest.manifest
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity name="InteropEventTest" version="1.0.0.0" type="win32"/>
<clrClass
name="InteropEventTest.InteropConnection"
clsid="{2EE25BBD-1849-4CA8-8369-D65BF47886A5}"
progid="InteropEventTest.InteropConnection"
runtimeVersion="v4.0.30319"
threadingModel="Both"/>
<file name="InteropEventTest.tlb">
<typelib
tlbid="{5CD6C635-503F-4103-93B0-3EBEFB91E500}"
version="1.0"
helpdir=""
flags="hasdiskimage"/>
</file>
<comInterfaceExternalProxyStub
name="ITheEvents"
iid="{E1BC643E-0CCF-4A91-8499-71BC48CAC01D}"
proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
baseInterface="{00000000-0000-0000-C000-000000000046}"
tlbid="{5CD6C635-503F-4103-93B0-3EBEFB91E500}" />
</assembly>
tester.exe.manifest
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity name="tester.exe" version="1.0.0.0" type="win32" processorArchitecture="x86"/>
<dependency>
<dependentAssembly>
<assemblyIdentity name="InteropEventTest" version="1.0.0.0" type="win32"/>
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly>
<assemblyIdentity name="Interop.Event.Tester" version="1.0.0.0" type="win32" processorArchitecture="x86"/>
</dependentAssembly>
</dependency>
</assembly>
经过很长一段时间(和几次失败的尝试),我发现我可以通过做一个小小的改变来使它工作:
使VB6代码编译成p - code而不是本机代码
我很确定这在某种程度上影响了线程之间的封送处理,但我无法找到任何信息来证实这一理论。
至少它有效…
!(24。2013年10月)
事实证明,在现实生活中,编译成p - code是不够的。在此模式的另一个实现中,我们最终得到事件消失在的任何地方,没有异常(我们认为),也没有痕迹。因此需要进行更多的调查:
1。真正的问题
在try-catch子句中包装事件触发表明实际上有一个异常被抛出,它只是从未出现在任何地方
if (OnHappened != null)
{
try
{
OnHappened(theMessage));
}
catch (Exception e)
{
Messagebox.Show(e.GetType().Name + " : " + e.message)
}
}
异常为TargetException (the object does not match the target type)
。一些研究表明,这很可能是线程问题(正如我之前所怀疑的那样)。
2。解决方案
大多数关于这个问题的文章似乎都是通过使用Invoke方法来解决的。事实证明,大多数其他人试图解决这个问题是建立winforms应用程序,因此有一个方便的Ìnvoke(Delegate)
方法可用于所有的窗体和控件。
由于Winforms在幕后也做了相当多的COM互操作(根据现在在google结果列表中被遗忘的文章),invoke方法用于确保在创建给定组件的线程上执行方法调用,从而确保它发生在消息泵送的UI线程上。
我想这可能和我的案子有关,所以我作弊了。
我使我的互操作类继承自winforms控件
public class InteropConnection : Control, IInteropConnection
现在我将调用包装在Invoke方法
中if (OnHappened != null)
{
try
{
Invoke(OnHappened, theMessage);
}
catch (Exception e)
{
Messagebox.Show(e.GetType().Name + " : " + e.message)
}
}
现在我得到了一个异常,因为控件没有分配 windowandle 。
结果是Control类有一个方便的CreateHandle()
方法,可以调用并解决这个特定的问题。(我不知道这会有什么可能的后果,因为文档不建议直接调用此方法。
现在一切似乎都在工作,尽管现在如果有新的东西跳出来咬我,我也不会感到惊讶…
我也遇到过同样的问题。COM可以将事件/调用封送到正确的线程,但它需要有一个代理存根。如果您将/tlb
选项与regasm一起使用,则将它们添加到注册表中,清单文件中的等效元素是typelib
和comInterfaceExternalProxyStub
。VB6可执行文件可以编译成本地二进制文件。
有关更多信息,请参阅我的SO主题:Regfree COM事件从其他线程失败
我们遇到了同样的问题。在我们的例子中,我们必须将proxyStubClsid32更改为{0002042 -0000-0000- c000 -000000000046}。
注意:有一个数字的变化!
更多信息在这里:http://www.mazecomputer.com/sxs/help/proxy.htm