使用C# Winform做升级程序,在winform启动程序中进行升级信息的检测,最后启动unity程序。
Winform升级程序项目介绍
在博客园找到一个比较合适的升级程序 WINFORM自动更新程序 文章中有源码放在码云上可以下载。按照文章进行配置即可,这里我遇到一些坑,大家可以注意一下。
目录结构
AutoUpdate与AutoUpdateGUI这两个都是自动升级程序,AutoUpdate是静默升级没有升级界面,AutoUpdateGUI有程序的升级界面可以看到升级程序的下载进度及下载文件的信息,如图
- AutoUpdateLib是升级程序的类库。默认编译生成.exe的可执行文件,可以将输出类型改为类库。
- 每个项目里都会有一个说明文件,介绍每个项目如何配置与使用。
- 项目编译生成目录 AutoUpdate-master\AutoUpdate\bin下 将编译成 AutoUpdate.exe,AutoUpdateLib.dll,并与你的主程序放在同一个目录。
- 本地配置文件updatelist.xml,userconfig.xml 必须存在。服务器配置文件limit.xml,updatelist.xml 必须存在。
- 限制的做法:服务端配置文件 limit.xml LimitKey,本地配置文件 UserConfig.xml UPDATE_KEY这一点要注意,key不一致不会升级。
- 升级autoupdate.exe的做法:下载服务端的limit.xml文件看本在的updatelist.xml Autoupdate.exe的版本
是否与limit.xml中AutoupdateVersion的值一致,不一致就下载autoupdate.exe文件 - updatelist.xml这里的配置属性要重点弄明白。
- 如何启动主程序参照 WINFORM自动更新程序 文章。
Window升级程序本地与服务器配置
使用IIS建立服务器升级站点,存放升级文件及配置信息。
所使用的配置文件信息
- limit.xml
<?xml version="1.0" encoding="utf-8" ?>
<Config>
<LimitKey>AGD</LimitKey>
<AutoupdateVersion>2.01.001</AutoupdateVersion>
</Config>
- UserConfig.xml
<?xml version="1.0" standalone="yes"?>
<Config>
<UPDATE_KEY>AGD</UPDATE_KEY>
<IS_UPDATE_AGENT>NO</IS_UPDATE_AGENT>
<FIXXED_UPDATE_URL>http://193.168.1.88/UnityUpdate</FIXXED_UPDATE_URL>
<SHARE_FOLDER>http://193.168.1.88/UnityUpdate</SHARE_FOLDER>
<ONLY_KEEP_ME>YES</ONLY_KEEP_ME>
<USE_BAT UseBat="NO" StopBat="stop.bat" StartBat="start.bat" AlwaysExecStartBat="Yes" />
</Config>
说明:limit.xml中的LimitKey与UserConfig.xml中的UPDATE_KEY一致。UserConfig.xml中的 USE_BAT为NO。
- 本地updatelist.xml
<?xml version="1.0" encoding="gb2312"?>
<AutoUpdater>
<description>iPOS AutoUpdate</description>
<Updater>
<Url>http://193.168.1.88/UnityUpdate</Url>
<NeedUpdate>YES</NeedUpdate>
<RequiredVersion>9.5.0</RequiredVersion>
<LastUpdateTime>2016-10-20</LastUpdateTime>
<Continue>YES</Continue>
<CheckMd5>NO</CheckMd5>
<SilentMode>NO</SilentMode>
<DownloadTimeout>5000</DownloadTimeout>
</Updater>
<Application applicationId="CoalStart">
<EntryPoint>CoalStart.exe</EntryPoint>
<Location>.</Location>
<Version>2.06.029</Version>
<VersionSort>Professional</VersionSort>
<KillPrograms>CoalStart.exe</KillPrograms>
</Application>
<Files>
<File IsKeyUpt="1" Ver="1.01.002" Name="CoalStart.exe" />
<File IsKeyUpt="1" Ver="2.01.001" Name="BTLFAutoUpdate.exe" />
<File IsKeyUpt="1" Ver="1.01.000" Name="build.rar" />
</Files>
</AutoUpdater>
说明:limit.xml中AutoupdateVersion与本地updatelist.xml中BTLFAutoUpdate的版本一致不需要进行更新。(这里我修改了源码中的升级程序的名称)。升级文件build.rar的版本为1.01.000。
- 服务器端updatelist.xml的配置信息
<?xml version="1.0" encoding="gb2312"?>
<AutoUpdater>
<description>iPOS AutoUpdate</description>
<Updater>
<Url>http://193.168.1.88/UnityUpdate</Url>
<!-- ftp://yishion:yishion@localhost/test/ -->
<NeedUpdate>YES</NeedUpdate>
<RequiredVersion>9.5.0</RequiredVersion>
<LastUpdateTime>2016-10-20</LastUpdateTime>
<Continue>YES</Continue>
<CheckMd5>NO</CheckMd5>
<SilentMode>NO</SilentMode>
<DownloadTimeout>5000</DownloadTimeout>
</Updater>
<Application applicationId="CoalStart">
<EntryPoint>CoalStart.exe</EntryPoint>
<Location>.</Location>
<Version>2.06.029</Version>
<VersionSort>Professional</VersionSort>
<KillPrograms>CoalStart.exe</KillPrograms>
</Application>
<Files>
<File IsKeyUpt="1" Ver="1.01.002" Name="CoalStart.exe" />
<File IsKeyUpt="1" Ver="2.01.001" Name="BTLFAutoUpdate.exe" />
<File IsKeyUpt="1" Ver="1.01.001" Name="build.rar" />
</Files>
</AutoUpdater>
说明:服务器端的升级文件build.rar的版本号为1.01.001比本地文件的版本号高,在打开主程序就会检测需要更新文件。
升级程序文件目录
- 服务器目录结构
- 本地目录结构
Window升级程序改写
-
修改启动的主程序
新建项目CoalStart,在Program.cs类中主程序启动改写为AutoUpdate.InvokeUpdate("CoalStart.exe", "CoalStart", StartMain, args);
-
修改升级程序的名称为BTLFAutoUpdate。
- 修改项目程序集名称
- 修改AutoUpdateLib类库中InvokeUpdate.cs的InvokeUpdate方法,改为Common.ProgramRunned(Common.CombineDir(Common.AppDir, “BTLFAutoUpdate.exe”), true, false);
- 修改AutoUpdateLib类库中的Autoupdating.cs的启动类的名称。
说道这,感觉源码有个小bug ,Autoupdate.exe大小写不一致。这个坑我蹲了好久。
- 修改AutoUpdateGUI项目的FrmUpdate文件中的CopyFile方法将AUTOUPDATE改为BTLFAUTOUPDATE。
- 最后就是配置文件中有关AutoUpdate.exe改为BTLFAutoUpdate.exe就可以了。
-
加入解压缩功能
由于我的升级文件是一个unity的构建之后自己压缩的一个压缩包,在升级下载后需要进行自动的文件解压。改写源码,我使用的更新模式非静默更新,因此找到AutoUpdateGUI项目中的FrmUpdate的类文件中DownUpdateFilePart方法加入如下代码。
lbState.Text = "解压文件中!";
string path = System.IO.Directory.GetCurrentDirectory();
if (!Exists())
{
MessageBox.Show("不支持RAR解压缩");
return;
}
//解a压1
try
{
unCompressRAR(path + "\\setup", path+ "\\temp\\Downloads", "build.rar", false);
//MessageBox.Show("解压完成!");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
lbState.Text = "文件下载并解压完成!";
压缩包解压方法,需要WinRAR.exe电脑上安装一个吧,(为啥不用ICSharpCode.SharpZipLib.dll,zip解压出来少文件,头疼!!)
/// <summary>
/// 解压缩文件t
/// </summary>
/// <param name="unRarPatch">解压缩后的文件所要存放的路径?</param>
/// <param name="rarPatch">rar文件所在的路径</param>
/// <param name="rarName">rar文件名</param>
/// <param name="deleteFlag"></param>
/// <returns></returns>
public static string unCompressRAR(string unRarPatch, string rarPatch, string rarName, bool deleteFlag)
{
try
{
RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe");
string str = key.GetValue("").ToString();
key.Close();
if (!System.IO.Directory.Exists(unRarPatch))
{
System.IO.Directory.CreateDirectory(unRarPatch);
}
string str2 = "x \"" + rarName + "\" \"" + unRarPatch + "\"" + " -y";
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = str;
info.Arguments = str2;
info.WindowStyle = ProcessWindowStyle.Normal;
info.WorkingDirectory = rarPatch;
Process process = new Process();
process.StartInfo = info;
process.Start();
process.WaitForExit();
process.Close();
if (deleteFlag && System.IO.File.Exists(rarPatch + @"\" + rarName))
{
System.IO.File.Delete(rarPatch + @"\" + rarName);
}
}
catch (Exception exception)
{
throw exception;
}
return unRarPatch;
}
/// <summary>
/// 判读是否支持RAR自动解压缩
/// </summary>
/// <returns></returns>
public static bool Exists()
{
return !string.IsNullOrEmpty(Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe").GetValue("").ToString());
}
Winform主程序调用unity程序
Winform升级程序负责文件的检测与unity程序的调用,这时候就需要进行逻辑判断。
FrmMain_Shown中开启线程,延时1秒进行升级程序的检测。存在则隐藏主程序,否则开启unity可执行程序。
开启程序使用引入[DllImport("shell32.dll")] public static extern int ShellExecute(IntPtr hwnd, StringBuilder lpszOp, StringBuilder lpszFile, StringBuilder lpszParams, StringBuilder lpszDir, int FsShowCmd);
调用unity可执行程序 ShellExecute(IntPtr.Zero, new StringBuilder("Open"), new StringBuilder("Coal.exe"), new StringBuilder(""), new StringBuilder(@"" + System.IO.Directory.GetCurrentDirectory() + "\\setup\\build"), 1);
public partial class FrmMain : Form
{
SynchronizationContext _syncContext = null;
public FrmMain()
{
InitializeComponent();
_syncContext = SynchronizationContext.Current;
}
[DllImport("shell32.dll")]
public static extern int ShellExecute(IntPtr hwnd, StringBuilder lpszOp, StringBuilder lpszFile, StringBuilder lpszParams, StringBuilder lpszDir, int FsShowCmd);
/// <summary>
/// 取去除文件扩展名之后的文件字符串
/// </summary>
/// <param name="Prog"></param>
/// <returns></returns>
private static string GetShortExec(string Prog)
{
int Pos = Prog.LastIndexOf(".");
return (Pos > 0 ? Prog.Substring(0, Pos) : Prog);
}
/// <summary>
/// 取程序名(去除路径,扩展名)
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
private static string GetFileNameOnly(string fileName)
{
return GetShortExec(new System.IO.FileInfo(fileName).Name);
}
/// <summary>
/// 返回进程id, c:\windows\system32\Notepad.exe,notepad.exe
/// </summary>
/// <param name="ProgFullName"></param>
/// <param name="Kill"></param>
/// <returns></returns>
public static int ProgramRunned(string ProgName, bool isFullName = false, bool Kill = false)
{
string Prog = isFullName ?
GetFileNameOnly(ProgName)
: GetShortExec(ProgName);
System.Diagnostics.Process[] mProcs =
System.Diagnostics.Process.GetProcessesByName(Prog);
if (0 == (mProcs?.Length ?? 0)) return 0;
int pid = 0;
for (int i = 0, Count = mProcs.Length; i < Count; i++)
{
System.Diagnostics.Process proc = mProcs[i];
bool exists = false;
if (isFullName)
exists = ProgName.Equals(proc.MainModule.FileName, StringComparison.OrdinalIgnoreCase);
else
exists = proc.ProcessName.Equals(ProgName, StringComparison.OrdinalIgnoreCase);
if (exists)
{
pid = proc.Id;
if (Kill)
{
proc.Kill();
}
}
}
return pid;
}
public static void Delay(int milliSecond)
{
int start = Environment.TickCount;
while (Math.Abs(Environment.TickCount - start) < milliSecond)//毫秒
{
continue;
}
}
void checkOpenForm() {
Delay(1000);
int updatecount = ProgramRunned("BTLFAutoUpdate");
if (updatecount > 0)
{
Thread demoThread = new Thread(new ThreadStart(threadMethod));
demoThread.IsBackground = true;
demoThread.Start();//启动线程
}
else
{
ShellExecute(IntPtr.Zero, new StringBuilder("Open"), new StringBuilder("Coal.exe"), new StringBuilder(""), new StringBuilder(@"" + System.IO.Directory.GetCurrentDirectory() + "\\setup\\build"), 1);
ProgramRunned("CoalStart", false, true);
}
}
private void threadMethod()
{
_syncContext.Post(SetFormHide, "");//子线程中通过UI线程上下文更新UI
}
private void SetFormHide(object obj)
{
this.Hide();
}
private void FrmMain_Shown(object sender, EventArgs e)
{
Thread tOpenForm;
tOpenForm = new Thread(checkOpenForm);
tOpenForm.Start();
}
}
最终效果
unity PC端启动升级程序