0

TL;DR:在大多数情况下都有效的 COM 事件,抛出一个System.Reflection.TargetExceptionwhen

  1. 使用免注册 SxS 部署 COM 服务器
  2. 该事件由 WCF 服务主机引发

我们正在使用复杂的架构来现代化一套已有 30 多年历史的企业应用程序。多年来,我们一直在使用 WCF 在我们的几个应用程序中触发 COM 事件。这使我们能够访问旧应用程序中的数据和业务流程,并将该数据公开给任何使用 WCF 客户端的现代应用程序。我们通过构建公开事件供应用订阅的 COM 服务器和一个 COM 方法来实现这一点,该方法在调用时会创建一个 WCF 服务主机,该主机会在收到消息请求时触发这些事件。我们最近更新了我们的一个遗留应用程序——一个旧的 Windows 桌面应用程序,它为我们的系统核心提供主要用户界面,并为我们的系统提供大部分业务操作。它可以在“无声”中运行 模式允许它类似于原始 NT 服务使用。它可以执行后台操作以及发送给它的服务请求。我们最近构建了一个 .NET COM 服务器,它可以为这个遗留应用程序提供一些现代 WPF 窗口。这是我们第一次尝试从基于 GDI 的旧应用程序中公开 WPF 窗口。我提到这一点是因为 COM 服务器可以在从服务器提供的 WPF 组件触发时引发事件。同样的 COM 服务器也可以维护一个 WCF 服务主机,它会引发与 WPF UI 相同的 COM 事件,以支持旧版应用程序的“静默”模式 - 当应用程序在没有 UI 的情况下运行时。所有这些都有效,并且已经毫无问题地部署到生产环境中。我们最近构建了一个 .NET COM 服务器,它可以为这个遗留应用程序提供一些现代 WPF 窗口。这是我们第一次尝试从基于 GDI 的旧应用程序中公开 WPF 窗口。我提到这一点是因为 COM 服务器可以在从服务器提供的 WPF 组件触发时引发事件。同样的 COM 服务器也可以维护一个 WCF 服务主机,它会引发与 WPF UI 相同的 COM 事件,以支持旧版应用程序的“静默”模式 - 当应用程序在没有 UI 的情况下运行时。所有这些都有效,并且已经毫无问题地部署到生产环境中。我们最近构建了一个 .NET COM 服务器,它可以为这个遗留应用程序提供一些现代 WPF 窗口。这是我们第一次尝试从基于 GDI 的旧应用程序中公开 WPF 窗口。我提到这一点是因为 COM 服务器可以在从服务器提供的 WPF 组件触发时引发事件。同样的 COM 服务器也可以维护一个 WCF 服务主机,它会引发与 WPF UI 相同的 COM 事件,以支持旧版应用程序的“静默”模式 - 当应用程序在没有 UI 的情况下运行时。所有这些都有效,并且已经毫无问题地部署到生产环境中。同样的 COM 服务器也可以维护一个 WCF 服务主机,它会引发与 WPF UI 相同的 COM 事件,以支持旧版应用程序的“静默”模式 - 当应用程序在没有 UI 的情况下运行时。所有这些都有效,并且已经毫无问题地部署到生产环境中。同样的 COM 服务器也可以维护一个 WCF 服务主机,它会引发与 WPF UI 相同的 COM 事件,以支持旧版应用程序的“静默”模式 - 当应用程序在没有 UI 的情况下运行时。所有这些都有效,并且已经毫无问题地部署到生产环境中。

但是,我最近开始为这个特定的 COM 服务器探索免注册 COM。在某种程度上,我已经出人意料地成功了。当 UI 运行时,WPF 组件都可以正常工作,并且这些 COM 事件由旧版应用程序成功处理。当应用程序在静默模式下运行时,客户端应用程序成功实例化和配置 COM 服务器,并成功绑定到 COM 事件。COM 服务器成功初始化 WCF 服务主机。但是,当主机收到消息并尝试引发其中一个事件时,会在引发事件之前引发此异常。客户永远不会收到该事件的通知。澄清一下,我在这里关注的特定事件在 COM 服务器的 WPF 组件触发它时成功引发和处理,但不是在 COM'

对象与目标类型不匹配。
(System.Reflection.TargetException)

来源:mscorlib 目标站点:InvokeDispMethod 声明类型:System.RuntimeType

堆栈跟踪: - - - - - - - - - - - - - - - - - - - - - - - - ---- 在 System.RuntimeType.InvokeDispMethod(String name, BindingFlags invokeAttr, Object target, Object[] args, Boolean[] byrefModifiers, Int32culture, String[] namedParameters) 在 System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags , Binder binder, Object target, Object[] providedArgs, ParameterModifier[] 修饰符, CultureInfoculture, String[] namedParams) at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Int32[] aWrapperTypes, MessageData& msgData) at 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 服务主机来说从来都不是问题,但我来了。

#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 客户端实例化并返回它,尽管我也尝试重构事件定义以便客户端返回 a 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
4

0 回答 0