微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么 .NET COM 事件在引发时抛出“对象与目标类型不匹配”?

如何解决为什么 .NET COM 事件在引发时抛出“对象与目标类型不匹配”?

TL;DR:在大多数情况下工作的 COM 事件,在

时抛出 System.Reflection.TargetException
  1. COM 服务器是使用免注册的 SxS 部署的
  2. 该事件由 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 举报,一经查实,本站将立刻删除。