优秀的编程知识分享平台

网站首页 > 技术文章 正文

C#编写软件升级更新程序的完整示例

nanyue 2024-10-01 13:14:21 技术文章 9 ℃

一个软件在做调整以后,都是要升级更新的,如果这个时候要求用户重新去下载,体验非常不好,所以程序一运行起来,就应该判断是不是要更新,如果要的话,直接自动更新,再运行,这样用户体验会好很多。


任务过程:更新的文件是放在一个远程目录http://192.168.1.100/Resourse/Update/中,软件启动后,先判断是不是需要更新,如果要,去远程目录下载文件更新,然后再运行,中间需要用户看到下载更新的过程

效果如图:这里我只是实现的过程,细节比如文字啥的比较简单,就没有弄


1、创建一个名为Update的项目,窗体名称改为DownFileFromURL,窗体代码如下:(大多数函数我已经弄好,直接复制就可以,注释的地方看下应该不难)

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;
using System.Windows.Forms;

namespace Update
{
    public partial class DownFileFromURL : Form
    {
        string webURL;        //远程下载文件地址
        string[] FileNames;   //要下载的文件名数组
        //下载的文件是否加上tmp,这个是我当时做别的加的功能,这里没什么用 
        string ReName;    
        //更新成功后,运行哪个文件      
        string RunEXEname;
        //完成成功标志,初始值为false
        bool ok = false;

        //构造函数
        public DownFileFromURL(string theurl, string filenames, string renametoaddtmp,string runwho)
        {
            #region 调用示例
            //string filenames = "a.exe|b.txt|c.bmp";

            //string args = "";
            ////下载地址
            //string args += "http://192.168.1.100/Resourse/Update/ ";
            ////下载文件名字符串,用“|”隔开
            //args += filenames + " ";
            ////更新文件不改名
            //args += "false" + " ";
            ////更新结束后运行哪个文件
            //args += "\\a.exe" + " ";
            #endregion

            //初始化本地参数
            webURL = theurl;
            FileNames = filenames.Split('|');
            ReName = renametoaddtmp;
            RunEXEname = runwho;

            InitializeComponent();
        }

        private void DownFileFromURL_Load(object sender, EventArgs e)
        {
            //在线程中操作控件值的话要加上下面这句话
            CheckForIllegalCrossThreadCalls = false;

            label1.Text = "开始下载新版本,请稍等...";
            Thread t = new Thread(new ThreadStart(DownFileGo));
            t.Start();
        }

        void DownFileGo()
        {
            int howmanyfiles = FileNames.Length;
            for (int i = 0; i < howmanyfiles; i++)
            {
                //逐个下载
                DownFile(mybar, Application.StartupPath + "\\", webURL + "/" + FileNames[i], ReName);

                if (i == howmanyfiles - 1)
                {
                    //下载完以后如果参数中要求直接运行指定程序,就运行它
                    if (RunEXEname.Length != 0)
                    {
                        Process.Start(Application.StartupPath + RunEXEname);
                    }

                    //成功标志改为true
                    ok = true;
                    this.Close();
                    break;
                }
            }
        }
        private void DownFile(ProgressBar thebar, string dir, string URL, string rename)
        {
            string filename = URL.Substring(URL.LastIndexOf("/") + 1);
            string FileName = dir + filename;  //下载到其它目录下,dir参数未测试

            if (rename == "true") { FileName = FileName + ".tmp"; }

            //得到远程文件信息
            HttpWebRequest Myrq = (HttpWebRequest)HttpWebRequest.Create(URL);
            HttpWebResponse myrp = (HttpWebResponse)Myrq.GetResponse();
            long totalBytes = myrp.ContentLength;
            thebar.Maximum = (int)totalBytes;

            //常规文件流处理代码
            Stream st = myrp.GetResponseStream();
            Stream so = new FileStream(FileName.Replace("%20", " ").Replace("%2520", " "), FileMode.Create);
            long totalDownloadedByte = 0;
            byte[] by = new byte[1024];
            int osize = st.Read(by, 0, (int)by.Length);
            while (osize > 0)
            {
                totalDownloadedByte = osize + totalDownloadedByte;
                Application.DoEvents();
                so.Write(by, 0, osize);
                thebar.Value = (int)totalDownloadedByte;
                osize = st.Read(by, 0, (int)by.Length);
                if (thebar.Value == (int)totalBytes)
                {
                    thebar.Value = 0;
                }
            }
            so.Close();
            st.Close();
        }

        private void DownFileFromURL_FormClosing(object sender, FormClosingEventArgs e)
        {
            //如果还没下载完,退出
            if (!ok)
            {
                e.Cancel = true;
            }
        }
    }
}

