c#异步可插协议包装默认HTTP协议抛出InvalidCastException
本文关键字:协议 HTTP InvalidCastException 默认 包装 异步 | 更新日期: 2023-09-27 18:12:46
为了能够过滤使用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计数,我已经排除了过早处置对象的可能性(无论如何都没有意义)。
我已经尝试了几种方法:
- 谷歌,但应用程序相当罕见
- http://msdn.microsoft.com/en-us/library/aa767916 (v = vs.85) . aspx
- 继承ComImport'ed对象-然后我的实现被忽略
- 查看refcounts
- 所有类型的类型转换
- 检查guid和接口是否有错误
- 询问同事
基本上,我想要实现的是包装IE的默认http协议实现来过滤掉url,包括那些从哪里检索资源。我也会对合适的替代方案感到满意,但它们必须符合GPLv2,可与浏览器应用程序一起部署,并且对系统的其余部分不做任何更改(即没有代理)。
谢谢你的帮助;)
顺便说一下,这将是我硕士论文的一部分:http://desktopgap.codeplex.com
目前我正在调查相同的行为。
我有两个想法。首先,对象是从创建它的另一个线程调用的: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调用函数。但是在托管代码的情况下,它首先查询接口。如果没有正确实现,额外的查询接口调用可能会失败或返回错误的对象。解决方案将是创建本机iunknown包装器,它将在QueryInterface上返回指向IInternetProtocol的指针。
经过一天的COM实验和阅读各种各样的东西在互联网上我找到了解决方案。
正如Simon Mourier指出的,这是一个线程问题:我创建的FilteredHttpProtocol
支持COM (= c#默认行为)的STA和MTA,默认的Http协议对象不支持,并且在从不同线程调用方法时(显然发生了),封送失败(这里也指出了)抛出E_NOINTERFACE。
由于改变COM线程行为显然有点奇怪(它需要写入注册表)并且不适合我,解决方案是在类工厂中创建一个自定义STA线程(即相同的线程传递到每个FilteredHttpProtocol对象)(而不是协议类本身)并使用Invoke()
调用每个方法。
一个简单的解决方法是:
[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;
}
// ...
}
(请不要使用此代码;))谢谢你的帮助,我希望这也能帮助到别人。
欢呼,
老人