none
Asynchronous Pluggable Protocols - how to delegate to default http/s handler

    Frage

  • Hi,

    I am trying to implement an Asynchronous Pluggable Protocols (APP), and wants to delegate the calls to the original handler (after some logging).

    I implemented the IInternetProtocol/Root and registered the APP with IInternetSession::RegisterNameSpace. The urlmon indeed calls my Start() function, but I can't find the way to call the original Start(). How can I do it?

    I am familiar with APP SDK by Igor Tandetnik, but I wish to implement it by myself.

    Thanks
    Dienstag, 17. April 2012 14:10

Antworten

  • yehuda s wrote:

    I implemented it on my APP object. In the code above (the one with the  colors) it is the CComPtr<IMyIInternetProtocolImple>
    myInProt object. What do you mean "by the client", who is the client  in my scenario?

    The browser, or rather, UrlMon on the browser's behalf. It creates the  APP, then calls its Start method passing IInternetBindInfo* as a  parameter.

    In order to intercept IInternetBindInfo calls, you need a separate  object that implements and forwards IInternetBindInfo the same way your  APP forwards IInternetProtocol et al. Then in your implementation of  Start, you create that object, and pass its IInternetBindInfo pointer to  the original APP's Start. From that point, the original APP will call  methods on that object, which will turn around and forward them to the  original client.

    Same with IInternetProtocolSink, if you need to intercept that, too. In  my Passthough APP, I have the same object implement both  IInternetProtocolSink and IInternetBindInfo (just for simplicity; they  don't have to be bundled together).


    Igor Tandetnik

    • Als Antwort markiert yehuda s Sonntag, 6. Mai 2012 07:13
    Sonntag, 29. April 2012 14:41
  • On 4/18/2012 10:04 AM, yehuda s wrote:

    Thanks for the answer, it helped me understand the flow.

    In my implementation of IClassFactory::CreateInstance I am obtaining the original class factory and passes it to my protocol instance by SetOriginalInternetProtocol() :

    [code]
    HRESULT STDMETHODCALLTYPE CMyIClassFactory::CreateInstance( IUnknown *pUnkOuter, REFIID riid, void **ppvObject )
    {

    if (pUnkOuter != NULL)
    {
        return CLASS_E_NOAGGREGATION ;
    }

    I don't recommend that. Your APP should support aggregation. I did the same thing originally, and observed very strange behavior from UrlMon, which went away when I enabled aggregation (don't remember the specific details though - it's been a while). UrlMon appears to assume that the APP is aggregatable.

    CComPtr<IClassFactory>  spTargetCF;
    HRESULT hr = CoGetClassObject(CLSID_HttpSProtocol, CLSCTX_ALL, 0, IID_IClassFactory,
            reinterpret_cast<void**>(&spTargetCF));

    CComPtr<IUnknown>  protocolRoot;
    hr = spTargetCF->CreateInstance(0, IID_IInternetProtocolRoot, reinterpret_cast<void**>(&protocolRoot));

    Yes, that's roughly the same thing I'm doing. The only difference is that I'm caching the original class factory's pointer, rather than getting a new one each time.

    and in Start (and all other functions) I am delegating:

    What other functions? There are several interfaces you need to implement and delegate. At least IInternetProtocolRoot, IInternetProtocol, IWinInetInfo and IWinInetHttpInfo.


    Igor Tandetnik

    • Als Antwort markiert yehuda s Sonntag, 6. Mai 2012 07:13
    Mittwoch, 18. April 2012 15:21

Alle Antworten

  • yehuda s wrote:

    I implemented the IInternetProtocol/Root and registered the APP with  IInternetSession::RegisterNameSpace. The urlmon indeed calls
    my Start() function, but I can't find the way to call the original  Start(). How can I do it?

    If you don't need to look at any other method calls beyond Start, return  INET_E_USE_DEFAULT_PROTOCOLHANDLER. UrlMon will switch to the default  handler, and yours won't be called anymore (for this request).

    I am familiar with APP SDK by Igor Tandetnik, but I wish to implement  it by myself.

    Well, you could study mine, then implement a similar approach in yours.


    Igor Tandetnik

    Dienstag, 17. April 2012 17:13
  • Hi Igor, thanks for your answer.

    I do wish to look at ALL the calls to the methods after Start, and I need to manipulate things in part of the methods, but I also need the ability to call the original implementation in some cases.

    I tried to figure out how to call the original method from your code, and I understand that it is done through the m_spInternetProtocol member, that is initialized by IInternetProtocolImpl::SetTargetUnknown() with the punkTarget that is called in CComClassFactoryProtocol::CreateInstance with ComPtr<IUnknown> spUnkTarget that is initailized in CreateInstanceTarget()  with

    spTargetCF->CreateInstance(0, IID_IInternetProtocolRoot, reinterpret_cast<void**>(ppTargetProtocol)).

    spTargetCF is initialized in GetTargetClassFactory by using m_spTargetCF.CopyTo to init it,  BUT I fail to understand how the m_spTargetCF is initialized. I know it is in CComClassFactoryProtocol::SetTargetClassFactory that is called by CComClassFactoryProtocol::SetTargetCLSID that is called by 

    CMetaFactory<Factory, Protocol, FactoryComObject>::CreateInstance, but I don't understand who is calling this function, and I don't understand the flow: When this factory is called? Is this flow of initializing the  m_spInternetProtocol and creating the  m_spTargetCF is happening on every URL, or once in the process? I would be happy if you can guide me on how to achieve the ability to call the original methods. 

    Thanks,

    Yehuda


    Mittwoch, 18. April 2012 07:10
  • yehuda s wrote:

    I do wish to look at ALL the calls to the methods after Start, and I  need to manipulate things in part of the methods, but I also
    need the ability to call the original implementation in some cases.

    Then you need to do the same thing my Passthrough APP does.

    I tried to figure out how to call the original method from your code,  and I understand that it is done through the
    m_spInternetProtocol member, that is initialized by  IInternetProtocolImpl::SetTargetUnknown() with the punkTarget that is  called
    in CComClassFactoryProtocol::CreateInstance with ComPtr<IUnknown>  spUnkTarget that is initailized in CreateInstanceTarget() with 

    spTargetCF->CreateInstance(0,  IID_IInternetProtocolRoot,reinterpret_cast<void**>(ppTargetProtocol)).

    At the end of the day, that's just calling CoCreateInstance with  CLSID_HttpProtocol or CLSID_HttpSProtocol. Or, to be precise,  CoGetClassObject to obtain the original class factory, followed by  CreateInstance on said class factory.

    spTargetCF is initialized in GetTargetClassFactory by using  m_spTargetCF.CopyTo to init it, BUT I fail to understand how the
    m_spTargetCF is initialized. I know it is in  CComClassFactoryProtocol::SetTargetClassFactory that is called by
    CComClassFactoryProtocol::SetTargetCLSID that is called by 

    CMetaFactory<Factory, Protocol, FactoryComObject>::CreateInstance, but  I don't understand who is calling this function

    The user of the toolkit does. In my sample application, this call is in  CMainDlg::OnInitDialog. CMetaFactory::CreateInstance manufactures a  class factory (hence "meta" - it's a factory of factories) suitable for  passing to IInternetSession::RegisterNamespace.

    Is this flow of initializing the m_spInternetProtocol and creating the
    m_spTargetCF is happening on every URL, or once in the process?

    m_spTargetCF is initialized at the point where a custom class factory  (implemented in CComClassFactoryProtocol) is created (via  CMetaFactory::CreateInstance) and registered with IInternetSession -  usually once per process per protocol (e.g. HTTP or HTTPS). Then, for  every request, UrlMon calls CreateInstance on CComClassFactoryProtocol.  This, in turn, creates an instance of the custom APP (via the normal ATL  machinery, as implemented by CComClassFactory), an instance of the  original handler (via m_spTargetCF->CreateInstance), and plugs the  latter into the former (via the custom APP's implementation of  IPassthroughObject::SetTargetUnknown).


    Igor Tandetnik

    Mittwoch, 18. April 2012 13:09
  • Hi Igor,

    Thanks for the answer, it helped me understand the flow.

    In my implementation of IClassFactory::CreateInstance I am obtaining the original class factory and passes it to my protocol instance by SetOriginalInternetProtocol() :

    HRESULT STDMETHODCALLTYPE CMyIClassFactory::CreateInstance( IUnknown *pUnkOuter, REFIID riid, void **ppvObject )
    {
    	
    if (pUnkOuter != NULL)
    {
    	return CLASS_E_NOAGGREGATION ;
    }
    CComPtr<IClassFactory> spTargetCF;
    HRESULT hr = CoGetClassObject(CLSID_HttpSProtocol, CLSCTX_ALL, 0, IID_IClassFactory,
    		reinterpret_cast<void**>(&spTargetCF));
    
    CComPtr<IUnknown> protocolRoot;
    hr = spTargetCF->CreateInstance(0, IID_IInternetProtocolRoot, reinterpret_cast<void**>(&protocolRoot));
    
    CComPtr<IMyIInternetProtocolImple> myInProt;
    if(FAILED(myInProt.CoCreateInstance(CLSID_MyIInternetProtocolImple)))
    {
    	return E_OUTOFMEMORY;
    }
    
    myInProt->SetOriginalInternetProtocol(protocolRoot);
    return myInProt->QueryInterface(riid, ppvObject);
    	
    }

    Inside SetOriginalInternetProtocol I am saving the pointer to CComPtr<IInternetProtocol> member:

    STDMETHODIMP CMyIInternetProtocolImple::SetOriginalInternetProtocol(IUnknown* orig)
    {
    HRESULT hr = orig->QueryInterface(&m_spInternetProtocol);
    return hr;
    }

    and in Start (and all other functions) I am delegating:

    HRESULT hr = m_spInternetProtocol->Start(szUrl, pOIProtSink, pOIBindInfo, grfPI, dwReserved);
    return hr;

    But now I am failing to browse, I am seeing the page of 'connection problem'. I do see calls to Start and Terminate all the time, and also to Read somtimes.

    Is there a mistake in my implementation? Why I am failing to browse?

    Thanks


    Mittwoch, 18. April 2012 14:04
  • On 4/18/2012 10:04 AM, yehuda s wrote:

    Thanks for the answer, it helped me understand the flow.

    In my implementation of IClassFactory::CreateInstance I am obtaining the original class factory and passes it to my protocol instance by SetOriginalInternetProtocol() :

    [code]
    HRESULT STDMETHODCALLTYPE CMyIClassFactory::CreateInstance( IUnknown *pUnkOuter, REFIID riid, void **ppvObject )
    {

    if (pUnkOuter != NULL)
    {
        return CLASS_E_NOAGGREGATION ;
    }

    I don't recommend that. Your APP should support aggregation. I did the same thing originally, and observed very strange behavior from UrlMon, which went away when I enabled aggregation (don't remember the specific details though - it's been a while). UrlMon appears to assume that the APP is aggregatable.

    CComPtr<IClassFactory>  spTargetCF;
    HRESULT hr = CoGetClassObject(CLSID_HttpSProtocol, CLSCTX_ALL, 0, IID_IClassFactory,
            reinterpret_cast<void**>(&spTargetCF));

    CComPtr<IUnknown>  protocolRoot;
    hr = spTargetCF->CreateInstance(0, IID_IInternetProtocolRoot, reinterpret_cast<void**>(&protocolRoot));

    Yes, that's roughly the same thing I'm doing. The only difference is that I'm caching the original class factory's pointer, rather than getting a new one each time.

    and in Start (and all other functions) I am delegating:

    What other functions? There are several interfaces you need to implement and delegate. At least IInternetProtocolRoot, IInternetProtocol, IWinInetInfo and IWinInetHttpInfo.


    Igor Tandetnik

    • Als Antwort markiert yehuda s Sonntag, 6. Mai 2012 07:13
    Mittwoch, 18. April 2012 15:21
  • Hi Igor,

    "all other functions" - I meant the functions of the interfaces you mentioned. But I implemented only IInternetProtocolRoot and IInternetProtocol, I didn't understand from the msdn that I should also implement the other two, is it a must? I will implement them too, and add support for aggregation, and will update.

    Thanks for your help

    Mittwoch, 18. April 2012 19:03
  •  Can you suggest why these calls might fail sometimes when viewing a web page in IE9 that has HTML5 video?  For example I'll get the errors show below if I visit http://www.beautyoftheweb.com/Experience

    So far I've only seen this on web pages that have HTML5 video, and it works fine in IE8.

    My factory's CreateInstance method get's called multiple times as the page loads, but three of those calls fail to create the default http handler. The video on that page fails to run so long as my add-on is enabled.

    I've tried using CoCreateInstance directly, and also using separate CoGetClassObject and CreateInstance calls:

      CComPtr<IUnknown> pUnkDefHandler;
        hr = CoCreateInstance (CLSID_HttpProtocol, NULL, CLSCTX_ALL, IID_IInternetProtocolRoot, reinterpret_cast<void**>(&pUnkDefHandler));
        if (S_OK != hr || !pUnkDefHandler)
        {

            // at this point hr = E_NOINTERFACE

            CComPtr<IClassFactory>  spTargetCF;
            hr = CoGetClassObject(m_guidProtocolType, CLSCTX_ALL, 0, IID_IClassFactory, reinterpret_cast<void**>(&spTargetCF));

            CComPtr<IUnknown>  pUnkDefHandler;
            hr = spTargetCF->CreateInstance(0, IID_IInternetProtocolRoot, reinterpret_cast<void**>(&pUnkDefHandler));
            // here hr = Interface not registered}


    • Bearbeitet LuthResearch Mittwoch, 18. April 2012 22:28 typo in the code
    Mittwoch, 18. April 2012 20:04
  • On 4/18/2012 4:04 PM, LuthResearch wrote:

    My factory's CreateInstance method get's called multiple times as the page loads, but three of those calls fail to create the default http handler.

    How do you create the default handler? What error do you get?

    UrlMon may sometimes call the object's methods on workers threads that have never initialized COM (yes, I know, that's against the rules; I'm just the messenger). Things like CoCreateInstance or CoGetClassObject won't work on such a thread. That is the main reason why I cache the default APP's IClassFactory pointer - its CreateInstance method still works from "bad" threads.


    Igor Tandetnik

    Mittwoch, 18. April 2012 20:52
  • Thanks. Caching does seem to solve that problem.

    The code I posted above is how I'm getting the default http (or https) handler.


    Mittwoch, 18. April 2012 21:21
  • Hi Igor,

    I implemented the InternetProtocolInfo, but I am failing to get the default handler of it. I tried using the same technique I used for the InternetProtocolRoot but I am getting E_NOINTERFACE:

    ... The code above ...

    CComPtr<IUnknown> protocolInfo;
    hr = spTargetCF->CreateInstance(0, IID_IInternetProtocolInfo, reinterpret_cast<void**>(&protocolInfo));

    and after passing the protocolInfo the my protocol:

    STDMETHODIMP CMyInetProtImpl::SetOrigProtInfo(IUnknown* orig)
    {
    HRESULT hr = orig->QueryInterface(&m_spInternetProtocolInfo);
    return hr; //here it is E_NOINTERFACE
    }

    I saw that you used a macro of COM_INTERFACE_ENTRY_PASSTHROUGH(IInternetProtocolInfo, m_spInternetProtocolInfo.p) but I don't understand the macro.

    Why my code is failing? Doesn't the spTargetCF implements IID_IInternetProtocolInfo? Can you explain me the macro please?

    Thank you

    Sonntag, 22. April 2012 13:54
  • yehuda s wrote:

    I implemented the InternetProtocolInfo, but I am failing to get the  default handler of it. I tried using the same technique I
    used for the InternetProtocolRoot but I am getting E_NOINTERFACE:

    CComPtr<IUnknown> protocolInfo;
    hr = spTargetCF->CreateInstance(0, IID_IInternetProtocolInfo,  reinterpret_cast<void**>(&protocolInfo));

    You don't normally create a separate object for IInternetProtocolInfo.  It's implemented on the same APP you have already created, alongside  IInternetProtocolRoot et al. You just QueryInterface for it.

    I saw that you used a macro of  COM_INTERFACE_ENTRY_PASSTHROUGH(IInternetProtocolInfo,m_spInternetProtoco lInfo.p) but I don't
    understand the macro.

    This is what it does. When my object is queried for some interface for  the first time, I query the original APP for the same interface. If this  succeeds, I cache the resulting pointer and report success to the  client. If the query on original APP fails, I report that failure back  to the client.

    Why my code is failing? Doesn't the spTargetCF implements  IID_IInternetProtocolInfo?

    spTargetCF is a class factory, it implements IClassFactory. Its  CreateInstance method creates a new COM object (the original APP), which  in turn implements a number of interfaces, among then IInternetProtocol,  IInternetProtocolRoot and IInternetProtocolInfo.


    Igor Tandetnik

    Sonntag, 22. April 2012 15:05
  • Hi Igor,

    I managed to delegate all the calls to the different interfaces I am implementing to the default handlers, and it is working well. But I have a problem with the IInternetBindInfo. I implemented it, but I see no calls to it's functions, particularly GetBindInfo. Do you know why? Should I do anything else besides implementing it?

    Thanks

    Sonntag, 29. April 2012 07:47
  • yehuda s wrote:

    I managed to delegate all the calls to the different interfaces I am  implementing to the default handlers, and it is working
    well. But I have a problem with the IInternetBindInfo. I implemented  it, but I see no calls to it's functions, particularly
    GetBindInfo.

    Which object have you implemented it on? It's impemented by the client,  not by the APP.


    Igor Tandetnik

    Sonntag, 29. April 2012 14:17
  • I implemented it on my APP object. In the code above (the one with the colors) it is the CComPtr<IMyIInternetProtocolImplemyInProt object. What do you mean "by the client", who is the client in my scenario? 

    Thanks

    Sonntag, 29. April 2012 14:29
  • yehuda s wrote:

    I implemented it on my APP object. In the code above (the one with the  colors) it is the CComPtr<IMyIInternetProtocolImple>
    myInProt object. What do you mean "by the client", who is the client  in my scenario?

    The browser, or rather, UrlMon on the browser's behalf. It creates the  APP, then calls its Start method passing IInternetBindInfo* as a  parameter.

    In order to intercept IInternetBindInfo calls, you need a separate  object that implements and forwards IInternetBindInfo the same way your  APP forwards IInternetProtocol et al. Then in your implementation of  Start, you create that object, and pass its IInternetBindInfo pointer to  the original APP's Start. From that point, the original APP will call  methods on that object, which will turn around and forward them to the  original client.

    Same with IInternetProtocolSink, if you need to intercept that, too. In  my Passthough APP, I have the same object implement both  IInternetProtocolSink and IInternetBindInfo (just for simplicity; they  don't have to be bundled together).


    Igor Tandetnik

    • Als Antwort markiert yehuda s Sonntag, 6. Mai 2012 07:13
    Sonntag, 29. April 2012 14:41
  • Mmmm, now I understand!! Thank you very much.

    By the way, after my basic implementation, I tried to check the issue of memory leaks when running the same flow over and over again while disabling cache and only delegating to the default handler (we talked about this in the past - Memory leak as a result of Async Plugable Protocol not releasing instances) and found that there is a leak. I think that this proves that Microsoft has a bug in urlmon and APP connectivity, at least when replacing the http\s protocol. 

    Thanks

    Sonntag, 29. April 2012 14:54