如何解决为什么 .NET COM 事件在引发时抛出“对象与目标类型不匹配”?
TL;DR:在大多数情况下工作的 COM 事件,在
时抛出System.Reflection.TargetException
- COM 服务器是使用免注册的 SxS 部署的
- 该事件由 WCF 服务主机引发
我们正在使用复杂的架构对一套已有 30 多年历史的企业应用程序进行现代化改造。多年来,我们一直在使用 WCF 在我们的几个应用程序中触发 COM 事件。这使我们能够访问旧应用程序中的数据和业务流程,并将这些数据公开给任何使用 WCF 客户端的现代应用程序。我们通过构建公开事件供应用程序订阅的 COM 服务器和单个 COM 方法来实现这一点,该方法在调用时创建一个 WCF 服务主机,该主机在收到消息请求时触发这些事件。我们最近更新了我们的一个遗留应用程序 - 一个旧的 Windows 桌面应用程序,它为我们的系统核心提供主要用户界面,并为我们的系统提供大量业务操作。它可以在“静默”模式下运行,这使得它可以像原始 NT 服务一样使用。它可以执行后台操作以及发送给它的服务请求。我们最近构建了一个 .NET COM 服务器,它可以为这个遗留应用程序提供一些现代 WPF 窗口。这是我们第一次尝试从基于 GDI 的旧应用程序中公开 WPF 窗口。我提到这一点是因为 COM 服务器可以在从服务器提供的 WPF 组件触发时引发事件。这个相同的 COM 服务器也可以维护一个 WCF 服务主机,它引发与 WPF UI 相同的 COM 事件,以支持旧应用程序的“静默”模式 - 当应用程序在没有 UI 的情况下运行时。所有这些都有效,并且已顺利部署到生产环境中。
但是,我最近开始探索针对这个特定 COM 服务器的免注册 COM。我出人意料地成功了。当 UI 运行时,WPF 组件都可以正常工作,并且这些 COM 事件由旧应用程序成功处理。当应用以静默模式运行时,客户端应用成功实例化并配置 COM 服务器,并成功绑定到 COM 事件。 COM 服务器成功初始化 WCF 服务主机。但是当主机收到消息并尝试引发这些事件之一时,会在引发事件之前抛出此异常。客户端永远不会收到该事件的通知。澄清一下,我在此关注的特定事件在 COM 服务器的 WPF 组件触发时成功引发和处理,但在 COM 的 WCF 服务主机触发时不会。
对象与目标类型不匹配。
(System.Reflection.TargetException)
来源:mscorlib 目标站点:InvokedispMethod 声明类型: System.RuntimeType
堆栈跟踪:
-------------------------------------------------- - 在 System.RuntimeType.InvokedispMethod(String name,BindingFlags
invokeAttr,对象目标,Object[] args,Boolean[] byrefModifiers,Int32 文化,String[] namedParameters) 在
System.RuntimeType.InvokeMember(字符串名称,BindingFlags
bindingFlags,Binder binder,Object target,Object[] providedArgs,ParameterModifier[] 修饰符、CultureInfo 文化、String[]
namedParams) 在 System.RuntimeType.ForwardCallToInvokeMember(String
memberName、BindingFlags 标志、对象目标、Int32[] aWrapperTypes、
MessageData& msgData) 在 MyApp.IMyCOMServer.RetrieveProcesses()
在 MyApp.MyCOMServer.OnRetrieveProcesses() 在
MyApp.MyService.d__7.MoveNext()
WPF 组件和 WCF 组件之间的一个重要区别是线程。旧版应用程序是单线程公寓应用程序,因此所有 WPF 事件都由应用程序的 UI 线程抽取和处理。这是我期望失败的地方,因为这不是一件容易完成的任务,而且对我们来说是非常新的架构。但是当应用程序在没有 UI 的情况下运行时,WCF 服务会在一个新线程上处理每条消息。遗留应用程序是用 Visual FoxPro 编写的,它支持 COM 自动化服务器,并为我们处理围绕 COM 事件和线程问题的所有混乱。 20 多年来,我们一直在我们的 VFP 应用程序中广泛使用 COM 事件,并且在这方面拥有丰富的经验,所以这是我认为问题最少的地方。对于我们的 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 服务实例的相关部分。该服务返回一个 Task 主要是为了支持我们的 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
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。