C# 实现URLDownloadToFile回调

为了更快的切入主题,这里就不在回答

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




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值