TL;DR: A COM event which works in most circumstances, throws a System.Reflection.TargetException
when
- The COM server is deployed using registration free SxS
- And the event is raised by a WCF service host
We're using a complicated architecture to modernize a suite of enterprise applications that are over 30 years old. We've been using WCF for years to trigger COM events in several of our applications. This allows us to gain access to data and business processes within our legacy apps and expose that data to any modern app which uses a WCF client. We accomplish this by building COM servers which expose events for the app to subscribe to and a single COM method which, when invoked, creates a WCF service host that triggers these events when message requests are received. We recently updated one of our legacy apps – an old Windows desktop application which provides the primary user interface for the core of our system and also supplies the bulk of business operations for our system. It can be run in a "silent" mode which allows it to be used similar to a primitive NT service. It can perform background operations as well as service requests sent to it. We recently built a .NET COM server which can provide some modern WPF windows for this legacy app. This is our first foray exposing WPF windows from our legacy GDI-based app. I mention this because the COM server can raise events when triggered from the WPF components the server supplies. This same COM server can alternatively maintain a WCF service host, which raises the same COM events that the WPF UI does, to support the legacy app's "silent" mode – when the app is running without UI. All of this works and has been deployed to production without issues.
However, I've recently started exploring registration-free COM for this particular COM server. I've been surprisingly successful to a point. When the UI is running, the WPF components all work just fine, and these COM events are successfully handled by the legacy app. When the app is running in silent mode, the client app successfully instantiates and configures the COM server as well as successfully binds to the COM events. The COM server successfully initializes a WCF service host. But when the host receives a message and attempts to raise one of these events, this exception is thrown before the event is raised. The client is never notified of the event. Just to clarify, the particular event I am focusing on here is successfully raised and handled when the COM server's WPF components trigger it, but not when the COM's WCF service host triggers it.
Object does not match target type.
(System.Reflection.TargetException)Source: mscorlib Target site: InvokeDispMethod Declaring type: System.RuntimeType
Stack Trace: --------------------------------------------------- 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 MyApp.IMyCOMServer.RetrieveProcesses()
at MyApp.MyCOMServer.OnRetrieveProcesses() at MyApp.MyService.d__7.MoveNext()
One important difference between the WPF components and the WCF components is threading. The legacy app is a single threaded apartment app, so all WPF events are pumped and handled by the app's UI thread. This is where I would expect failure because this was no easy task to accomplish, and is very new architecture for us. But when the app is running without UI, the WCF service processes each message on a new thread. The legacy app is written in Visual FoxPro, which supports COM automation servers and handles all the messiness around COM events and threading issues for us. We've been extensively using COM events in our VFP apps for over 20 years and have a vast amount of experience in this area, so this is where I would expect the least problems. Threading has never been an issue for our WCF service hosts before, yet here I am. This is the relevant portion of the COM event interface.
#region COM Event Delegates
[ComVisible(false)]
public delegate ProcessList RetrieveProcessesDelegate();
#endregion
[ComVisible(true)]
[Guid(ComDefinitions.EventsInterfaceId)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IMyCOMEvents
{
[DispId(30)]
ProcessList RetrieveProcesses();
}
Here is the relevant portion of the COM server interface. The COM client instantiates the COM server, then subscribes to events exposed by IMyCOMEvents defined above, then finally calls StartWcfService defined below. The client then idles, listening for events to handle.
[ComVisible(true)]
[Guid(ComDefinitions.InterfaceId)]
public interface IMyCOMServer
{
[Description("Initializes and starts the WCF service host ")]
void StartWcfService();
}
Here is the relevant portion of the COM server. When the exception is thrown the RetrieveProcesses event is not null – it appears the COM client has successfully subscribed. ProcessList is a POCO-like COM server that the COM client is expected to instantiate and return, though I've also tried refactoring the event definition so the client returns a System.String
, but the same exception is thrown. ProcessList also supports serialization using the System.Runtime.Serialization.DataContractSerializer
, which is pertinent to the WCF service when I get to that further down.
[ComVisible(true)]
[Guid(ComDefinitions.ClassId)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IMyCOMEvents))]
[ProgId(ComDefinitions.ProgId)]
public class MyCOMServer : IMyCOMServer
{
private ServiceHost _svc;
public event RetrieveProcessesDelegate RetrieveProcesses;
public ProcessList OnRetrieveProcesses()
{
return RetrieveProcesses?.Invoke();
}
public void StartWcfService()
{
if (_svc != null)
{
// Service is already running;
return;
}
_svc = new ServiceHost(new MyService(this));
var binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None)
{
CloseTimeout = TimeSpan.FromMinutes(1d),
OpenTimeout = TimeSpan.FromMinutes(1d),
ReceiveTimeout = TimeSpan.FromHours(5d),
SendTimeout = TimeSpan.FromHours(5d),
TransferMode = TransferMode.Streamed,
HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
MaxBufferPoolSize = 524288L,
MaxBufferSize = 2147483647,
MaxConnections = 5,
MaxReceivedMessageSize = 2147483647L,
ReaderQuotas =
{
MaxDepth = 32,
MaxStringContentLength = 10000000,
MaxArrayLength = 16384,
MaxBytesPerRead = 16384,
MaxNameTableCharCount = 100000
}
};
var endpoint = _svc.AddServiceEndpoint(typeof(IMyService), binding, MessageDefinitions.MyUrl);
_svc.Open();
}
catch (Exception ex)
{
// Write to event log
EventWriter.Write(ex);
throw new COMException(ex.Message, ex);
}
}
internal struct ComDefinitions
{
public const string TypeLibId = "0ABC52FB-9B16-4E61-A869-8244650EC62C";
public const string ClassId = "CB3CCF16-68F4-4C50-8047-2A2FBE379B43";
public const string InterfaceId = "032A0A65-056C-4038-A121-7CA21D34A4D1";
public const string EventsInterfaceId = "B0294518-9A1A-425B-B6EE-87BDA09CA0C0";
public const string ProgId = "MyCOMServer.ProgId";
}
Here is the relevant portion of the WCF service instance. The service returns a Task primarily to support our WCF clients, which are async all-the-way-up.
[ComVisible(false)]
[ServiceContract(Namespace = MessageDefinitions.Namespace)]
public interface IMyService
{
[OperationContract(AsyncPattern = true)]
[FaultContract(typeof(ServiceFaultInfo))]
Task<ProcessList> RetrieveProcessesAsync();
}
[ComVisible(false)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
public class MyService : IMyService
{
private readonly MyCOMServer _comServer;
public MyService(MyCOMServer comServer)
{
_comServer = comServer;
}
public Task<ProcessList> RetrieveProcessesAsync()
{
try
{
var result = _comServer.OnRetrieveProcesses() ?? new ProcessList();
return Task.FromResult(result);
}
catch (Exception ex)
{
// Log exception
EventWriter.Write(ex);
var reason = new FaultReason(ex.Message);
var code = new FaultCode(_vfpFaultCode, MessageDefinitions.Namespace);
throw new FaultException<ServiceFaultInfo>(new ServiceFaultInfo(ex), reason, code);
}
}
}
Here is the relevant COM client code, which is written in Visual FoxPro.
LOCAL loComInstance, loComEventHandler
loComInstance = CREATEOBJECT("MyCOMServer.ProgId")
loComEventHandler = CREATEOBJECT("ComEventHandler")
IF NOT EVENTHANDLER(loComInstance, loComEventHandler)
ERROR 1429, "Unable to subscribe to MyCOMServer.ProgId events"
ENDIF
DO WHILE .T.
READ EVENTS
ENDDO
DEFINE CLASS ComEventHandler AS Session OLEPUBLIC
IMPLEMENTS IMyCOMEvents IN MyCOMServer.tlb
PROCEDURE IMyCOMEvents_RetrieveProcesses() AS "MyCOMServer.ProcessListProgId"
LOCAL loList
loList = CREATEOBJECT("MyCOMServer.ProcessListProgId")
* … Populate list
RETURN loList
ENDPROC
ENDDEFINE