2013-05-08 3 views
1

Для того, чтобы иметь возможность фильтровать URL-адреса, выбранные (включая JS, изображения и т. Д.) С помощью веб-браузера C# (WinForms), единственным доступным вариантом является асинхронный подключаемый Протокол обертывания HTTP (а затем и других). К сожалению, это не удается с InvalidCastException, вызванным первоначальной первоначальной реализацией протокола после нескольких звонков < - это тоже странная часть, кажется, она преуспела несколько раз перед сбоем.C# Asynchronous Pluggable Protocol wrapting default HTTP Protocol throws 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) 
    { 
    } 
} 

Это оригинальный протокол HTTP, используемый IE, при использовании его вместо обертки он отлично работает:

[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)). 

Видя, что я уже отдал объект сай d несколько раз (что должно приводить к вызову каждый раз и что он был вызван (проверен через контрольные точки и т. д.) несколько раз, прежде чем он провалится, эта ошибка действительно озадачивает. Если посмотреть на подсчеты ссылок, я уже исключал, что объект находится слишком рано (в любом случае это не имеет смысла).

я пытался несколько вещей:

  • Google, но АРР довольно редки
  • http://msdn.microsoft.com/en-us/library/aa767916(v=vs.85).aspx
  • Наследование объект ComImport'ed - тогда моя реализация игнорируется
  • Посмотрите на реф считает
  • Листы всех видов
  • Проверьте идентификаторы GUID и интерфейсы на наличие ошибок
  • Запрашиваемые коллеги

В основном то, что я пытаюсь достичь завернуть реализации протокола по умолчанию HTTP в IE, чтобы отфильтровать URL-адресов, в том числе и тех, где ресурсы извлекаются из. Я также был бы удовлетворен подходящими альтернативами, но они должны быть совместимы с GPLv2, развертываться с помощью приложения-браузера и не выполнять никаких изменений в остальной части системы (т. Е. Без прокси).

Спасибо за помощь;)

Кстати, это будет частью моей магистерской диссертации здесь: http://desktopgap.codeplex.com

+0

Почему у вас ClassInterfaceType None? Я бы убрал его. Кроме того, E_NOINTERFACE может быть проблемой с потоками. Когда ваш объект создан? На какой поток? Что это за тип квартиры? (http://blogs.msdn.com/b/oldnewthing/archive/2004/12/13/281910.aspx) –

+0

Привет! Спасибо за ваш ответ. Действительно, посмотрев на нити, используемые для вызова методов: '# 1 ThreadId: 3 C'tor() # 1 URL: \t HTTP: // ... # 1 исходный поток: 3 вызывающий поток 3 Start() STA # 2 threadID: 3 C'tor() # 2 URL: \t http: // ... # 2 оригинальная резьба: 3 вызывающая нить 3 Start() STA # 1 исходная резьба: 3 вызывающая нить 14 Continue() MTA # 2 исходная резьба: 3 вызывающая нить 13 Continue() MTA Первое исключение случайного типа ' System.InvalidCastException '... ' Однако эти потоки создаются тем, кто их называет, поэтому я не уверен, как я могу изменить квартиру ... – claus

+0

Создаете ли вы каждый экземпляр FilteredHttpProtocol в текущем потоке или вы поделиться экземпляром? –

ответ

0

После дня экспериментирования с COM и чтения всяческих вещей в Интернете я нашел решение.

Как Simon Mourier отметил, что это был вопрос нарезание резьбы: FilteredHttpProtocol Я создал поддерживает STA и MTA с COM (= C поведение # по умолчанию), который по умолчанию объект HTTP-протокол не и при вызове метода из другого потока (что, по-видимому, происходит), Marshaling терпит неудачу (также указал here), бросая 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; 
    } 
    // ... 
} 

(пожалуйста, не используйте этот код, как это;)) Спасибо за вашу помощь, Надеюсь, это тоже поможет кому-то еще.

Приветствия,

Клаус

0

В настоящее время я расследую такое же поведение.

У меня есть две идеи. Во-первых в том, что объект вызывается из другого потока он создан:

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-таблицу. Но в случае управляемого кода он сначала запрашивает интерфейс. И этот дополнительный вызов интерфейса запроса может потерпеть неудачу или вернуть неправильный объект, если он не был должным образом реализован. Решением будет создание собственной оболочки IUknown, которая вернет указатель на IInternetProtocol на QueryInterface.

+0

Насколько я понял, потоковая передача - то есть объект, запускаемый в MTA - является основной причиной. Первоначальный HTTPProtocol запускается в STA, поэтому оболочка должна быть в одном, но поведение по умолчанию для объекта ComVisible - это STA & MTA, поэтому вызов из другого потока в какой-то момент. – claus