一切福田,不離方寸,從心而覓,感無不通。

.net windows服务程序编写总结

1、在.net中,windows服务的实现类必需继承于System.ServiceProcess.ServiceBase
public partial class myService : ServiceBase
{
 
}
2、在windows服务的实现类的构造函数中进行必要的初始化工作,如设置系统标识服务的简短名称等。
public partial class myService : ServiceBase
{
public SqlBackupService()
{
ServiceName = "Myservice";
AutoLog = false;
CanStop = true;
}
}
3、重写OnStar和OnStop函数,在OnStar里实现具体功能,OnStop中释放OnStat中创建的资源。
public partial class myService : ServiceBase
{
private int tickcount = 0;
private System.Timers.Timer t = null;
 
public SqlBackupService()
{
ServiceName = "Myservice";
AutoLog = false;
CanStop = true;
}
 
protected override void OnStart(string[] args)
{
Console.WriteLine("Myservice start …");
//建立定时器
t = new System.Timers.Timer(10000);
t.AutoReset = true; //每隔10000毫秒触发一次
t.Elapsed += new System.Timers.ElapsedEventHandler(myWork);
t.Start();
}
 
protected override void OnStop()
{
t.Stop();
t.Close();
Console.WriteLine("end(stop) the MYservice…");
}
}
对服务首次调用“开始”时,可执行文件调用 ServiceBase 派生类的构造函数。在构造函数执行之后将立即调用 OnStart 命令处理方法。在
 
服务首次加载之后,构造函数不会再次执行,因此有必要将构造函数执行的处理和 OnStart 执行的处理分开。可以由 OnStop 释放的任何资源
 
都应在 OnStart 中创建。如果服务在 OnStop 释放资源后再次启动,那么,在构造函数中创建资源会妨碍这些资源的正确创建。
 
.NET Framework里面提供了三种Timer:
System.Windows.Forms.Timer
System.Timers.Timer
System.Threading.Timer
在服务中使用的定时器是System.Timers.Timer或System.Threading.Timer,

不是System.Windows.Forms.Timer。VS缺省提供的是

System.Windows.Forms.Timer。

 
System.Windows.Forms.Timer的Timer Start之后,定时(按设定的Interval)调用挂接在Tick事件上的EvnetHandler。在这种Timer的
 
EventHandler中可以直接获取和修改UI元素而不会出现问题--因为这种Timer实际上就是在UI线程自身上进行调用的。也正是因为这个原因,导
 
致了在Timer的EventHandler里面进行长时间的阻塞调用,将会阻塞界面响应的后果。
 
System.Timers.Timer是在.NET的Thread Pool上面运行的,而不是直接在UI Thread上面运行,所以在这种Timer的EventHandler里面进行耗时
 
较长的计算不会导致UI失去响应。但是这里有两个地方需要注意:
a)因为一般来说System.Timers.Timer不是运行在UI Thread上面的,所以如果要在这种Timer的EventHandler里面更新UI元素的话,需要进行
 
一次线程切换,在WinForm开发中一般通过UI元素的Invoke方法完成:
private delegate void UpdateUICallBack();
private void UpdateUI()
{
 
}
this.Invoke(new UpdateUICallBack(UpdateUI));
b)System.Timers.Timer有一个Property:SynchronizingObject 。如果设置了这个Property(一般是某个Form),那么之后对Timer挂接的
 
EventHandler的调用将会在创建这个UI元素的线程上进行(一般来说就是UI线程)。值得注意的是,如果你通过WinForm设计器把
 
System.Timers.Timer拖放到Form上,那么这个Property将会自动被设置。此时这种Timer就和System.Windows.Forms.Timer的效果一样:长调
 
用将会阻塞界面。
 
System.Threading.Timer 是一个简单的轻量计时器,它使用回调方法并由线程池线程提供服务。不建议将其用于 Windows 窗体,因为其回调
 
不在用户界面线程上进行。System.Windows.Forms.Timer 是用于 Windows 窗体的更佳选择。要获取基于服务器的计时器功能,可以考虑使用
 
System.Timers.Timer,它可以引发事件并具有其他功能。
 
4、windows服务的实现类写好后,若要安装服务,要从Installer 类继承的项目安装程序类,并将该类的 RunInstallerAttribute 属性设置为
 
