最近考虑.NET下自动升级程序的问题,到网上搜索了一下,现将自动升级程序框架描述及c#demo实现汇总如下。该框架的描述入图所示。
图:自动升级程序框架描述图
1框架的5个部分
- 客户端程序,即需要被升级的程序,在下图中用Client.exe表示
- 服务器端程序,该程序与自动升级程序无关,之所以把它画出来,是为了图的完整性,用Server.exe表示
- 升级服务端程序,该程序负责监听升级客户端程序的连接请求,用UpdateServer.exe表示
- 升级客户端程序,该程序与客户端程序位于相同的主机上,负责与升级服务端程序通信并对客户端程序进行升级操作,用UpdateClient.exe表示
- 升级客户端程序代理,该代理以dll形式提供给客户端程序使用,该代理负责与启动升级客户端程序并传递相关信息给升级客户端程序,用UpdateClientProxy.dll表示
2自动升级程序思路
自动升级程序的设计思路比较清晰,遵循如下步骤进行:
- 客户端程序主程序启动时,使用升级客户端程序代理启动升级客户端程序并将客户端程序的当前版本号和其它相关的信息传递给升级客户端程序,由于该操作是异步并行的,因此,客户端程序不需要等待结果即可继续运行当前版本的程序,如果当升级客户端发现新版本时会以消息的形式通知客户端程序。
- 启动起来的升级客户端程序接收来自客户端程序传递的当前版本号和其它相关信息(比如客户端程序所在目录等),升级客户端程序同升级服务端程序通过socket建立连接,并将当前版本号传递给升级服务端程序,由升级服务端程序告知当前是否有新版本,如果没有,则退出;如果有,从升级服务端程序获得需要更新的文件,保存在本地的临时文件夹中,下载完毕后,通知客户端程序有新版本,如果有必要,升级客户端程序将结束正在运行的客户端程序,将下载得到的新版本文件复制替换到客户端程序所在目录,然后升级客户端程序从新启动客户端程序,实现一次自动更新的过程。
- 升级服务端程序是个常驻内存的服务,实时监听来自升级客户端程序的连接请求,判断是否需要提供给升级客户端程序新版本的程序文件,即,当接收到升级客户端传送的版本号时,升级服务端程序将查询本地的配置文件,比较觉得是否有新版本程序,如果没有则告知升级客户端,如果有,则将已经存储在本地特定目录下的新版本文件通过约定好的协议,传递给升级客户端。
3 C#实现
3.1主要技术细节
3.1.1进程内通信
使用内存映像文件机制实现升级客户端代理向升级客户端程序传递数据。
3.1.2自定义消息
导入win32api,SendMessage
[DllImport(
"
User32.dll
"
, EntryPoint
=
"
SendMessage
"
)]
public static extern int SendMessage (IntPtr hwnd, int wmsg, IntPtr wparam, IntPtr lparam);
public static extern int SendMessage (IntPtr hwnd, int wmsg, IntPtr wparam, IntPtr lparam);
建立消息类
class
Message
... {
public const int USER = 0x0400;
public const int WM_TEST = USER+101;
public const int WM_CLOSE = 0x0010;
}
... {
public const int USER = 0x0400;
public const int WM_TEST = USER+101;
public const int WM_CLOSE = 0x0010;
}
发送自定义消息给指定窗体
public
void
SendMessage()
... {
Win32.SendMessage(hFormHandle,Message.WM_TEST,IntPtr.Zero,IntPtr.Zero);
}
... {
Win32.SendMessage(hFormHandle,Message.WM_TEST,IntPtr.Zero,IntPtr.Zero);
}
窗体接收自定义消息,需要重载WndProc
protected
override
void
WndProc(
ref
Message m)
... {
switch(m.Msg)
...{
case Message.WM_TEST:
//处理该自定消息的代码
break;
}
base.WndProc (ref m);
}
... {
switch(m.Msg)
...{
case Message.WM_TEST:
//处理该自定消息的代码
break;
}
base.WndProc (ref m);
}
3.1.3 C#下代码调用外部程序
System.Diagnostics.Process proc
=
System.Diagnostics.Process.Start(
" d://ConsoleApplication2//bin/Debug//ConsoleApplication2.exe " );
" d://ConsoleApplication2//bin/Debug//ConsoleApplication2.exe " );
3.2演示程序代码
3.2.1升级客户端代理类
using
System;
using System.Threading;
using System.Runtime.InteropServices;
namespace UpdateClient
... {
/**//// <summary>
/// Class1 的摘要说明。
/// </summary>
public class UpdateClientProxy
...{
private static string _exeVersion = "";
private static string _exePhysicalPath = "";
private static IntPtr _exeHandle = IntPtr.Zero;
private static Thread _updateProc = null;
public static void BeginUpdate( string exeVersion,string exePhysicalPath,IntPtr exeHandle )
...{
_exeVersion = exeVersion;
_exePhysicalPath = exePhysicalPath;
_exeHandle = exeHandle;
_updateProc = new Thread(new ThreadStart(UpdateProc));
_updateProc.Start();
}
private static void UpdateProc()
...{
IntPtr hVersion = CreateFileMapping( "exeVersion" );
IntPtr hPhysicalPath = CreateFileMapping( "exePhysicalPath" );
IntPtr hHandle = CreateFileMapping( "exeHandle" );
WriteMapView( hVersion,_exeVersion );
WriteMapView( hPhysicalPath,_exePhysicalPath );
WriteMapView( hHandle,_exeHandle.ToString() );
System.Diagnostics.Process proc = System.Diagnostics.Process.Start(
"d:/ConsoleApplication2/bin/Debug/ConsoleApplication2.exe");
}
private static IntPtr CreateFileMapping( string name )
...{
IntPtr hFileMap = IntPtr.Zero;
hFileMap = Win32.CreateFileMapping(Win32.InvalidHandleValue,
IntPtr.Zero, Win32.PAGE_READWRITE,
0, 0x100, name);
if (hFileMap == IntPtr.Zero)
throw new Exception("CreateFileMapping():无法创建内存映像文件");
return hFileMap;
}
private static void WriteMapView( IntPtr hFileMap,string strValue )
...{
IntPtr address = Win32.MapViewOfFile(hFileMap, Win32.FILE_MAP_WRITE,
0, 0, IntPtr.Zero);
byte[] bs = System.Text.Encoding.GetEncoding("gb2312").GetBytes(strValue);
for(int i=0;i<bs.Length;i++)
...{
Marshal.WriteByte(address,i,bs[i]);
}
Marshal.WriteByte(address,bs.Length,0);
Win32.UnmapViewOfFile(address);
}
private static string ReadMapView( IntPtr hFileMap )
...{
IntPtr address = Win32.MapViewOfFile(hFileMap, Win32.FILE_MAP_WRITE,
0, 0, IntPtr.Zero);
byte[] bs = new byte[0x100];
int i;
for(i=0;i<bs.Length;i++)
...{
byte temp = Marshal.ReadByte(address,i);
if(temp==0)
break;
bs[i] = temp;
}
Win32.UnmapViewOfFile(address);
return System.Text.Encoding.GetEncoding("gb2312").GetString(bs,0,i);
}
}
internal class Win32
...{
[DllImport("User32.dll",EntryPoint="FindWindow")]
public static extern IntPtr FindWindow(string lpClassName,
string lpWindowName);
[DllImport("User32.dll", EntryPoint="SendMessage")]
public static extern int sendmessage (IntPtr hwnd, int wmsg, IntPtr wparam, IntPtr lparam);
[DllImport("User32.dll", EntryPoint="SendMessage")]
public static extern int sendmessage (IntPtr hwnd, int wmsg, IntPtr wparam, string lparam);
public static readonly IntPtr InvalidHandleValue = new IntPtr(-1);
public const UInt32 FILE_MAP_WRITE = 2;
public const UInt32 PAGE_READWRITE = 0x04;
[DllImport("Kernel32")]
public static extern IntPtr CreateFileMapping(IntPtr hFile,
IntPtr pAttributes, UInt32 flProtect,
UInt32 dwMaximumSizeHigh, UInt32 dwMaximumSizeLow, String pName);
[DllImport("Kernel32")]
public static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject,
UInt32 dwDesiredAccess,
UInt32 dwFileOffsetHigh, UInt32 dwFileOffsetLow,
IntPtr dwNumberOfBytesToMap);
[DllImport("Kernel32")]
public static extern Boolean UnmapViewOfFile(IntPtr address);
}
}
using System.Threading;
using System.Runtime.InteropServices;
namespace UpdateClient
... {
/**//// <summary>
/// Class1 的摘要说明。
/// </summary>
public class UpdateClientProxy
...{
private static string _exeVersion = "";
private static string _exePhysicalPath = "";
private static IntPtr _exeHandle = IntPtr.Zero;
private static Thread _updateProc = null;
public static void BeginUpdate( string exeVersion,string exePhysicalPath,IntPtr exeHandle )
...{
_exeVersion = exeVersion;
_exePhysicalPath = exePhysicalPath;
_exeHandle = exeHandle;
_updateProc = new Thread(new ThreadStart(UpdateProc));
_updateProc.Start();
}
private static void UpdateProc()
...{
IntPtr hVersion = CreateFileMapping( "exeVersion" );
IntPtr hPhysicalPath = CreateFileMapping( "exePhysicalPath" );
IntPtr hHandle = CreateFileMapping( "exeHandle" );
WriteMapView( hVersion,_exeVersion );
WriteMapView( hPhysicalPath,_exePhysicalPath );
WriteMapView( hHandle,_exeHandle.ToString() );
System.Diagnostics.Process proc = System.Diagnostics.Process.Start(
"d:/ConsoleApplication2/bin/Debug/ConsoleApplication2.exe");
}
private static IntPtr CreateFileMapping( string name )
...{
IntPtr hFileMap = IntPtr.Zero;
hFileMap = Win32.CreateFileMapping(Win32.InvalidHandleValue,
IntPtr.Zero, Win32.PAGE_READWRITE,
0, 0x100, name);
if (hFileMap == IntPtr.Zero)
throw new Exception("CreateFileMapping():无法创建内存映像文件");
return hFileMap;
}
private static void WriteMapView( IntPtr hFileMap,string strValue )
...{
IntPtr address = Win32.MapViewOfFile(hFileMap, Win32.FILE_MAP_WRITE,
0, 0, IntPtr.Zero);
byte[] bs = System.Text.Encoding.GetEncoding("gb2312").GetBytes(strValue);
for(int i=0;i<bs.Length;i++)
...{
Marshal.WriteByte(address,i,bs[i]);
}
Marshal.WriteByte(address,bs.Length,0);
Win32.UnmapViewOfFile(address);
}
private static string ReadMapView( IntPtr hFileMap )
...{
IntPtr address = Win32.MapViewOfFile(hFileMap, Win32.FILE_MAP_WRITE,
0, 0, IntPtr.Zero);
byte[] bs = new byte[0x100];
int i;
for(i=0;i<bs.Length;i++)
...{
byte temp = Marshal.ReadByte(address,i);
if(temp==0)
break;
bs[i] = temp;
}
Win32.UnmapViewOfFile(address);
return System.Text.Encoding.GetEncoding("gb2312").GetString(bs,0,i);
}
}
internal class Win32
...{
[DllImport("User32.dll",EntryPoint="FindWindow")]
public static extern IntPtr FindWindow(string lpClassName,
string lpWindowName);
[DllImport("User32.dll", EntryPoint="SendMessage")]
public static extern int sendmessage (IntPtr hwnd, int wmsg, IntPtr wparam, IntPtr lparam);
[DllImport("User32.dll", EntryPoint="SendMessage")]
public static extern int sendmessage (IntPtr hwnd, int wmsg, IntPtr wparam, string lparam);
public static readonly IntPtr InvalidHandleValue = new IntPtr(-1);
public const UInt32 FILE_MAP_WRITE = 2;
public const UInt32 PAGE_READWRITE = 0x04;
[DllImport("Kernel32")]
public static extern IntPtr CreateFileMapping(IntPtr hFile,
IntPtr pAttributes, UInt32 flProtect,
UInt32 dwMaximumSizeHigh, UInt32 dwMaximumSizeLow, String pName);
[DllImport("Kernel32")]
public static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject,
UInt32 dwDesiredAccess,
UInt32 dwFileOffsetHigh, UInt32 dwFileOffsetLow,
IntPtr dwNumberOfBytesToMap);
[DllImport("Kernel32")]
public static extern Boolean UnmapViewOfFile(IntPtr address);
}
}
3.2.2模拟的客户端程序
private
void
Form1_Load(
object
sender, System.EventArgs e)
... {
UpdateClient.UpdateClientProxy.BeginUpdate(
"1.0.0.1",
AppDomain.CurrentDomain.BaseDirectory+AppDomain.CurrentDomain.FriendlyName,
this.Handle
);
}
protected override void WndProc( ref Message m)
... {
switch(m.Msg)
...{
case Message.WM_TEST:
new System.Threading.Thread(new System.Threading.ThreadStart(DemoProc)).Start();
break;
}
base.WndProc (ref m);
}
private void DemoProc()
... {
this.textBox1.Text = "升级程序检测到有新版本,即将关闭应用程序并进行升级操作!";
System.Threading.Thread.Sleep(5000);
this.Close();
}
... {
UpdateClient.UpdateClientProxy.BeginUpdate(
"1.0.0.1",
AppDomain.CurrentDomain.BaseDirectory+AppDomain.CurrentDomain.FriendlyName,
this.Handle
);
}
protected override void WndProc( ref Message m)
... {
switch(m.Msg)
...{
case Message.WM_TEST:
new System.Threading.Thread(new System.Threading.ThreadStart(DemoProc)).Start();
break;
}
base.WndProc (ref m);
}
private void DemoProc()
... {
this.textBox1.Text = "升级程序检测到有新版本,即将关闭应用程序并进行升级操作!";
System.Threading.Thread.Sleep(5000);
this.Close();
}
3.2.3模拟的升级客户端程序
[STAThread]
static void Main( string [] args)
... {
Console.WriteLine("模拟的升级客户端程序已经被启动");
IntPtr hVersion = FileMapping.CreateFileMapping( "exeVersion" );
IntPtr hPhysicalPath = FileMapping.CreateFileMapping( "exePhysicalPath" );
IntPtr hHandle = FileMapping.CreateFileMapping( "exeHandle" );
string _version = FileMapping.ReadMapView( hVersion );
string _physicalpath = FileMapping.ReadMapView( hPhysicalPath );
string _handle = FileMapping.ReadMapView( hHandle );
if(
_version!=null&&_version!="" &&
_physicalpath!=null&&_physicalpath!="" &&
_handle!=null&&_handle!="" )
...{
Console.WriteLine("接收到程序传送的版本号:{0}",_version);
Console.WriteLine("接收到程序传送的物理路径:{0}",_physicalpath);
Console.WriteLine("正在连接升级服务器......");
Console.WriteLine("连接到升级服务器.");
Console.WriteLine("获取升级列表");
Console.WriteLine("正在下载文件a.dll");
Console.WriteLine("正在下载文件b.dll");
Console.WriteLine("正在下载文件abc.xml");
Console.WriteLine("已经成功下载全部所需的更新文件");
Console.WriteLine("通知主程序关闭自己");
Win32.sendmessage(new IntPtr(Convert.ToInt32(_handle)),Message.WM_TEST,IntPtr.Zero,IntPtr.Zero);
System.Threading.Thread.Sleep(5000);
Console.WriteLine("主程序已经关闭,升级客户端程序将进行文件替换");
Console.WriteLine("正在替换a.dll");
Console.WriteLine("正在替换b.dll");
Console.WriteLine("正在替换abc.xml");
Console.WriteLine("所有更新文件全部替换成功");
Console.WriteLine("升级客户端程序将重新启动主程序");
System.Diagnostics.Process.Start(_physicalpath);
}
}
static void Main( string [] args)
... {
Console.WriteLine("模拟的升级客户端程序已经被启动");
IntPtr hVersion = FileMapping.CreateFileMapping( "exeVersion" );
IntPtr hPhysicalPath = FileMapping.CreateFileMapping( "exePhysicalPath" );
IntPtr hHandle = FileMapping.CreateFileMapping( "exeHandle" );
string _version = FileMapping.ReadMapView( hVersion );
string _physicalpath = FileMapping.ReadMapView( hPhysicalPath );
string _handle = FileMapping.ReadMapView( hHandle );
if(
_version!=null&&_version!="" &&
_physicalpath!=null&&_physicalpath!="" &&
_handle!=null&&_handle!="" )
...{
Console.WriteLine("接收到程序传送的版本号:{0}",_version);
Console.WriteLine("接收到程序传送的物理路径:{0}",_physicalpath);
Console.WriteLine("正在连接升级服务器......");
Console.WriteLine("连接到升级服务器.");
Console.WriteLine("获取升级列表");
Console.WriteLine("正在下载文件a.dll");
Console.WriteLine("正在下载文件b.dll");
Console.WriteLine("正在下载文件abc.xml");
Console.WriteLine("已经成功下载全部所需的更新文件");
Console.WriteLine("通知主程序关闭自己");
Win32.sendmessage(new IntPtr(Convert.ToInt32(_handle)),Message.WM_TEST,IntPtr.Zero,IntPtr.Zero);
System.Threading.Thread.Sleep(5000);
Console.WriteLine("主程序已经关闭,升级客户端程序将进行文件替换");
Console.WriteLine("正在替换a.dll");
Console.WriteLine("正在替换b.dll");
Console.WriteLine("正在替换abc.xml");
Console.WriteLine("所有更新文件全部替换成功");
Console.WriteLine("升级客户端程序将重新启动主程序");
System.Diagnostics.Process.Start(_physicalpath);
}
}