6

这是一个使用事件的非常简单的 .net <-> COM 互操作示例。

只要我在 .net 库的 Visual Studio 构建选项中使用 regasm 或register for 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>
4

3 回答 3

4

经过很长一段时间(以及几次失败的尝试),事实证明我可以通过做一个微小的改变来完成这项工作:

使 VB6 代码编译为 P-Code 而不是本机代码。

我很确定这会以某种方式影响线程之间的编组处理方式,但我一直无法找到任何证实该理论的信息。

至少它有效...

或不!(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 结果列表上的文章),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)
  }
}

现在我遇到了一个异常,因为控件没有分配WindowHandle

事实证明,Control 类有一个方便的CreateHandle()方法,可以调用并解决这个特定问题。(我不知道这有什么可能的后果,因为文档不建议直接调用此方法

现在一切似乎一直在工作,尽管如果现在有新的东西跳起来咬我,我不会感到惊讶......

于 2013-10-09T10:35:03.607 回答
2

我遇到了同样的问题。COM 可以将事件/调用编组到正确的线程,但它需要有一个代理存根。如果您使用带有 regasm 的选项,这些将被添加到注册表/tlb中,并且清单文件中的等价物是元素typelibcomInterfaceExternalProxyStub. VB6 可执行文件可以编译为本机二进制文件。

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

于 2014-04-29T10:54:48.970 回答
0

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

注意:有一位数的变化!

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

于 2020-08-27T07:38:18.080 回答