分享
 
 
 

CSDN助手源码剖析(二)--URL Moniker的封装

王朝other·作者佚名  2006-07-10
窄屏简体版  字體: |||超大  

一、目标

在这篇文章中,我们要通过对URL Moniker的封装,实现以下几个功能:

支持URL的“GET”和“POST”两种操作。

支持同步和异步调用。

二、约定

我们建立一个类CCuteMoniker,通过向外部提供一个Request方法,从而实现以上两个功能。

HRESULT CCuteMoniker::Request(LPCTSTR szMethod,LPCTSTR szURL,VARIANT_BOOL bAsync,

LPCTSTR szHeaders,LPCTSTR szPostData,

CuteHTTPResponseProc pProc,void* pParam1,CParam_Http_Base* pParam2,IStream** ppResponse,

DWORD nBindFlags)

参数说明:

szMethod:“GET”或“POST”

szURL:要访问的URL

bAsync:同步还是异步

szHeaders:Request的头部信息

szPostData:Request的POST提交数据

pProc:异步调用时的回调函数,当数据成功返回时,调用此函数

pParam1:与本次访问相关的参数1

pParam2:与本次访问相关的参数2

ppResponse:当同步调用时,返回数据流

nBindFlags:访问时指定的绑定标志,如可以指定BINDF_NOWRITECACHE,即“禁止将返回的数据写入缓存”。这对于“CSDN助手”的缓存优化很重要。相关信息请参见文章CSDN助手源码剖析(一)--缓存优化

特别说明:

参数pParam2类型为CParam_Http_Base* 。CParam_Http_Base是参数基类。外部在访问URL资源时,经常要分配一些资源。这时,可以从CParam_Http_Base派生一个类,将所有分配的资源放入其中。这样,CCuteMoniker就会在合适的时候自动释放这些资源。

三、外部调用举例

下面举一个回复帖子的例子,主要有两个方法。

CCSDNTools::topicReply:用于启动向服务器提交数据;

CCSDNTools::func_topicReplyResponseProc:当回复成功时,调用此回调函数。

1、CCSDNTools::topicReply

//回复帖子

//bstrTopicID:帖子ID

//bstrContent:回复正文

//pDispCallback:采用异步操作,当回复成功时,调用这个回调接口

STDMETHODIMP CCSDNTools::topicReply(BSTR bstrTopicID, BSTR bstrContent, IDispatch* pDispCallback)

{

// TODO: 在此添加实现代码

USES_CONVERSION;

//构造异步参数,CParam_Execute_TopicView派生自CParam_Http_Base

CParam_Execute_TopicView* pParamExecute=new CParam_Execute_TopicView();

pParamExecute->m_bstrUrl=::SysAllocString(bstrTopicID);

pParamExecute->m_pDispCallback=pDispCallback;

if(pDispCallback!=NULL)

pDispCallback->AddRef();//增加引用

//构造URL

LPCTSTR szTopicID=W2A(bstrTopicID);

CString sUrl;

sUrl.Format("http://community.csdn.net/Expert/reply.asp?Topicid=%s",szTopicID);

//构造Headers

CString sHeaders;

sHeaders.Format("Content-Type:application/x-www-form-urlencoded\r\nReferer:http://community.csdn.net/Expert/xsl/Reply_Xml.asp?Topicid=%s\r\n",szTopicID);

//对回复的文本进行编码

CString sContent;

EscapeToCString(sContent,W2A(bstrContent));

//构造Post Data

CString sPostData;

sPostData.Format("Topicid=%s&xmlReply=aaaaa&csdnname=&csdnpassword=&ReplyContent=%s",

szTopicID,sContent);

//新建一个CCuteMoniker对象

CComPtr<IUnknown> pUnkThis;

this->QueryInterface(IID_IUnknown,(void**)&pUnkThis);

CCuteMoniker* pHttp=new CCuteMoniker(pUnkThis);

//调用Request方法

HRESULT hr=pHttp->Request("POST",sUrl,VARIANT_TRUE,sHeaders,sPostData,

func_topicReplyResponseProc,NULL,pParamExecute,NULL,BINDF_GETNEWESTVERSION);

if(FAILED(hr))

{

return hr;

}

return S_OK;

}

2、CCSDNTools::func_topicReplyResponseProc

//回复返回

//pParam1:参数1

//pParam2:参数2

//pHttpBase:这是CCuteMoniker的基类。

//pStream:这是返回的数据,用于指定回复是否成功,及后续的操作指令

void CCSDNTools::func_topicReplyResponseProc(void* pParam1,CParam_Http_Base* pParam2,CCuteHttpBase* pHttpBase,IStream* pStream)

