我想实现的效果是对于指定Web站点上的所有JPG图像在浏览器中显示的同时,也将图片的数据保存到硬盘上。
为了实现对图片数据的过滤,参考了凤之焚的《HTML代码过滤技术》一文,在此表示感谢!
http://www.cppblog.com/phenix-burn/archive/2006/08/29/11824.html   (文章)
http://download.csdn.net/source/158515   (对应的例子)整体的实现思路是这样子的:
1. 在Start函数中获得目标URL
2. 在ReportData函数中调用UrlMonProtocol的Read方法获得目标URL的数据,并存放入DataStream中
   同时将目标数据写入本地图片中
3. 在Read函数中将已经读取完的数据交给上层显示。基本流程和原来的例子一致,但在使用中碰到了几个问题,提出来恳请大家指点。1、 在MimeFilter.cpp文件的145行, 原本是
    HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    但在实际使用中发现,如果直接执行的话,程序无法启动,在VS的调试窗口内有以下信息输出:
    Warning: OleInitialize returned scode = RPC_E_CHANGED_MODE ($80010106).
    
    我把这条语句修改为 HRESULT hRes = CoInitialize(NULL);  就没有这个问题了,程序可以正常启动了
    不知道这样修改对不对?2、 通过实际的调试发现:对于一个指定的目标URL,会产生一个对应的MimeFilter过滤器实例对象。
    其中,过滤器实例的Start方法仅会被调用一次,但ReportData方法可能会在不同的进程中被并发调用。
    ReportData并发调用的情况并不是每次都会出现,如果监控的是文字的话,很少会出现,但如果监控的是图片的话,出现的概率就比较大。
    PS1: 凤之焚例子中的DataStream的初始化应该放在Start函数中执行,不然的话,在并发调用ReportData的情况下,可能会冲掉以前接收好的数据的。
    所以最好将以下二行代码放到Start函数中:
     DataStream = NULL;
CreateStreamOnHGlobal(0, true, &DataStream);
    PS2: 一旦出现ReportData并发调用的情况,就会出现死循环的情况。主要是由于UrlMonProtocol->Read在经过几次有数据的成功调用之后,之后的调用总是返回E_PENDING,具体请参考下面的LOG内容    测试代码如下:
do
{
memset(p,0,sizeof(p));
hr = UrlMonProtocol->Read(p, sizeof(p)-1, &Readtotal); if (Readtotal > 0){
DataStream->Write(p,Readtotal,&cbWritten);
TotalSize += Readtotal;
}
TRACE(TEXT("this=0x%08X ThreadID=%d  hr=0x%08X     Readtotal=%d  TotalSize=%d\r\n"), this, GetCurrentThreadId(), hr, Readtotal, TotalSize);
}while((hr != S_FALSE) && (hr != INET_E_DOWNLOAD_FAILURE) && (hr != INET_E_DATA_NOT_AVAILABLE));    LOG内容如下:
this=0x00377B18 ThreadID=3304   <== Start函数中的输出
this=0x00377B18 ThreadID=3304  hr=0x00000000     Readtotal=1023  TotalSize=1023 <== ReportData函数中的输出(进程ID:3304)
this=0x00377B18 ThreadID=3304  hr=0x00000000     Readtotal=122  TotalSize=1145 <== ReportData函数中的输出(进程ID:3304)
this=0x00377B18 ThreadID=3304  hr=0x8000000A     Readtotal=0  TotalSize=1145 <== ReportData函数中的输出(进程ID:3304)
this=0x00377B18 ThreadID=2228  hr=0x00000000     Readtotal=1023  TotalSize=2168 <== ReportData函数中的输出(进程ID:2228)
this=0x00377B18 ThreadID=2228  hr=0x00000000     Readtotal=1023  TotalSize=3191 <== ReportData函数中的输出(进程ID:2228)
this=0x00377B18 ThreadID=2228  hr=0x00000000     Readtotal=850  TotalSize=4041 <== ReportData函数中的输出(进程ID:2228)
this=0x00377B18 ThreadID=2228  hr=0x8000000A     Readtotal=0  TotalSize=4041 <== 后面调用UrlMonProtocol->Read的返回值都是E_PENDING(0x8000000A)
this=0x00377B18 ThreadID=3304  hr=0x8000000A     Readtotal=0  TotalSize=4041
this=0x00377B18 ThreadID=2228  hr=0x8000000A     Readtotal=0  TotalSize=4041
this=0x00377B18 ThreadID=3304  hr=0x8000000A     Readtotal=0  TotalSize=4041
this=0x00377B18 ThreadID=2228  hr=0x8000000A     Readtotal=0  TotalSize=4041
this=0x00377B18 ThreadID=3304  hr=0x8000000A     Readtotal=0  TotalSize=4041
...... 无限循环下去.....    这个问题搞了很久了,一直想不明白,求高人指点一下如何解决,谢谢啦。
    分数就剩40分了, 大家见谅,以后有分了再补。 ^-^!

