Regfree COM事件从其他线程失败

我有一个COM可见的.NET类,它暴露事件并从VB6使用。 在过去的几天里,我一直试图让这个与regfree COM一起工作,但没有成功。

  • 当从原始线程触发事件时,VB6事件以regfree模式运行。
  • 当注册了类型库时,VB6事件在从另一个线程触发时运行。 (regasm / tlb / codebase后跟regasm / codebase / unregister,后者不注销tlb)

在regfree模式下从另一个线程触发时,它会抛出exception,因此永远不会执行VB6事件代码。

System.Reflection.TargetException: Object does not match target type. at System.RuntimeType.InvokeDispMethod(String name, BindingFlags invokeAttr, Object target, Object[] args, Boolean[] byrefModifiers, Int32 culture, String[] namedParameters) at System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams) at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Int32[] aWrapperTypes, MessageData& msgData) at Example.Vb6RegFreeCom.IExampleClassEvents.TestEvent() at Example.Vb6RegFreeCom.ExampleClass.OnTestEvent(Action func) in ExampleClass.cs:line 78 

我可以想到两种情况:1)清单缺少与tlb注册相关的内容,或者2)创建新线程时激活上下文丢失。 不幸的是,我不知道如何找出是哪种情况,或者甚至可能是由其他东西引起的。

以下是显示我的问题的基本示例。

清单(VB6可执行文件)

          

清单(C#DLL)

      <!--    -->  

C# (平台目标:x86)

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; using Timer = System.Threading.Timer; using FormsTimer = System.Windows.Forms.Timer; namespace Example.Vb6RegFreeCom { [ComVisible(true)] [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] [Guid("467EB602-B7C4-4752-824A-B1BC164C7962")] public interface IExampleClass { [DispId(1)] int Test(int mode); } [ComVisible(true)] [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] [Guid("2669EBDB-16D9-45C8-B0A3-ED2CEE26862C")] public interface IExampleClassEvents { [DispId(1)] void TestEvent(); } [ComVisible(true)] [ClassInterface(ClassInterfaceType.None)] [ComSourceInterfaces(typeof(IExampleClassEvents))] [Guid("8D51802D-0DAE-40F2-8559-7BF63C92E261")] public class ExampleClass: IExampleClass { public event Action TestEvent; public int Test(int mode) { var tempEvent = TestEvent; if (tempEvent == null) return -1; switch (mode) { case 0: tempEvent(); break; case 1: var staThread = new Thread(() => OnTestEvent(tempEvent) ); //if (!staThread.TrySetApartmentState(ApartmentState.STA)) MessageBox.Show("Failed to set STA thread."); staThread.Start(); break; case 2: var invoker = new Invoker(); var otherThread = new Thread(() => invoker.Invoke((Action)(() => OnTestEvent(tempEvent)))); otherThread.Start(); break; case 3: var timer = new FormsTimer(); timer.Tick += (_1, _2) => { timer.Dispose(); OnTestEvent(tempEvent); }; timer.Interval = 100; timer.Start(); break; default: return -2; } return 1; } internal static void OnTestEvent(Action func) { try { func(); } catch (Exception err) { MessageBox.Show(err.ToString()); } } } internal class Invoker : Control { internal Invoker() { this.CreateHandle(); } } } 

VB6

 Option Explicit Dim WithEvents DotNetObject As ExampleClass Private Sub cmdImmediate_Click() CallDotNet 0 End Sub Private Sub cmdOtherThread_Click() CallDotNet 1 End Sub Private Sub cmdSameThread_Click() CallDotNet 2 End Sub Private Sub Form_Load() Set DotNetObject = New ExampleClass End Sub Private Sub CallDotNet(TestMode As Long) Dim ReturnValue As Long ReturnValue = DotNetObject.Test(TestMode) If ReturnValue  1 Then MsgBox "Return value is " & ReturnValue End Sub Private Sub DotNetObject_TestEvent() MsgBox "Event was raised." End Sub 

使用multithreading时,必须对呼叫进行编组。 这需要额外的信息,这些信息由comInterfaceExternalProxyStubtypelib元素提供。 我曾尝试过这些,但直到现在才找到合适的组合。

清单更改(C#DLL)

         

一旦我走上了正确的轨道,我发现了几个指向正确方向的指针。 我遇到的最好的描述如下。 在我的例子中,也使用了IDispatch。

摘录自“COM组件的免注册激活:演练” http://msdn.microsoft.com/en-us/library/ms973913.aspx

这些元素提供了注册表中可能存在的信息。 comInterfaceExternalProxyStub元素为类型库编组提供了足够的信息,它适用于从IDispatch派生的COM接口(包括所有自动化接口)。 在这些情况下,ole32.dll提供了使用的外部代理存根(即,在程序集中的文件外部)。 如果您的COM组件仅实现调度或双接口,那么这是您应该使用的元素。