您的位置:首页 > 编程语言 > C#

用C#创建简单的服务程序(Service)及其编译自动化

2011-04-24 16:09 423 查看
本文描述的内容主要涵盖两个方面:
1. 如何用C#创建一个简单的服务程序
2. 如何让服务在编译的时候自动安装、卸载、启动、停止
3. 可能会遇到的问题
本文操作环境:Win7 + Visual Studio 2008
请注意:Visual Studio 2008需要以管理员权限启动,因为我们的命令都需要这个权限。
1. 对于前一个问题,可以参考以下几篇文章:
MSDN:Visual Studio 2010 如何:创建 Windows 服务
用Visual C#创建Windows服务程序
用C#创建Windows服务(Windows Services)
如何用.NET创建Windows服务
这几篇文章主要描述了服务程序的一些概念和step by step的操作。对于不了解Service的同学来说应该,应该会有一个比较初步的了解。 但是,用Visual C#创建Windows服务程序 这篇文章的例子比较久远,我下载编译了该例子,并安装、启动了编译出来的服务,不知为 何会同时有两个服务在任务管理器中出现。所以,这篇文章的代码没有进行仔细的研究,而是自己建立了一个工程,一步一步的做下来。
2. 如何让服务在编译的时候自动安装、卸载、启动、停止?
首先,需要了解一下如何手动的进行服务的安装和卸载,它用到VS自带的一个工具:InstallUtil.exe。服务的启动和停止用到了net命令或者使用sc命令。
操作:用管理员权限打开Visual Studio 2008 Command Prompt(VS自带的命令行)->进入到编译生成可执行文件的目录
安装:输入命令 InstallUtil 服务名称
卸载:输入命令 InstallUtil /u 服务名称
启动:输入命令 net start 服务名称 或者 sc start 服务名称
停止:输入命令 net stop 服务名称 或者 sc stop 服务名称
我们的服务程序编译OK后,每次都需要重复如上的操作,相当繁琐,也大大降低了我们开发的效率,如果我们能让这些操作完全自动化,在编译完成后就能直接使用,这是一件相当美妙的事情。
最开始,我参照了.Net下的Windows服务程序开发指南.这篇文章,文章提供了一个思路,将如下几行命令放在:工程属性->Build Event->Post-build event command line里面,这样编译的时候就能够进行一些上述操作。

1:  net stop ServiceName


2:  "%SystemRoot%/Microsoft.NET/Framework/v2.0.50727/InstallUtil.exe" /u $(ProjectDir)bin/$(ConfigurationName)/$(TargetFileName)


3:  "%SystemRoot%/Microsoft.NET/Framework/v2.0.50727/InstallUtil.exe"  $(ProjectDir)bin/$(ConfigurationName)/$(TargetFileName)


4:  net start ServiceName.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }




但是,该方法还是不够完美,原因有二:


一是在第一次编译后该服务后,由于本地机器从来没有安装过ServiceName这个服务,此时就会报错, 服务名无效。请键入 NET HELPMSG 2185 以获得更多的帮助。因此,第一次使用时,要手动安装。

二是在第二次将要编译的时候,如果该服务没有停掉(即进程并没有结束),那么此时也会报错,因为没有权限修改这个可执行文件,它正在被另外一个进程占用。

于是,我稍微改进了一下,将1,2句放到Per-build event command line里,3,4句放到Post-build event command line,虽然或多或少的有一些改进,但不是不如意。




最根本的原因其实在于自己写的CMD实在是太弱了,而且命令行本身对于返回值的支持貌似不怎么好。因此,最好是有一种能够支持复杂逻辑的脚本,并能够很好的处理返回值。 显然,Windows Powershell是最好的选择。 但是,要使用Win7自带的PowerShell来自动化处理我们的编译,还需要解决两个问题。


1. PowerShell本身是否支持InstallUtil, sc等等命令,如果不支持,是否有对应的替代方法?

2. 能否将PowerShell脚本嵌入到Per-build or Post-build event command line中去,如果不能,是否有对应的替代方法?

第一个问题,我的答案是不能,至少我没调查出来,但是我们可以设置相关的环境变量到PowerShell里面去,让它能够支持这些命令。参考文章:Visual Studio 2008 PowerShell


第二个问题,很遗憾,我还是没有调查出来,但是我们可以在Per-build or Post-build event command line调用PowerShell命令执行其脚本文件。参考PowerShell的Help文档。




OK,先看脚本文件的代码:






