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

时间:2013-06-03 08:22:09

标签: c# vb6 com-interop

这是一个非常简单的.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>

3 个答案:

答案 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一起使用,则会将这些内容添加到注册表中,清单文件中的等效项是元素typelibcomInterfaceExternalProxyStub。 VB6可执行文件可以编译为本机二进制文件。

有关详细信息,请参阅我的SO主题:Regfree COM event fails from other thread

答案 2 :(得分:0)

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

注意:一位数字有变化!

此处提供更多信息:http://www.mazecomputer.com/sxs/help/proxy.htm