您的位置:首页 > 编程语言 > C#

C# 实现URLDownloadToFile回调

2015-12-01 11:44 489 查看
为了更快的切入主题,这里就不在回答

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] */ v
c9db
oid __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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息