.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);
posted @ 2010-12-31 11:49  迷茫中的游魂  阅读(4517)  评论(0编辑  收藏  举报