true。在项目中,为每个服务应用程序创建一个 ServiceProcessInstaller 实例,并为应用程序中的每个服务创建一个 ServiceInstaller 实
 
例。在项目安装程序类构造函数中,使用 ServiceProcessInstaller 和 ServiceInstaller 实例设置服务的安装属性,并将这些实例添加到
 
Installers 集合中。
 
[RunInstallerAttribute(true)]
public class ProjectInstaller : Installer
{
private ServiceInstaller serviceInstaller;
private ServiceProcessInstaller processInstaller;
 
public ProjectInstaller()
{
AfterInstall += new InstallEventHandler(myAfterInstall);
processInstaller = new ServiceProcessInstaller();
serviceInstaller = new ServiceInstaller();
 
// Service will run under system account
processInstaller.Account = ServiceAccount.LocalSystem;
 
// Service will have Start Type of Manual
serviceInstaller.StartType = ServiceStartMode.Manual;
 
serviceInstaller.ServiceName = Myservice.serviceName;
 
Installers.Add(serviceInstaller);
Installers.Add(processInstaller);
}
 
ServiceInstaller实例来安装一个类,该类扩展 ServiceBase 来实现服务。在安装服务应用程序时由安装实用工具调用该类。一个可执行文件
 
可以包含多项服务,但对每项服务均必须包含一个单独的 ServiceInstaller。ServiceInstaller 实例在系统中注册服务。安装程序还将每项
 
服务与一个事件日志关联,您可以使用该日志记录服务命令。可执行文件中的 main() 函数定义哪些服务应该运行。服务的当前工作目录是系
 
统目录,而不是可执行文件所位于的目录。
 
ServiceInstaller 执行特定于其所关联服务的操作。它由安装实用工具用来将与服务关联的注册表值写入
 
KEY_LOCAL_MACHINE\System\CurrentControlSet\Services 注册表项内的子项。服务由它在该子键内的“服务名”(ServiceName) 标识。该子
 
键还包含服务所属的可执行文件或 .dll 的名称。
 
对于从 Installer 类派生的所有类,Install 和 Uninstall 方法中 Installers 集合的状态必须相同。但是,如果在自定义安装程序类构造
 
函数中将安装程序实例添加到 Installers 集合,可以避免在 Install 和 Uninstall 方法中对集合进行维护。安装实用工具在被调用时将查
 
找 RunInstallerAttribute 属性。如果该属性为 true,则实用工具将安装添加到 Installers 集合中、与项目安装程序关联的所有服务。如
 
果 RunInstallerAttribute 为 false 或不存在,则安装实用工具忽略该项目安装程序。与项目安装类关联的 ServiceProcessInstaller 会安
 
装该项目中的所有 ServiceInstaller 实例所共有的信息。如果该服务包含有别于安装项目中的其他服务的内容,此方法就会安装这个服务特
 
定信息。注意:ServiceName 应与从 ServiceBase 派生的类的 ServiceBase..::.ServiceName 相同,这一点至关重要。
 
5、给服务添加命令行参数,需要在ServiceInstaller的AfterInstall事件中添加修改注册表的代码。
private void myAfterInstall(object sender, InstallEventArgs e)
{
try
{
RegistryKey regKey = Registry.LocalMachine.OpenSubKey
(@"SYSTEM\CurrentControlSet\Services\" + serviceInstaller.ServiceName, true);
object value = regKey.GetValue("ImagePath");
if (value != null)
{
string imagePath = value.ToString();
string sParam = GetArrayString(Program.GetParamArray(globalInstance.cmdArgumennt));
regKey.SetValue("ImagePath", imagePath + " "+sParam);
regKey.Flush();
}
regKey.Close();
}
catch
{
 
}
}
 
6、运行服务。在主程序的入口函数Main中执行:
static void Main(string[] args )
{
#region 启动服务
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new SqlBackupService()
};
ServiceBase.Run(ServicesToRun);
#endregion
}
 
7、以Console方式运行服务:
 
static void Main(string[] args )
{
#region 以控制台方式运行
if (globalInstance.cmdArgumennt.bRunAsConsole)
{
Console.WriteLine("MyService Run As Console Begin …");
MyService svc = new MyService();
svc.RunAsConsole(null);
Console.ReadLine();
Console.WriteLine("MyService Stop As Console …");
svc.StopAsConsole();
return;
}
#endregion
 
#region 启动服务
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new SqlBackupService()
};
ServiceBase.Run(ServicesToRun);
#endregion
}
 