{

USES_CONVERSION;

//强制转换参数2

CParam_Execute_TopicView* pParamExecute=(CParam_Execute_TopicView*)pParam2;

//构造URL,准备缓存结果

LPCTSTR szTopicID=W2A(pParamExecute->m_bstrUrl);

CString sUrl;

sUrl.Format("http://community.csdn.net/Expert/reply.asp?Topicid=%s",szTopicID);

//将数据缓存至Internet临时目录,为的是让浏览器转向这个页面,从而自动执行后续的操作指令。

char szFileName[MAX_PATH];

CCuteToolsB::SavetoCache(pStream,sUrl,szFileName,1,"htm",NULL);

//回调,将缓存得到的临时文件名回调给外部调用者,以便浏览器转向这个页面。

CComVariant vParam1=szTopicID;

CComVariant vParam2=szFileName;

CCuteTools::AutoWrap(

DISPATCH_METHOD,NULL,pParamExecute->m_pDispCallback,NULL,2,vParam2,vParam1);

}

四、创建URL Moniker,启动访问过程

接下来,我们看看在CCuteMoniker::Request方法中如何创建URL Moniker对象,并启动访问过程。

CComPtr<IMoniker> m_spMoniker;//URL Moniker对象

CComPtr<IBindCtx> m_spBindCtx;//绑定环境,通过向绑定环境注册一个回调接口,我们可以控制URL传输的过程,并得到反馈信息。

CComPtr<IStream> spStream;//如果是同步调用,可在绑定返回时,直接得到数据流

//创建一个URL Moniker对象

hr = CreateURLMoniker(NULL, A2W(szURL), &m_spMoniker);

//创建一个绑定环境

hr = CreateBindCtx(0, &m_spBindCtx);

//向绑定环境注册一个回调接口IBindStatusCallback,CCuteMoniker派生自接口IBindStatusCallback。

hr = RegisterBindStatusCallback(m_spBindCtx, static_cast<IBindStatusCallback*>(this), 0, 0L);

//执行绑定,启动实际的URL访问及数据传输过程。

hr = m_spMoniker->BindToStorage(m_spBindCtx, 0, __uuidof(IStream), (void**)&spStream);

//如果是同步操作,则直接返回数据流

if(!bAsync)

{

ATLASSERT(ppResponse!=NULL);

//复制数据流,并返回。

return CCuteTools::CopyStream(spStream,ppResponse);

}

五、绑定状态回调接口IBindStatusCallback

CCuteMoniker派生自接口IBindStatusCallback,在实际的访问及数据传输过程中,Moniker对象会通过接口IBindStatusCallback取得相关的绑定信息,如绑定标志、访问方法、Post Data,也可以通过它反馈当前的进度,汇报返回的数据。

1、IBindStatusCallback::OnStartBinding方法。

将传入的参数IBinding *pBinding保存下来。通过IBinding ,我们可以暂停、重启、中止绑定过程。

STDMETHOD(OnStartBinding)(DWORD /*dwReserved*/, IBinding *pBinding)

{

ATLTRACE(atlTraceControls,2,_T("CBindStatusCallback::OnStartBinding\n"));

m_spBinding = pBinding;

return S_OK;

}

2、IBindStatusCallback::GetBindInfo方法。

通过这个方法,我们可以指定绑定标志、访问方法、Post Data。

STDMETHOD(GetBindInfo)(DWORD *pgrfBINDF, BINDINFO *pbindInfo)

{

ATLTRACE(atlTraceControls,2,_T("CBindStatusCallback::GetBindInfo\n"));

if (pbindInfo==NULL || pbindInfo->cbSize==0 || pgrfBINDF==NULL)

return E_INVALIDARG;

//绑定标志,

//默认为 (BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE |BINDF_GETNEWESTVERSION | BINDF_NOWRITECACHE)

*pgrfBINDF = m_dwBindFlags;

//初始化结构体

ULONG cbSize = pbindInfo->cbSize; // remember incoming cbSize

memset(pbindInfo, 0, cbSize); // zero out structure

pbindInfo->cbSize = cbSize; // restore cbSize

//指定访问方法

if(this->m_sMethod=="POST")

pbindInfo->dwBindVerb = BINDVERB_POST;

else

pbindInfo->dwBindVerb = BINDVERB_GET;

//指定post data

pbindInfo->cbstgmedData=this->m_dwPostSize;

pbindInfo->stgmedData.tymed=TYMED_HGLOBAL;

pbindInfo->stgmedData.hGlobal=this->m_hGlobalPost;

return S_OK;

}

3、IBindStatusCallback::OnDataAvailable方法

当有数据返回(可能分多次返回)时,调用此方法。我们可以得到返回的数据流对象及数据大小

STDMETHOD(OnDataAvailable)(DWORD grfBSCF, DWORD dwSize, FORMATETC * /*pformatetc*/, STGMEDIUM *pstgmed)

