这是一个非常简单的.net< - >使用事件的COM互操作示例。
只要我在.net库的Visual Studio构建选项中使用regasm或注册用于com interop 选项,此示例就可以正常工作。 但我需要使用免费注册互操作并行清单进行部署。
应用程序在并排模式下运行得很好,只是事件似乎消失了。我怀疑它是一些线程编组问题,但我似乎无法找到正确的解决方案。
这当然是尝试通过稍微复杂的互操作集成来复制我遇到的问题。与现实问题相比,我在这里遇到的问题有一个区别:
两个解决方案都无法在免注册部署中运行时正确接收.net代码中引发的事件,并且当.net dll在注册表中注册时,两个解决方案都按预期工作。 但是:在“真实”项目中,当从System.Reflection.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
清单文件的内容:
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>
答案 0 :(得分:4)
经过很长一段时间(以及几次失败的尝试)事实证明我可以通过做一个微小的改变来完成这项工作:
使VB6代码编译为P代码而不是本机代码。
我很确定这会以某种方式影响线程之间的编组处理,但我一直无法找到确认该理论的任何信息。
至少它有效......
或不是! (2013年10月24日)
事实证明,在现实生活中编译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结果列表中现已遗忘的文章).enoke方法用于确保在创建该方法的线程上执行方法调用给定组件,从而确保它在消息抽取的UI线程上发生。
我认为这可能与我的情况有关,所以我作弊。
我让我的interop类继承了winforms控件
public class InteropConnection : Control, IInteropConnection
现在我将我的调用包装在Invoke方法
中if (OnHappened != null)
{
try
{
Invoke(OnHappened, theMessage);
}
catch (Exception e)
{
Messagebox.Show(e.GetType().Name + " : " + e.message)
}
}
现在我遇到了一个异常,因为Control没有分配 WindowHandle 。
事实证明,Control类有一个方便的CreateHandle()
方法,可以调用并解决这个特殊问题。 (我不知道这会带来什么后果,as the documentation does not recommend calling this method directly。
现在所有人似乎都在工作,但是如果有什么东西突然出现并且咬我的话我也不会感到惊讶......
答案 1 :(得分:2)
我遇到了同样的问题。 COM可以封送事件/调用正确的线程,但它需要有一个代理存根。如果您将/tlb
选项与regasm一起使用,则会将这些内容添加到注册表中,清单文件中的等效项是元素typelib
和comInterfaceExternalProxyStub
。 VB6可执行文件可以编译为本机二进制文件。
有关详细信息,请参阅我的SO主题:Regfree COM event fails from other thread
答案 2 :(得分:0)
我们遇到了同样的问题。 在我们的情况下,我们必须将proxyStubClsid32更改为{00020420-0000-0000-C000-000000000046}。
注意:一位数字有变化!