我有一个COM可见的.NET类,该类公开事件并从VB6中使用。在过去的几天里,我一直在尝试使用自由注册COM来使其工作,但是没有成功。
- 当事件从原始线程触发时,VB6事件以无需注册的模式运行。
- 当类型库已注册时(regasm /tlb /codebase,然后是regasm /codebase /unregister,后者不会取消注册tlb),从另一个线程触发时,VB6事件会运行。
当在自由注册模式下从另一个线程触发时,它会抛出异常,因此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可执行文件)
<?xml version="1.0" encoding="utf-8"?>
<assembly xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1 assembly.adaptive.xsd" manifestVersion="1.0" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<assemblyIdentity name="VB6COM" version="1.0.0.0" type="win32" />
<dependency xmlns="urn:schemas-microsoft-com:asm.v2">
<dependentAssembly codebase="Example.Vb6RegFreeCom.tlb">
<assemblyIdentity name="Example.Vb6RegFreeCom" version="1.0.0.0" publicKeyToken="B5630FCEE39CF455" language="neutral" processorArchitecture="x86" />
</dependentAssembly>
</dependency>
</assembly>
清单文件(C# DLL)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity name="Example.Vb6RegFreeCom" version="1.0.0.0" publicKeyToken="b5630fcee39cf455" processorArchitecture="x86"></assemblyIdentity>
<clrClass clsid="{8D51802D-0DAE-40F2-8559-7BF63C92E261}" progid="Example.Vb6RegFreeCom.ExampleClass" threadingModel="Both" name="Example.Vb6RegFreeCom.ExampleClass" runtimeVersion="v4.0.30319"></clrClass>
<file name="Example.Vb6RegFreeCom.dll" hashalg="SHA1"></file>
<!--
<file name="Example.Vb6RegFreeCom.TLB">
<typelib tlbid="{FABD4158-AFDB-4223-BB09-AB8B45E3816E}" version="1.0" flags="" helpdir="" />
</file>
-->
</assembly>
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
(Note: This is already the translated content in simplified Chinese. The original text was in English and I did not change its meaning, only translated it.)
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
DotNetObject_TestEvent
中的MsgBox
在哪个线程上执行?(它必须在VB6对象创建的STA上执行--从其他公寓调用将涉及到封送,这就是STA存在的全部意义) - wqwForm_Load
中,您在主线程上创建了C#对象(Set DotNetObject = New ExampleClass
),因此没有使用任何封送处理,也无法从单独的线程中引发事件。您必须在单独的线程上创建coclass(New ExampleClass
),将接口封送回来,然后再进行下沉。 - wqw