{

ATLTRACE(atlTraceControls,2,_T("CBindStatusCallback::OnDataAvailable\n"));

HRESULT hr = S_OK;

//当第一次返回数据时,设置标志BSCF_FIRSTDATANOTIFICATION

//这时,我们取得数据流对象

if (BSCF_FIRSTDATANOTIFICATION & grfBSCF)

{

if (!m_spStream && pstgmed->tymed == TYMED_ISTREAM)

m_spStream = pstgmed->pstm;

}

//当最后一次返回数据时,设置标志BSCF_LASTDATANOTIFICATION

//这时,我们取得数据的完整大小

if (BSCF_LASTDATANOTIFICATION & grfBSCF)

{

this->m_dwTotalRead=dwSize;

}

return hr;

}

4、IBindStatusCallback::OnStopBinding方法

在绑定结束的时候,最后执行这个方法。这时,如果是异步调用,我们就可以把得到的数据通过回调函数返回给外部。

STDMETHOD(OnStopBinding)(HRESULT hresult, LPCWSTR /*szError*/)

{

ATLTRACE(atlTraceControls,2,_T("CBindStatusCallback::OnStopBinding\n"));

//清理对象

if(m_spBinding!=NULL)

m_spBinding.Release();

if(m_spBindCtx!=NULL)

m_spBindCtx.Release();

if(m_spMoniker!=NULL)

m_spMoniker.Release();

//如果是异步调用,则执行回调

if(this->m_bAsync)

{

ATLASSERT(m_pProc!=NULL);

//回调

try

{

//复制数据流

CComPtr<IStream> pStream;

CCuteTools::CopyStream(m_spStream,&pStream);

//调用回调函数

this->m_pProc(this->m_pParam1,this->m_pParam2,this,pStream);

}

catch(_com_error& e)

{}

}

//释放数据流

if(m_spStream!=NULL)

m_spStream.Release();

//如果是异步,释放自身

if(this->m_bAsync)

{

this->Release();

}

return S_OK;

}

5、IBindStatusCallback还有其他几个方法,由于在"CSDN助手"中没有用到,所以简单的返回S_OK。

六、接口IHttpNegotiate,处理Headers信息

至此,一个基本的URL访问框架已经成形了。但还没有解决在Request时发送Headers,当Response时得到Headers的问题。

接口IHttpNegotiate可以帮助我们解决这个问题。在MSDN中,有这样一句话来描述IHttpNegotiate:“Urlmon.dll uses the QueryInterface method on your implementation of IBindStatusCallback to obtain a pointer to your IHttpNegotiate interface.”。由于类CCuteMoniker派生自接口IBindStatusCallback,显然我们还要让类CCuteMoniker派生自接口IHttpNegotiate。这样,Moniker对象才能通过已注册的接口IBindStatusCallback得到接口IHttpNegotiate。

1、IHttpNegotiate::BeginningTransaction方法,提供Request时的Headers信息

virtual HRESULT STDMETHODCALLTYPE BeginningTransaction(

/* [in] */ LPCWSTR szURL,

/* [unique][in] */ LPCWSTR szHeaders,

/* [in] */ DWORD dwReserved,

/* [out] */ LPWSTR *pszAdditionalHeaders)

{

USES_CONVERSION;

if(this->m_sHeaders!="")

{

LPCWSTR swzHeaders=A2W(this->m_sHeaders);

int nSize=(wcslen(swzHeaders)+1)*2;

//必须用CoTaskMemAlloc分配内存,因为Monker对象用CoTaskMemFree进行释放。

LPWSTR pszHeaders=(LPWSTR)CoTaskMemAlloc(nSize);

memcpy(pszHeaders,swzHeaders,nSize);

*pszAdditionalHeaders=pszHeaders;

}

return S_OK;

}

2、IHttpNegotiate::OnResponse方法,在这里我们可以得到Response的Headers信息及响应码。

virtual HRESULT STDMETHODCALLTYPE OnResponse(

/* [in] */ DWORD dwResponseCode,

/* [unique][in] */ LPCWSTR szResponseHeaders,

/* [unique][in] */ LPCWSTR szRequestHeaders,

/* [out] */ LPWSTR *pszAdditionalRequestHeaders)

{

this->m_nResponseStatus=dwResponseCode;

this->m_sResponseHeaders=szResponseHeaders;

return S_OK;

}

七、其他参考文章

关于CCuteTools::CopyStream方法,相关信息请参见为何有些IStream不能得到HGlobal句柄

关于CCuteTools::AutoWrap方法,相关信息请参见

“CSDN助手”源代码下载,请转到http://blog.csdn.net/seasol/archive/2006/07/04/873747.aspx

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有