1:  function Get-Batchfile ($file) {


2:      $cmd = "`"$file`" & set"


3:      cmd /c $cmd | Foreach-Object {


4:          $p, $v = $_.split('=')


5:          Set-Item -path env:$p -value $v


6:      }


7:  }


8:   


9:  function VsVars32()


10:  {


11:      $vs90comntools = (Get-ChildItem env:VS90COMNTOOLS).Value


12:      $batchFile = [System.IO.Path]::Combine($vs90comntools, "vsvars32.bat")


13:      Get-Batchfile $BatchFile


14:


15:      [System.Console]::Title = "Visual Studio 2008 Windows PowerShell"


16:  }


17:   


18:  function BuildEvent([Boolean]$isPreBuild, $ServiceName, $ServiceFullPath)


19:  {


20:      #如果是编译前($isPreBuild==True),


21:      #    则先获取所有服务,再遍历所有服务,如果找到该服务,且该服务正处于运行状态,则先Stop该服务,然后再UnInstall该服务


22:      #如果是编译后($isPreBuild==False),


23:      #    则直接先安装服务,再Start该服务


24:      if ($isPreBuild)


25:      {


26:          $Rts = Get-Service


27:          foreach ($Item in $Rts)


28:          {


29:              if ($Item.Name -eq $ServiceName -and $Item.Status -eq "Running")


30:              {


31:                  Stop-Service $ServiceName


32:                  echo "Stop $ServiceName Success!"


33:                  InstallUtil /u $ServiceFullPath


34:                  echo "UnInstall $ServiceName Success!"


35:              }


36:          }


37:      }


38:      else


39:      {


40:          InstallUtil $ServiceFullPath


41:          echo "Install $ServiceName Success!"


42:          Start-Service $ServiceName


43:          echo "Start $ServiceName Success!"


44:      }


45:  }


46:   


47:  ############################################################################


48:  ############################################################################


49:   


50:  #程序从这里入口


51:  "Visual Studio 2008 Windows PowerShell"


52:  ""


53:  #将VS的相关环境变量设置到Windows PowerShell中去


54:  #Set Visual Studio 2008 Command Prompt's environment variable into Windows PowerShell


55:  VsVars32


56:   


57:  #解析从命令行传过来的参数:


58:  #    参数1:是否是Pre-Build,需要传入true/false的字符串


59:  #    参数2:服务名称


60:  #    参数3:服务对应的可执行文件全路径(绝对路径)


61:  if ($args.Count -eq 3)


62:  {


63:      if ($args[0] -ne "true" -and $args[0] -ne "false")


64:      {


65:          echo "Argument error!!! Please check it."


66:          exit


67:      }


68:


69:      echo "Pre-Build or Post-Build event command line[True:Pre-Build, False:Post-Build]:" $args[0]


70:      echo "Service Name:"      $args[1]


71:      echo "Service Full Path:" $args[2]


72:


73:      [Boolean]$isPreBuild = $true


74:      if ($args[0] -eq "false")


75:      {


76:          [Boolean]$isPreBuild = $false


77:      }


78:


79:      #调用处理函数


80:      BuildEvent $isPreBuild $args[1] $args[2]


81:  }



.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }



然后,我们需要让VS能够调用该脚本,并传入相应的参数即可:

在Per-build event command line中,添加:  powershell -File $(ProjectDir)Pre-BuildAndPost-BuildService.ps1 true $(TargetName) $(TargetPath)

在Post-build event command line中,添加: powershell -File $(ProjectDir)Pre-BuildAndPost-BuildService.ps1 false $(TargetName) $(TargetPath)

3. 可能会遇到的问题

3.1 如何调试:


     ①打log

     ②直接Attach到进程


     需要注意的是,Windows 服务管理器将所有尝试启动服务的时间限制在 30 秒内,如果想要调试OnStart方法,我的做法比较山寨,是在OnStart的时候加入一个Thread.Sleep(20000),然后迅速启动服务,迅速Attach到服务进程。MS推荐使用临时服务加载真正要处理的服务,请参考:如何:调试 Windows 服务应用程序


3.2 在计算机->管理->服务中,我能够看到对应的服务,但为什么状态为disabled(禁用),无法启动,此时,如果启动该服务,则会出现错误对话框:


     The specified service has been marked for deletion(指定服务已标记为删除)。那么,如何删掉该服务?其实,此时你什么都不用做,关掉这个服务的窗口,关掉其控制命令行,再重新打开服务管理窗口,发现一切正常。实在不行,只有用杀手锏----重启电脑。


3.3 如何删除一个服务:①sc delete ServiceName ②在注册表HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services下找到对应的服务,删除即可。




貌似用Windows Live Writter发布的日志还算不错。但是没有摘要啊。。。郁闷。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: