【问题标题】:C# Asynchronous Pluggable Protocol wrapping default HTTP Protocol throws InvalidCastExceptionC# 异步可插入协议包装默认 HTTP 协议抛出 InvalidCastException
【发布时间】:2013-05-02 21:37:09
【问题描述】:

为了能够过滤使用 C# Web 浏览器 (WinForms) 获取的 URL(包括 JS、图像等),唯一可包含的选项似乎是包装 HTTP 的异步可插入协议(以及后来的其他协议) )。不幸的是,原始原始协议实现在多次调用后抛出InvalidCastException失败了

现在一些代码:

首先注册并附加协议的工厂:

  var ep = new FilteredHttpProtocolFactory();
  Guid id = Guid.Parse ("E00957BD-D0E1-4eb9-A025-7743FDC8B27B");
  session.RegisterNameSpace (ep, ref id, "http", 0, null, 0);

(工厂:)

[Guid ("EF474615-8079-4CFA-B114-6D1D28634DD8")]
[ComVisible (true)]
[ClassInterface (ClassInterfaceType.None)]
public class FilteredHttpProtocolFactory : IClassFactory
{
  public void CreateInstance (object pUnkOuter, Guid riid, out object ppvObject)
  {
    ppvObject = new FilteredHttpProtocol();
  }

  public void LockServer (bool fLock)
  {
  }
}

这是 IE 使用的原始 HTTP 协议,当使用它而不是包装器时,它工作得很好:

[ComImport]
[Guid ("79eac9e2-baf9-11ce-8c82-00aa004ba90b")]
public class OriginalHttpHandler
{
}

这是包装器本身:

[Guid ("E00957BD-D0E1-4eb9-A025-7743FDC8B27B")]
[ComVisible (true)]
[ClassInterface (ClassInterfaceType.None)]
[AsyncProtocol (Name = "http2", Description = "blah")]
public class FilteredHttpProtocol : IInternetProtocol, IInternetProtocolRoot
{ 
private readonly IInternetProtocol _wrapped;

public FilteredHttpProtocol ()
{
  var originalHttpHandler = new OriginalHttpHandler();
  _wrapped = (IInternetProtocol) originalHttpHandler;
}

public void Start (string szURL, IInternetProtocolSink Sink, IInternetBindInfo pOIBindInfo, uint grfPI, uint dwReserved)
{
  _wrapped.Start (szURL, Sink, pOIBindInfo, grfPI, dwReserved);
}

public void Continue (ref _tagPROTOCOLDATA pProtocolData)
{
  _wrapped.Continue (ref pProtocolData);      // <- FAILS HERE
}
   // .... other methods from IInternetProtocol
    public uint Read (IntPtr pv, uint cb, out uint pcbRead)
    {
      return _wrapped.Read (pv, cb, out pcbRead); // <- OR HERE
    }
}

所以,奇怪的是,构造函数被调用,Start() 被调用,甚至 Read()Continue() 被调用多次,直到整个事情失败(Read()Continue())当页面的某些部分已经可见时(!),但似乎大部分都缺少一个特定的图像(大部分!):

Unable to cast COM object of type 'Clients.Windows.Protocol.OriginalHttpHandler' 
to interface type 'Clients.Windows.Protocol.IInternetProtocol'. This operation 
failed because the QueryInterface call on the COM component for the interface 
with IID '{79EAC9E4-BAF9-11CE-8C82-00AA004BA90B}' failed due to the following 
error: No such interface supported (Exception from HRESULT: 0x80004002 E_NOINTERFACE)).

看到我已经多次将对象强制转换到所述接口(这应该导致每次调用QueryInterface(),并且在失败之前已经多次调用它(通过断点等进行验证)这个错误真的是令人费解。通过查看 ref 计数,我已经排除了对象被过早处理的可能性(无论如何都没有意义)。

我已经尝试了几件事:

基本上,我想要实现的是包装 IE 的默认 http 协议实现以过滤掉 URL,包括从中检索资源的 URL。我也会对合适的替代方案感到满意,但它们必须符合 GPLv2,可与浏览器应用程序一起部署,并且不对系统的其余部分进行任何更改(即无代理)。

感谢您的帮助;)

顺便说一句,这将成为我硕士论文的一部分:http://desktopgap.codeplex.com

【问题讨论】:

  • 为什么你有 ClassInterfaceType None?我会删除它。此外,E_NOINTERFACE 可能是线程问题。你的对象是什么时候创建的?在什么线程上?这是什么线程公寓类型? (blogs.msdn.com/b/oldnewthing/archive/2004/12/13/281910.aspx)
  • 嗨!感谢您的回复。确实在查看了用于调用方法的线程之后:#1 threadID: 3 C'tor() #1 URL: http://... #1 original thread: 3 calling thread 3 Start() STA #2 threadID: 3 C'tor() #2 URL: http://... #2 original thread: 3 calling thread 3 Start() STA #1 original thread: 3 calling thread 14 Continue() MTA #2 original thread: 3 calling thread 13 Continue() MTA A first chance exception of type 'System.InvalidCastException'... 但是这些线程是由调用它们的人创建的,这就是为什么我不确定如何更改公寓...
  • 是在当前线程上创建每个FilteredHttpProtocol实例还是共享实例?
  • ...此外,似乎在需要多个子请求时输入了 MTA,在我的情况下,需要加载 2 个图像(对象 #1 和 #2)——但仍然是每个子请求有自己的对象,并且它们的构造使用相同的线程。
  • 我的发现实际上与下面 user2371720 的answer 的发现相同。所以它们似乎也是在单个线程上单独创建的

标签: c# .net internet-explorer http com


【解决方案1】:

目前我正在调查同样的行为。

我有两个想法。首先是从另一个创建它的线程调用该对象:

Method, Apartment, ThreadId, ObjId
--------------------
Constructor: , STA, 9, #1
Start: , STA, 9, #1
Continue: , STA, 9, #1
Read: , STA, 9, #1
Read: , STA, 9, #1
Lock: , STA, 9, #1
Read: , STA, 9, #1
Read: , STA, 9, #1
Constructor: , STA, 10, #2
Start: , STA, 10, #2
Constructor: , STA, 10, #3   <-- Notice ThreadId is 10
Terminate: , STA, 9, #1
Start: , STA, 10, #3         <-- Valid call
Constructor: , STA, 10, #4
Start: , STA, 10, #4
Read: , STA, 10, #4
Continue: , MTA, 11, #2
Terminate: , STA, 10, #4
Continue: , MTA, 12, #3      <-- EXCEPTION HERE!!! We have new thread with Id of 12 and MTA apartment
Constructor: , STA, 10, #5
Start: , STA, 10, #5
Constructor: , STA, 10, #6
Unlock: , STA, 9, #1
Start: , STA, 10, #6

第二个想法是 CLR 处理 COM 调用的方式与原生 c++ 不同。我在尝试使用 DirectShow 时遇到过这种情况。因此,在 c++ 的情况下,它只需要指向接口的指针并通过 v-table 调用函数。但在托管代码的情况下,它首先查询接口。如果没有正确实施,那么额外的查询接口调用可能会失败或返回错误的对象。解决方案是创建本地 IUknown 包装器,该包装器将在 QueryInterface 上返回指向 IInternetProtocol 的指针。

【讨论】:

  • 据我现在发现,线程——即在 MTA 中运行的对象——是根本原因。原始 HTTPProtocol 在 STA 中运行,因此包装器也必须在一个中,但 ComVisible 对象的默认行为是 STA 和 MTA,因此在某些时候从不同的线程调用。
【解决方案2】:

经过一天的 COM 试验和互联网上的各种阅读后,我找到了解决方案。

正如Simon Mourier 指出的那样,这是一个线程问题:我创建的FilteredHttpProtocol 支持带有 COM(= C# 默认行为)的 STA 和 MTA,默认 Http 协议对象不支持,并且在从不同的线程(显然发生了),编组失败(也指出here)抛出 E_NOINTERFACE。

由于更改 COM 线程行为显然有点奇怪(它需要写入注册表)并且对我不起作用,因此解决方案是在类工厂中创建自定义 STA 线程(即同一个线程被传递到每个 FilteredHttpProtocol 对象)(而不是 Protocol 类本身)并使用 Invoke() 调用 every 方法。

一个快速而肮脏的解决方案是:

[ComVisible (true)]
public class FilteredHttpProtocolFactory : IClassFactory
{
    private readonly Control _ctrl;

    public FilteredHttpProtocolFactory ()
    {
      _ctrl = new Control();
    }

    public void CreateInstance (object pUnkOuter, Guid riid, out object ppvObject)
    {
      ppvObject = new FilteredHttpProtocol(_ctrl);
    }

    public void LockServer (bool fLock)
    {
    }
}

还有协议类本身:

[ComVisible (true)]
[AsyncProtocol (Name = "http2", Description = "blah")]
public class FilteredHttpProtocol :  IInternetProtocol, IInternetProtocolRoot
{
    private IInternetProtocol _wrapped;
    private static int s_id = 0;
    private int _id = -1;
    private int _creatingTID = -1;

    private Control _dispatcher;

    public FilteredHttpProtocol (Control ctrl)
    {
      _dispatcher = ctrl;
      _id = s_id;
      s_id++;
      _creatingTID = Thread.CurrentThread.ManagedThreadId;
      Debug.WriteLine ("#" + _id + " threadID: " + _creatingTID + " C'tor()");

      _dispatcher.Dispatcher.Invoke (
          () =>
          {
            var originalHttpHandler = new OriginalHttpHandler();
            _wrapped = (IInternetProtocol) originalHttpHandler;
          });
    }

    public void Start (string szURL, IInternetProtocolSink Sink, IInternetBindInfo pOIBindInfo, uint grfPI, uint dwReserved)
    {
      Debug.WriteLine ("#" + _id + " URL: " + "\t" + szURL);
      _dispatcher.Dispatcher.Invoke (
          () =>
          {
            Debug.WriteLine (
                "#" + _id + " original thread: " + _creatingTID + " calling thread " + Thread.CurrentThread.ManagedThreadId
                + " Start() "
                + Thread.CurrentThread.GetApartmentState());
            _wrapped.Start (szURL, Sink, pOIBindInfo, grfPI, dwReserved);
          });
    }

    public void Continue (ref _tagPROTOCOLDATA pProtocolData)
    {
      var _pProtocolData = pProtocolData;
      _dispatcher.Dispatcher.Invoke (
          () =>
          {
            Debug.WriteLine (
                "#" + _id + " original thread: " + _creatingTID + " calling thread " + Thread.CurrentThread.ManagedThreadId + " Continue() "
                + Thread.CurrentThread.GetApartmentState());
            _wrapped.Continue (ref _pProtocolData);
          });
      pProtocolData = _pProtocolData;
    }
    // ...
}

(请不要按原样使用此代码;)) 感谢您的帮助,我希望这对其他人也有帮助。

干杯,

克劳斯

【讨论】:

    猜你喜欢
    • 2011-01-02
    • 2020-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-31
    相关资源
    最近更新 更多