2、设置它的启动参数,修改program.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Update
{
    static class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            Process instance = RunningInstance();
            //这个程序是否运行,因为我自己用,所以我一直用进程名判断
            if (instance == null)
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);

                //如果参数个数正确
                if (args.Length == 5)
                {
                    string theurl = args[0];
                    string filenames = args[1];
                    string rename = args[2];
                    string runexename = args[3];

                    //这里先把自己的主程序进程杀掉,
                    //再运行更新窗体
                    string killexename = args[4];
                    Kill(killexename);

                    Application.Run(new DownFileFromURL(theurl, filenames, rename, runexename));
                }

                else
                {
                    MessageBox.Show("^#$#!#@!*^*@%#$#!@%\r\n\r\n@%#!@%!#@!*^@%#!@%!@%@%#$#!@%#@!*^\r\n\r\n#@!*^@%#!@%!@%#@!*^#$#@%!@%#@!*^#\r\n\r\n................");
                }
            }
            else
            {
                Process.GetCurrentProcess().Kill();
            }
        }
        /// <summary>
        /// 杀死进程
        /// <para>Kill("winword");不带exe</para>
        /// </summary>
        /// <param name="pname">进程名称,不带exe</param>
        public static void Kill(string pname)
        {
            try
            {
                Process[] p = Process.GetProcesses();
                foreach (Process tp in p)
                {

                    if (tp.ProcessName == pname | tp.ProcessName.ToLower() == pname)
                    {
                        tp.Kill();
                    }
                }
            }
            catch
            { }
        }
        public static Process RunningInstance()
        {
            Process current = Process.GetCurrentProcess();
            Process[] processes = Process.GetProcessesByName(current.ProcessName);
            foreach (Process process in processes)
            {
                if (process.Id != current.Id)
                {
                    if (Assembly.GetExecutingAssembly().Location.Replace("/", "\\") == current.MainModule.FileName)
                    {
                        return process;
                    }
                }
            }
            return null;
        }
    }
}

经过这两步,更新程序本身就完成了,文件名就是项目名:update.exe.现在我们来看下如何用其它程序来调用它。

新建一个主程序 ksh.exe,在它运行时,比如可以放在program.cs中,加入以下模块

//我更改了数据库中的更新版本号
//此时从库中读出来,也就是库中片版本号,当然也可以想其它办法
string cmdstr = "select Cver from Ver";
string dbver = GetFirstData(cmdstr);

//当前片版本号
//[assembly: AssemblyFileVersion("1.0")]
//这个也可以把当前版本号放在xml中与dbver对比,更新完再更新xm即可
string nowver = Application.ProductVersion;

//更新文件文件名
string exename = Application.StartupPath + "\\Update.exe";

//如果版本号不一致
if (dbverf > nowverf)  
{
    //添加update.exe运行需要的参数
    //下载地址
    string args = "http://192.168.1.100/Resourse/Update/C ";
    //要下载的文件名列表
    string filenames = "a.exe|b.txt|c.xml";
    string[] filenamesarr = filenames.Split('|');
    args += filenames + " ";
    //下载的文件是否改名
    args += "false" + " ";
    //更新后运行哪个文件
    args += "\\a.exe" + " ";
    //我当前的进程名,因为要更新自己的话
    //万一自己还没有退出,则需要让update.exe杀死自己
    //当然你也可以让update检测,等它退出再更新
    args += "ksh";

    //运行update.exe
    RunINCMD(exename, args);

    //退出自己,准备更新
    Application.ExitThread();
    this.DialogResult = DialogResult.No;
}
void RunINCMD(string EXEname, string Args)
{
    System.Diagnostics.Process Process1 = new System.Diagnostics.Process();
    Process1.StartInfo.FileName = EXEname;
    Process1.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;

    Process1.StartInfo.Arguments = " " + Args;
    Process1.Start();
    Process1.WaitForExit();
}

这样当ksh.exe运行后,就会先判断自己的版本号与远程版本号是不是一致,如果不一致,就提供update.exe需要的参数,然后运行update.exe,自己退出或被update.exe文件kill掉,保证更新进行。

在更新过程中,因为我是写在线程中的,所以有几个文件,大小多少,下载进度这些都是可控的,我没有写

最后,

//要下载的文件名列表

string filenames = "a.exe|b.txt|c.xml";

有一天你想在下载列表中新加一些文件,如果你这样写就得重新改程序,我用的方法是浏览更新文件目录,把目录下所有文件名读出来,这样你下次要求软件更新时,新增文件直接复制到远程目录中就可以了。

这个功能前几天我写过,有要用的可以点下面去看:

C#得到远程目录中的文件名称

最近发表
标签列表