解决方案 »

  1.   

    1、无需再调用CoInitialize或CoInitializeEx,因为线程已经被初始化为STA了。
    2、ReportData用法错误。一个文件的下载只会对应一个mimefilter,出现多个的原因是因为第一个filter没有正确向浏览器报告下载结果,导致浏览器以为它出错,从而重启新的filter来执行下载。
    如果对文件的下载是同步的,执行一次ReportData就够了,如果下载是异步的,应该分多次调用ReportData,调用时需要使用不同的标志,有三个标志一定要用到BSCF_FIRSTDATANOTIFICATION、BSCF_LASTDATANOTIFICATION、BSCF_DATAFULLYAVAILABLE,同步调用时这三个标志可以或到一起。
    在最后一次ReportData之后要调用ReportResult(S_OK, 0, NULL);
    同理,在Read方法读完最后的数据后也需要调用ReportResult(S_OK, 0, NULL)并返回S_FALSE。
      

  2.   

    十分感谢胡兄,不过我还是有点不明白 -_-!第一点没什么问题
    第二点:
    一个文件的下载只会对应一个mimefilter,这个没错,但是我这边并不是出现了多个filter,只是原来的那个filter的ReportData被并发调用。
    怀疑和对文件的下载是否同步有关系,但我不知道如何控制使的下载变成同步
      

  3.   

    下面是ReportData的具体实现,麻烦帮我看一下STDMETHODIMP CHTMLFilter::ReportData( DWORD grfBSCF, ULONG ulProgress, ULONG ulProgressMax)
    {
        USES_CONVERSION;
        //存储网页代码
        char p[1024];
        HRESULT hr;
        ULONG Readtotal;
        ULONG cbWritten=0;    do{
            memset(p,0,sizeof(p));
            hr = UrlMonProtocol->Read(p, sizeof(p)-1, &Readtotal);        if (Readtotal > 0){
                DataStream->Write(p,Readtotal,&cbWritten);
                TotalSize += Readtotal;
            }
            TRACE(TEXT("this=0x%08X ThreadID=%d  hr=0x%08X     Readtotal=%d  TotalSize=%d\r\n"), this, GetCurrentThreadId(), hr, Readtotal, TotalSize);    }while((hr != S_FALSE) && (hr != INET_E_DOWNLOAD_FAILURE) && (hr != INET_E_DATA_NOT_AVAILABLE));    if(hr == S_FALSE)
        {
            ULARGE_INTEGER Dummy;
            _LARGE_INTEGER zero;
            zero.QuadPart =0;
            DataStream->Seek ( zero, STREAM_SEEK_SET, &Dummy);

            UrlMonProtocolSink->ReportData(BSCF_FIRSTDATANOTIFICATION | BSCF_LASTDATANOTIFICATION | BSCF_DATAFULLYAVAILABLE, TotalSize, TotalSize);
            UrlMonProtocolSink->ReportResult(S_OK, S_OK, NULL);
        }
        else
        {
            Abort(hr, 0);
        }
        return S_OK;
    }
      

  4.   

    这个是Read的实现
    STDMETHODIMP CHTMLFilter::Read(void *pv, ULONG cb, ULONG *pcbRead)
    {
        DataStream->Read(pv, cb, pcbRead);
        Written+=*pcbRead;
        if (Written == TotalSize)
        {
            ReportResult(S_OK, 0, NULL);
            return S_FALSE;
        }
        else 
        {
            return S_OK;
        }
    }
      

  5.   

    我似乎回答过类似的问题。
    在Read实现中把 if (Written == TotalSize) 改成 if (Written >= TotalSize) 这个很重要。另外,CHTMLFilter::ReportData是被谁调用的?这个是你的自定义函数,应该是你自己调用的,为什么会被并发调用?
      

  6.   

    CHTMLFilter::ReportData实现的是IInternetProtocolSink接口的ReportData方法,
    他不是我的自定义函数,是被系统回调的,我好像没法控制的。下面是ReportData函数在被并发调用时分别对应的堆栈情况:
    堆栈1:
    > Mimefilter.exe!CHTMLFilter::ReportData(unsigned long grfBSCF=1, unsigned long ulProgress=2594, unsigned long ulProgressMax=98590)  Line 119 C++
      urlmon.dll!42cf83b7() 
      urlmon.dll!42d31db1() 
      urlmon.dll!42d0c844() 
      urlmon.dll!42d209b5() 
      ntdll.dll!7c96d886() 
      ntdll.dll!7c949d18() 
      ntdll.dll!7c91b686() 
      oleaut32.dll!771215f8() 
      oleaut32.dll!77121629() 
      ntdll.dll!7c949b34() 
      ntdll.dll!7c926a44() 
      ntdll.dll!7c926abe() 
      urlmon.dll!42cf192a() 
      urlmon.dll!42d20d52() 
      urlmon.dll!42d10eb8() 
      urlmon.dll!42d10e8e() 
      urlmon.dll!42d10cc2() 
      urlmon.dll!42d10c9f() 
      urlmon.dll!42d0f128() 
      Mimefilter.exe!CHTMLFilter::Continue(_tagPROTOCOLDATA * pProtocolData=0x0321fd34)  Line 40 + 0x29 C++
      urlmon.dll!42d317d2() 
      urlmon.dll!42d0f75f() 
      urlmon.dll!42d20d47() 
      urlmon.dll!42d20d26() 
      wininet.dll!42c1eb3d() 
      mswsock.dll!71a544b0() 
      ws2_32.dll!71ab93c2() 
      mswsock.dll!71a544b0() 
      ntdll.dll!7c91056d() 
      kernel32.dll!7c80995a() 
      kernel32.dll!7c80996d() 
      wininet.dll!42c4355d() 
      kernel32.dll!7c80996d() 
      wininet.dll!42c14507() 
      wininet.dll!42c20ba5() 
      wininet.dll!42c1eeb7() 
      wininet.dll!42c207c4() 
      shlwapi.dll!77f69548() 
      ntdll.dll!7c927545() 
      ntdll.dll!7c927583() 
      ntdll.dll!7c927645() 
      ntdll.dll!7c92761c() 
      kernel32.dll!7c80b683() 
      ntdll.dll!7c910760()  堆栈2:
    Mimefilter.exe!CHTMLFilter::ReportData(unsigned long grfBSCF=1, unsigned long ulProgress=1, unsigned long ulProgressMax=98590)  Line 119 C++
      urlmon.dll!42cf83b7() 
      urlmon.dll!42d31db1() 
      urlmon.dll!42d0c844() 
      urlmon.dll!42d209b5() 
      

  7.   

    无需实现IInternetProtocolSink这个接口,这个接口指针系统已经实现了,在Start方法里面会传给你的,你只需保存下来即可,需要的时候就调用它的ReportXXX方法
      

  8.   

    因为我是在IInternetProtocolSink::ReportData中实现目标数据过滤的, 
    如果我不实现这个方法的话,那么我应该在哪个函数中实现目标数据过滤功能?
      

  9.   

    是这样的,我希望实现的是对于指定类型的资源能够先经过我的MimeFilter,然后再由MimeFilter来交给上层。
    保存文件是针对图片而言的,目标数据过滤是针对文本数据而言的。但目前在ReportData这个函数处碰壁了,因为经常会碰到二个并发的ReportData从而造成死循环。刚才我试了一下,将利用UrlMonProtocol->Read循环读取网络上的数据这段代码放到Start函数中,结果发现Start函数并发了,昏过去了。
      

  10.   

    要实现过滤也无需实现IInternetProtocolSink。在Start方法里,对于任何URL请求,如果你想做额外的处理,就按照上面的做法自己下载获得数据;
    如果自己不想做特别处理,直接返回INET_E_USE_DEFAULT_PROTOCOLHANDLER;
    如果想禁止此URL下载,可以直接返回多种错误码,比如INET_E_DATA_NOT_AVAILABLE、INET_E_DOWNLOAD_FAILURE、INET_E_INVALID_URL等等都可以。
      

  11.   

    并发的问题解决了,以下代码:
    STDMETHODIMP CHTMLFilter::Continue(PROTOCOLDATA *pProtocolData)
    {
        UrlMonProtocol->Continue(pProtocolData);
        return S_OK;
    }
    修正如下:
    STDMETHODIMP CHTMLFilter::Continue(PROTOCOLDATA *pProtocolData)
    {
        //UrlMonProtocol->Continue(pProtocolData);
        return E_NOTIMPL;
    }
    ================================================================
    总算搞清楚ReportData为什么会并发了,当UrlMonProtocol->Read的返回值hr=E_PENDING的时候,
    UrlMon会调用扩展的Continue方法,如果在扩展的Continue方法中再调用UrlMonProtocol->Continue的话,就会产生并发问题了。就着一个小小的地方,搞了整整三天,郁闷死了,不过幸好最终还是解决了。
    凤之焚兄的例子中好像还有一些小BUG,只能慢慢解决啦。
    万分感谢胡柏华老兄的支持!!!
      

  12.   

    楼主  问你一个问题我下载msdn上  xmlmimefilter 的例子
    修改了一下mime对象为 image/gif
    发现只有当右键选图片 然后点属性的时候 才能截获
    这是为什么啊
      

  13.   

    xmlmimefilter 的例子,为什么在我这里会死掉?