TL;DR: событие COM, которое работает в большинстве случаев, вызывает System.Reflection.TargetException
, когда
- COM-сервер развертывается с использованием бесплатной регистрации SxS.
- И событие вызывается узлом службы WCF.
Мы используем сложную архитектуру для модернизации набора корпоративных приложений, которым более 30 лет. Мы уже много лет используем WCF для запуска событий COM в нескольких наших приложениях. Это позволяет нам получать доступ к данным и бизнес-процессам в наших устаревших приложениях и предоставлять эти данные любому современному приложению, которое использует клиент WCF. Мы достигаем этого путем создания COM-серверов, предоставляющих приложениям события для подписки, и единого COM-метода, который при вызове создает узел службы WCF, запускающий эти события при получении запросов сообщений. Недавно мы обновили одно из наших устаревших приложений — старое настольное приложение для Windows, которое обеспечивает основной пользовательский интерфейс для ядра нашей системы, а также обеспечивает основную часть бизнес-операций для нашей системы. Его можно запустить в автоматическом режиме, что позволяет использовать его аналогично примитивному сервису NT. Он может выполнять фоновые операции, а также отправлять ему служебные запросы. Недавно мы создали COM-сервер .NET, который может предоставлять некоторые современные окна WPF для этого устаревшего приложения. Это наша первая попытка открыть окна WPF из нашего устаревшего приложения на основе GDI. Я упоминаю об этом, потому что COM-сервер может вызывать события при запуске из компонентов WPF, поставляемых сервером. В качестве альтернативы этот же сервер COM может поддерживать узел службы WCF, который вызывает те же события COM, что и пользовательский интерфейс WPF, для поддержки режима без вывода сообщений устаревшего приложения, когда приложение работает без пользовательского интерфейса. Все это работает и без проблем развернуто в продакшене.
Однако недавно я начал изучать COM без регистрации для этого конкретного COM-сервера. В какой-то степени я был на удивление успешным. Когда пользовательский интерфейс запущен, все компоненты WPF работают нормально, и эти события COM успешно обрабатываются устаревшим приложением. Когда приложение работает в автоматическом режиме, клиентское приложение успешно создает и настраивает сервер COM, а также успешно привязывается к событиям COM. COM-сервер успешно инициализирует узел службы WCF. Но когда узел получает сообщение и пытается вызвать одно из этих событий, это исключение выдается до того, как событие будет вызвано. Клиент никогда не уведомляется о событии. Просто чтобы уточнить, конкретное событие, на котором я здесь сосредоточиваюсь, успешно вызывается и обрабатывается, когда его запускают компоненты WPF COM-сервера, но не когда его запускает узел службы WCF COM.
Объект не соответствует типу цели.
(System.Reflection.TargetException)Источник: mscorlib Целевой сайт: InvokeDispMethod Тип объявления: System.RuntimeType
Трассировки стека: ----------------------------------------------- ---- at System.RuntimeType.InvokeDispMethod (имя строки, BindingFlags invokeAttr, Object target, Object[] args, Boolean[] byrefModifiers, культура Int32, String[] namedParameters) at System.RuntimeType.InvokeMember (имя строки, BindingFlags bindingFlags , Связыватель связывателя, Цель объекта, Object[] createdArgs, модификаторы ParameterModifier[], культура CultureInfo, String[] namedParams) в System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Int32[] aWrapperTypes, MessageData& msgData) в MyApp.IMyCOMServer.RetrieveProcesses()
в MyApp.MyCOMServer.OnRetrieveProcesses() в MyApp.MyService.d__7.MoveNext()
Одним из важных различий между компонентами WPF и компонентами WCF является многопоточность. Устаревшее приложение представляет собой однопотоковое апартаментное приложение, поэтому все события WPF передаются и обрабатываются потоком пользовательского интерфейса приложения. Здесь я ожидал бы провала, потому что это была непростая задача, и это очень новая архитектура для нас. Но когда приложение работает без пользовательского интерфейса, служба WCF обрабатывает каждое сообщение в новом потоке. Устаревшее приложение написано на языке Visual FoxPro, который поддерживает серверы автоматизации COM и решает за нас весь беспорядок, связанный с событиями COM и проблемами потоковой передачи. Мы широко используем события COM в наших приложениях VFP уже более 20 лет и имеем огромный опыт в этой области, поэтому я ожидаю, что именно здесь возникнут наименьшие проблемы. Потоки никогда раньше не были проблемой для хостов наших сервисов WCF, и вот я здесь. Это важная часть интерфейса событий COM.
#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();
}
Вот соответствующая часть интерфейса COM-сервера. COM-клиент создает экземпляр COM-сервера, затем подписывается на события, предоставляемые IMyCOMEvents, определенными выше, и, наконец, вызывает StartWcfService, определенный ниже. Затем клиент бездействует, ожидая событий для обработки.
[ComVisible(true)]
[Guid(ComDefinitions.InterfaceId)]
public interface IMyCOMServer
{
[Description("Initializes and starts the WCF service host ")]
void StartWcfService();
}
Вот соответствующая часть COM-сервера. Когда возникает исключение, событие RetrieveProcesses не является нулевым — кажется, что COM-клиент успешно подписался. ProcessList — это POCO-подобный COM-сервер, который, как ожидается, будет создан и возвращен COM-клиентом, хотя я также пытался реорганизовать определение события, чтобы клиент возвращал System.String
, но выбрасывалось то же исключение. ProcessList также поддерживает сериализацию с использованием System.Runtime.Serialization.DataContractSerializer
, что имеет отношение к службе WCF, когда я доберусь до этого ниже.
[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";
}
Вот соответствующая часть экземпляра службы WCF. Служба возвращает задачу в первую очередь для поддержки наших клиентов WCF, которые полностью асинхронны.
[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);
}
}
}
Вот соответствующий код COM-клиента, написанный в 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