当使用并排清单部署时,.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>

当使用并排清单部署时,.net代码中引发的事件似乎不会发生在COM代码中

经过很长一段时间(和几次失败的尝试),我发现我可以通过做一个小小的改变来使它工作:

使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一起使用,则将它们添加到注册表中,清单文件中的等效元素是typelibcomInterfaceExternalProxyStub。VB6可执行文件可以编译成本地二进制文件。

有关更多信息,请参阅我的SO主题:Regfree COM事件从其他线程失败

我们遇到了同样的问题。在我们的例子中,我们必须将proxyStubClsid32更改为{0002042 -0000-0000- c000 -000000000046}。

注意:有一个数字的变化!

更多信息在这里:http://www.mazecomputer.com/sxs/help/proxy.htm