为了更快的切入主题,这里就不在回答
1.为什么要使用URLDownloadToFile?
2.使用URLDownloadToFile有多少好处,多少坏处?
3.C#有封装好的什么什么
.....................
等等之类的问题,这里只说! 使用C# 如何实现URLDownloadToFile的回调,能停止,能暂停,能看到进度的问题
说到低,实现URLDownloadToFile的回调,就是对接口IBindStatusCallback的实现,
微软MSN有代码:
[ComImport,Guid("79EAC9C1-BAF9-11CE-8C82-00AA004BA90B"),InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IBindStatusCallback{....}
还有另外个网站有完整系统的说明(需要翻墙才能看)
http://www.pinvoke.net/default.aspx/Interfaces/IBindStatusCallback.html
但很遗憾,我用纯C#无法很好实现这个接口,总是报内存无权访问...
无奈之下,不得另劈途径,是否可借助非托管DLL去实现接口IBindStatusCallback呢?
于是初步确定算法步骤如下
1.使用C++写好一个DLL,该DLL有如下功能
①.定义用于与C#交互的函数定义:
typedef LRESULT (CALLBACK* Progress_Ptr)(LONG lMsgType,//消息类型,0:接口函数调用; 1:下载进度消息
ULONG ulProgress,//进度消息时,表示下载完成数;调用消息时,永远描述被调用函数的序号
ULONG ulProgressMax,//进度消息时,表示总文件大小;调用消息时,保持为0
ULONG ulStatusCode//进度消息时,表示相关状态;调用消息时,保持为0
);//用于用户接口定义
②.定义一个继承接口IBindStatusCallback的类MyCallback,除了实现相关函数为,多了一个属性和方法,分别是
属性 Progress_Ptr m_fun_ptr;//该函数指针指向C# 提供的委托
方法 SetPtr(Progress_Ptr);//该方法用于对属性的实现,就是将属性指向用户体提供的参数,一个C#的委托
③.MyCallback类除了OnProgress函数,其他都直接返回 S_OK;//0,而OnProgress函数则通过执行用户接口m_fun_ptr来返回,从而把 下载控制权留给了调用者
④.对外导出二个函数
MyCallback* NewIBSClass(Progress_Ptr uproc);//该函数用于使用用户提供的函数,实例一个MyCallback类,并返回其实例指针
void DisIBSClass(MyCallback* mcall);//销毁一个实例类
3.C#部分,
①.根据CDownIBind.dll的回调约定,定义一个委托,
/// <summary>
/// 一个用于接收来自回调的委托
/// </summary>
/// <param name="msgType">消息类型 0:接口函数调用通知,1:下载进度通知</param>
/// <param name="ulProgress"></param>
/// <param name="ulProgressMax"></param>
/// <param name="ulStatusCode"></param>
/// <returns>继续下载返回S_OK=0,终止下载返回E_ABORT = 0x80004004</returns>
private delegate uint OnProcDel(int msgtype,uint ulProgress, uint ulProgressMax, uint ulStatusCode);
②.将URLDownloadToFile声明为
/// <summary>
/// 下载一个网络文件
/// </summary>
/// <param name="pAxCaller">如果是一个Actix时使用</param>
/// <param name="szURL">网络地址</param>
/// <param name="szFileName">本地文件地址</param>
/// <param name="dwReserved">保留:设为0</param>
/// <param name="lpfnCB">一个执行回调的指针,是一个继承IBindStatusCallback的类示例</param>
/// <returns>0表示成功,其他表示失败</returns>
[DllImport("urlmon.dll")]
public static extern uint URLDownloadToFile(IntPtr pAxCaller,
string szURL,
string szFileName,
uint dwReserved,
IntPtr lpfnCB);
其他就不多说了!,,上代码:
C++编写DLL部分,怎么写DLL..我就懒得说了,自己找资料,,
DLL 名称:CDownIBind.dll
#include "windows.h"
#include <Urlmon.h>
#pragma comment(lib, "Urlmon.lib")
typedef LRESULT (CALLBACK* Progress_Ptr)(LONG lMsgType,//消息类型,下载进度消息1,接口函数调用通知0
ULONG ulProgress,//进度消息时,表示下载完成数;调用消息时,永远描述被调用函数的序号
ULONG ulProgressMax,//进度消息时,表示总文件大小;调用消息时,保持为0
ULONG ulStatusCode//进度消息时,表示相关状态;调用消息时,保持为0
);//用于用户接口定义
class MyCallback:public IBindStatusCallback
{
//一个用户接口
Progress_Ptr m_fun_ptr;
public:
MyCallback():m_fun_ptr(NULL){}
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject)
{
if(NULL!=m_fun_ptr)//调用用户接口,表示函数1被调用
m_fun_ptr(0,1,0,0);
return S_OK;
}
virtual ULONG STDMETHODCALLTYPE AddRef(void)
{
if(NULL!=m_fun_ptr)//调用用户接口,表示函数2被调用
m_fun_ptr(0,2,0,0);
return S_OK;//返回 S_OK=0 表示继续下载
//return 1;
}
virtual ULONG STDMETHODCALLTYPE Release( void)
{
if(NULL!=m_fun_ptr)//调用用户接口,表示函数3被调用
m_fun_ptr(0,3,0,0);
return 0;
}
virtual HRESULT STDMETHODCALLTYPE OnStartBinding(
/* [in] */ DWORD dwReserved,
/* [in] */ IBinding __RPC_FAR *pib)
{
if(NULL!=m_fun_ptr)//调用用户接口,表示函数4被调用
m_fun_ptr(0,4,0,0);
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE GetPriority(
/* [out] */ LONG __RPC_FAR *pnPriority)
{
if(NULL!=m_fun_ptr)//调用用户接口,表示函数5被调用
m_fun_ptr(0,5,0,0);
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE OnLowResource(
/* [in] */ DWORD reserved)
{
if(NULL!=m_fun_ptr)//调用用户接口,表示函数5被调用
m_fun_ptr(0,6,0,0);
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE OnStopBinding(
/* [in] */ HRESULT hresult,
/* [unique][in] */ LPCWSTR szError)
{
if(NULL!=m_fun_ptr)//调用用户接口,表示函数7被调用
m_fun_ptr(0,7,0,0);
return S_OK;
}
virtual /* [local] */ HRESULT STDMETHODCALLTYPE GetBindInfo(
/* [out] */ DWORD __RPC_FAR *grfBINDF,
/* [unique][out][in] */ BINDINFO __RPC_FAR *pbindinfo)
{
if(NULL!=m_fun_ptr)//调用用户接口,表示函数8被调用
m_fun_ptr(0,8,0,0);
return S_OK;
}
virtual /* [local] */ HRESULT STDMETHODCALLTYPE OnDataAvailable(
/* [in] */ DWORD grfBSCF,
/* [in] */ DWORD dwSize,
/* [in] */ FORMATETC __RPC_FAR *pformatetc,
/* [in] */ STGMEDIUM __RPC_FAR *pstgmed)
{
if(NULL!=m_fun_ptr)//调用用户接口,表示函数9被调用
m_fun_ptr(0,9,0,0);
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE OnObjectAvailable(
/* [in] */ REFIID riid,
/* [iid_is][in] */ IUnknown __RPC_FAR *punk)
{
if(NULL!=m_fun_ptr)//调用用户接口,表示函数10被调用
m_fun_ptr(0,10,0,0);
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE OnProgress(
/* [in] */ ULONG ulProgress,
/* [in] */ ULONG ulProgressMax,
/* [in] */ ULONG ulStatusCode,
/* [in] */ LPCWSTR szStatusText)//这个函数是最重要的了,下载过程会一直调用该函数
{
/*
如果用户接口存在,则把控制权转移到用户层,
这里不传递szStatusText参数,
是因为本人不想招惹麻烦,这个参数有时候会是 NULL 可能带来风险,但没试验
*/
if(NULL!=m_fun_ptr)
return m_fun_ptr(1,ulProgress,ulProgressMax,ulStatusCode);
return S_OK;
}
//此方法将,类属性指向用户接口函数,
//传入用户提供的接口
void SetPtr(Progress_Ptr fun_ptr)
{
m_fun_ptr = fun_ptr;
}
};
//一个导出函数,用户提供一个Progress_Ptr 函数来实现一个接口类,并返回类指针
extern "C"
MyCallback* CALLBACK NewIBSClass(Progress_Ptr onprocc)//得到一个构造类
{
MyCallback* temCall=new MyCallback;
temCall->SetPtr(onprocc);
return temCall;
}
//销毁实例类指针
extern "C"
VOID CALLBACK DisIBSClass(MyCallback* temCall)//删除一个类指针
{
if(NULL==temCall)
return;
delete temCall;
}
extern "C"
VOID CALLBACK LoadFun()//首次查找这个函数,保证DLL 能正确使用
{
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
C# 部分
public partial class Form1 : Form
{
/// <summary>
/// 下载一个网络文件
/// </summary>
/// <param name="pAxCaller">如果是一个Actix时使用</param>
/// <param name="szURL">网络地址</param>
/// <param name="szFileName">本地文件地址</param>
/// <param name="dwReserved">保留:设为0</param>
/// <param name="lpfnCB">一个执行回调的指针,是一个继承IBindStatusCallback的类示例</param>
/// <returns>0表示成功,其他表示失败</returns>
[DllImport("urlmon.dll")]
private static extern uint URLDownloadToFile(IntPtr pAxCaller,
string szURL,
string szFileName,
uint dwReserved,
IntPtr lpfnCB);
/// <summary>
/// 实现一个IBindStatusCallback接口,并返回指针
/// </summary>
/// <param name="delfun">一个OnProcDel委托的实例</param>
/// <returns></returns>
[DllImport("CDownIBind.dll")]
private static extern IntPtr NewIBSClass(OnProcDel delfun);
/// <summary>
/// 销毁一个实现的IBindStatusCallback接口
/// </summary>
/// <param name="ibis"></param>
/// <returns></returns>
[DllImport("CDownIBind.dll")]
private static extern int DisIBSClass(IntPtr ibis);
/// <summary>
/// 一个用于接收来自回调的委托
/// </summary>
/// <param name="msgType">消息类型 0:接口函数调用通知,1:下载进度通知</param>
/// <param name="ulProgress"></param>
/// <param name="ulProgressMax"></param>
/// <param name="ulStatusCode"></param>
/// <returns>继续下载返回S_OK=0,终止下载返回E_ABORT = 0x80004004</returns>
private delegate uint OnProcDel(int msgtype, uint ulProgress, uint ulProgressMax, uint ulStatusCode);
/// <summary>
/// IBindStatusCallback类示例的指针
/// </summary>
IntPtr ibClas = IntPtr.Zero;
OnProcDel thisProc, //表示指向本地的委托指针
apiProc;//用于接收来自DLL的通知
System.Threading.Thread DownThrObj;
/// <summary>
/// 下载状态 0:没有动作,1:下载中,2:暂停中,3:等待终止
/// </summary>
byte bDownStats = 0;
string sUrl = "", sLocfile = "", sDownCap = "";
public Form1()
{
InitializeComponent();
apiProc += OnProecc;//挂载函数
thisProc += OnProecc;//挂载函数
ibClas = NewIBSClass(apiProc);//实现本得到指针
label1.Text =
label2.Text = "";
CMD_控制.Enabled = false;
//注册时间
CMD_控制.Click += CMDClick;
CMD_下载.Click += CMDClick;
}
protected override void OnClosed(EventArgs e)
{
if (bDownStats != 0)
{
bDownStats = 3;
DownThrObj.Join();
}
if (ibClas != IntPtr.Zero)
DisIBSClass(ibClas);
base.OnClosed(e);
}
private void CMDClick(object send, EventArgs e)
{
if (CMD_下载 == send)
{
#region
switch (bDownStats)
{
case 0://没有动作
label1.Text = "";
PB_进度条.Value = 0;
if (textBox1.Text.Length == 0)
{
label2.Text = "提供的网络地址无效";
return;
}
if (ibClas == IntPtr.Zero)
{
try
{
ibClas = NewIBSClass(apiProc);
}
catch (Exception ex)
{
Win32Exception we = new Win32Exception(Marshal.GetLastWin32Error(), ex.Message);
label2.Text = "实现接口失败:\r" + we.Message;
return;
}
}
if (ibClas == IntPtr.Zero)
{
label2.Text = "接口未实现";
return;
}
listBox1.Items.Clear();
textBox1.Enabled = false;
CMD_控制.Enabled = true;
CMD_控制.Text = "暂停";
CMD_下载.Text = "终止";
bDownStats = 1;
sUrl = textBox1.Text + "?ux=" + new Random().NextDouble().ToString("0.0000000000000000");//为了防止从缓存下载,使用一个随机参数
sLocfile = "d:\\15.exe";//本地文件可以自行修改
DownThrObj = new System.Threading.Thread(DownThrFun);
DownThrObj.IsBackground = true;
label2.Text = "下载:" + sLocfile;
DownThrObj.Start();//开始下载
break;
case 1://下载中
case 2://暂停中
bDownStats = 3;
break;
case 3://等待终止
break;
default:
label2.Text = "状态未知";
break;
}
#endregion
return;
}
if (CMD_控制 == send)
{
#region
switch (bDownStats)
{
case 1://下载中
bDownStats = 2;
break;
case 2://暂停中
bDownStats = 1;
break;
}
#endregion
return;
}
}
/// <summary>
/// 用于下载线程工作的函数
/// </summary>
private void DownThrFun()
{
int iva1 = 0;
try
{
iva1 = (int)URLDownloadToFile(IntPtr.Zero, sUrl, sLocfile, 0, ibClas);
}
catch (Exception ex)
{
if (bDownStats == 1)
{
Win32Exception we = new Win32Exception(Marshal.GetLastWin32Error(), ex.Message);
sDownCap = "下载失败:" + we.Message;
bDownStats = 0;
OnProecc(3, 0, 0, 0);
return;
}
}
if (iva1 != 0)
{
Win32Exception we = new Win32Exception(Marshal.GetLastWin32Error(), "失败");
sDownCap = "下载失败:" + iva1.ToString();
bDownStats = 0;
OnProecc(3, 0, 0, 0);
return;
}
if (bDownStats == 3)
{
bDownStats = 0;
sDownCap = "用户终止";
OnProecc(3, 0, 0, 0);
return;
}
bDownStats = 0;
OnProecc(3, 1, 0, 0);
}
/// <summary>
/// 接口函数,本窗口也是
/// </summary>
/// <param name="msgtype"></param>
/// <param name="ulProgress"></param>
/// <param name="ulProgressMax"></param>
/// <param name="ulStatusCode"></param>
/// <returns></returns>
private uint OnProecc(int msgtype, uint ulProgress, uint ulProgressMax, uint ulStatusCode)
{
if (bDownStats != 1 && bDownStats != 2 && msgtype != 3 && msgtype != 13)
return 0x80004004;// S_OK=0,继续,E_ABORT = 0x80004004 终止
if (msgtype < 10)//小于10,有可能来自其他线程,所以需要进行投递成本地消息
{
ZtKey:
if (bDownStats == 2)
{
System.Threading.Thread.Sleep(10);
goto ZtKey;
}
msgtype = msgtype + 10;
this.BeginInvoke(thisProc, msgtype, ulProgress, ulProgressMax, ulStatusCode);//投递到目标消息队列
return 0;
}
if (ulProgress > 0)
{
if (ulProgressMax > 0)
{
float fva1 = ulProgress * 1.0f / ulProgressMax * 100.0f;
label1.Text = fva1.ToString("0.0");
PB_进度条.Value = (int)fva1;
}
}
else
listBox1.Items.Add(msgtype.ToString() + ":" + ulProgress.ToString() + "|" + ulProgressMax.ToString());
if (msgtype == 13)
{
label1.Text = "";
textBox1.Enabled = true;
CMD_下载.Text = "开始";
CMD_控制.Text = "暂停";
CMD_控制.Enabled = false;
PB_进度条.Value = 0;
if (ulProgress == 0)
label2.Text = "失败:" + sDownCap;
else
label2.Text = sLocfile;
}
return 0;
}
}
关于如何实现,超时,,,就简单了,无需再说罢! 通过Stopwatch给每个下载线程一个活动戳,,一点超过多长时间 不活动,就意味着超时了,直接终止,下载线程既可
下载地址,2点积分哦,最近积分吃紧,
http://download.csdn.net/detail/yangshengchuan/9316173