I have a COM visible .NET class which exposes events and is used from VB6. For the last couple of days I have been trying to get this to work with regfree COM, but without success.
When firing from another thread in regfree mode it throws an exception, thus the VB6 event code is never executed.
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
There are two scenarios I can think of: 1) the manifest is missing something related to the tlb registration, or 2) the activation context is lost when creating the new thread. Unfortunately, I don't know how to find out which is the case, or maybe it is even caused by something else.
Below is a basic example showing my problem.
Manifest (VB6 executable)
<?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>
Manifest (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# (platform target: 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
With multi-threading the calls have to be marshalled. This requires extra information, which is provided by the comInterfaceExternalProxyStub
and typelib
element. I had experimented with those, but did not find the right combination until now.
Manifest changes (C# DLL)
<file name="Example.Vb6RegFreeCom.dll" hashalg="SHA1">
<typelib tlbid="{FABD4158-AFDB-4223-BB09-AB8B45E3816E}" version="1.0"
flags="hasdiskimage" helpdir="" />
</file>
<comInterfaceExternalProxyStub name="IExampleClassEvents"
iid="{2669EBDB-16D9-45C8-B0A3-ED2CEE26862C}"
tlbid="{FABD4158-AFDB-4223-BB09-AB8B45E3816E}"
proxyStubClsid32="{00020420-0000-0000-C000-000000000046}">
</comInterfaceExternalProxyStub>
<comInterfaceExternalProxyStub name="IExampleClass"
iid="{467EB602-B7C4-4752-824A-B1BC164C7962}"
tlbid="{FABD4158-AFDB-4223-BB09-AB8B45E3816E}"
proxyStubClsid32="{00020420-0000-0000-C000-000000000046}">
</comInterfaceExternalProxyStub>
Once I was on the right track I found several pointers into the right direction. The best description I came across is below. In my example also IDispatch was used.
Excerpt from "Registration-Free Activation of COM Components: A Walkthrough" http://msdn.microsoft.com/en-us/library/ms973913.aspx
These elements provide information that would otherwise be present in the registry. The comInterfaceExternalProxyStub element provides enough information for type library marshalling to occur and it is appropriate for COM interfaces that derive from IDispatch (which includes all Automation interfaces). In these cases ole32.dll provides the external proxy-stub used (i.e., external to the files in the assembly). If your COM components implement only dispatch or dual interfaces then this is the element you should use.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With