8、Windows服务有用户界面,能够实现与用户交互的形式。
普通的service程序不能和用户交互是因为其desktop和正常的desktop不同。只要把service的desktop切换成正常desktop,就可以交互了。
Windows 服务应用程序在不同于登录用户的交互区域的窗口区域中运行。窗口区域是包含剪贴板、一组全局原子和一组桌面对象的安全对象。
 
由于 Windows 服务的区域不是交互区域,因此 Windows 服务应用程序中引发的对话框将是不可见的,并且可能导致程序停止响应。同样,错
 
误信息应记录在 Windows 事件日志中,而不是在用户界面中引发。
.NET Framework 支持的 Windows 服务类不支持与交互区域(即登录用户)进行交互。同时,.NET Framework 不包含表示区域和桌面的类。如
 
果 Windows 服务必须与其他区域进行交互,则需要访问非托管的 Windows API。 即using System.Runtime.InteropServices。
OnStart中不能直接弹出一个Form,这样做是没有任何反应的。可以在这个方法里运行一个线程。该线程需要访问窗口区域对象或桌面对象。
protected override void OnStart(string[] args)
{
threadForm = new Thread(new ThreadStart(FormShow));
threadForm.Start();
}
 
void FormShow()
{
 
//Ensure connection to service window station and desktop, and
//save their handles.
GetDesktopWindow();
IntPtr hwinstaSave = GetProcessWindowStation();
IntPtr dwThreadId = GetCurrentThreadId();
IntPtr hdeskSave = GetThreadDesktop(dwThreadId);
 
//Impersonate the client and connect to the User’s
//window station and desktop.
//RpcImpersonateClient(h);
IntPtr hwinstaUser = OpenWindowStation("WinSta0", false, 33554432);
if (hwinstaUser == IntPtr.Zero)
{
RpcRevertToSelf();
return;
}
 
SetProcessWindowStation(hwinstaUser);
IntPtr hdeskUser = OpenDesktop("Default", 0, false, 33554432);
RpcRevertToSelf();
if (hdeskUser == IntPtr.Zero)
{
SetProcessWindowStation(hwinstaSave);
CloseWindowStation(hwinstaUser);
return;
}
SetThreadDesktop(hdeskUser);
 
IntPtr dwGuiThreadId = dwThreadId;
 
Form1 f = new Form1(); //此FORM1可以带notifyIcon,可以显示在托盘里,用户可点击托盘图标进行设置
System.Windows.Forms.Application.Run(f);
 
 
dwGuiThreadId = IntPtr.Zero;
SetThreadDesktop(hdeskSave);
SetProcessWindowStation(hwinstaSave);
CloseDesktop(hdeskUser);
CloseWindowStation(hwinstaUser);
}
 
protected override void OnStop()
{
if (threadForm != null)
{
if (threadForm.IsAlive)
{
threadForm.Abort();
threadForm = null;
}
}
}
 
[DllImport("user32.dll")]
static extern int GetDesktopWindow();
 
[DllImport("user32.dll")]
static extern IntPtr GetProcessWindowStation();
 
[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentThreadId();
 
[DllImport("user32.dll")]
static extern IntPtr GetThreadDesktop(IntPtr dwThread);
 
[DllImport("user32.dll")]
static extern IntPtr OpenWindowStation(string a, bool b, int c);
 
[DllImport("user32.dll")]
static extern IntPtr OpenDesktop(string lpszDesktop, uint dwFlags,
bool fInherit, uint dwDesiredAccess);
 
[DllImport("user32.dll")]
static extern IntPtr CloseDesktop(IntPtr p);
 
[DllImport("rpcrt4.dll", SetLastError = true)]
static extern IntPtr RpcImpersonateClient(int i);
 
[DllImport("rpcrt4.dll", SetLastError = true)]
static extern IntPtr RpcRevertToSelf();
 
[DllImport("user32.dll")]
static extern IntPtr SetThreadDesktop(IntPtr a);
 
[DllImport("user32.dll")]
static extern IntPtr SetProcessWindowStation(IntPtr a);
 
[DllImport("user32.dll")]
static extern IntPtr CloseWindowStation(IntPtr a);