如何编写windows服务程序

如何编写windows服务程序,第1张

首先Microsoft Windows 服务(即,以前的NT 服务)使您能够创建在它们自己的Windows 会话中可长时间运行的可执行应用程序。这些服务可以在计算机启动时自动启动,可以暂停和重新启动而且不显示任何用户界面。这使服务非常适合在服务器上使用,或任何时候,为了不影响在同一台计算机上工作的其他用户,需要长时间运行功能时使用。还可以在不同于登录用户的特定用户帐户或默认计算机帐户的安全上下文中运行服务。

服务是有状态的,当我们使用windows自带的服务管理程序sc.exe查看服务状态时可以显示服务的当前状态,这个状态是由我们在程序代码中进行控制的。你最好在服务初始化的时候将服务设置为SERVICE_START_PENDING,当初始化完毕时设为SERVICE_RUNNING,这些状态是系统自定义的状态,可通过msdn查看其他状态。这个状态信息你会在sc.exe中看到。

在编写windows服务程序过程中你需要关注的函数有:

1.首先是main函数,由于windows服务不需要界面,所以大部分程序为win32控制台应用程序,所以程序主函数为main 而不是WinMain()。在主函数要做的主要工作就是初始化一个SERVICE_TABLE_ENTRY 分派表结构体,然后调用StartServiceCtrlDispatcher()这将把调用进程的主线程转换为控制分派器。该分派器启动一个新线程,该线程运行分派表中对应于你的服务的ServiceMain()函数。ServiceMain()函数将在下面提到。

此过程示例代码如下:

SERVICE_TABLE_ENTRY entrytable[2]

entrytable[0].lpServiceName="testservice"

entrytable[0].lpServiceProc=(LPSERVICE_MAIN_FUNCTION)ServiceMain

entrytable[1].lpServiceName=NULL

entrytable[1].lpServiceProc=NULL

StartServiceCtrlDispatcher(entrytable)

在这之后系统将自动创建一个线程去执行ServiceMain函数的内容,你应该将你要执行的任务在ServiceMain中循环,这样服务就开始运行了。

2.ServiceMain函数为void WINAPI ServiceMain(int argc, char** argv)格式的函数,函数名字可以任意定义。它的作用就是:将你需要执行的任务放到该函数中循环执行即可。这就是服务程序的工作函数。在ServiceMain执行你的任务前,需要给SERVICE_TABLE_ENTRY 分派表结构体进行赋值,注意由于此时服务还没有开始执行你的任务所以我们将服务的状态设置为SERVICE_START_PENDING,即正在初始化。我们进行如下赋值:

servicestatus.dwServiceType = SERVICE_WIN32

servicestatus.dwCurrentState = SERVICE_START_PENDING

servicestatus.dwControlsAccepted=SERVICE_ACCEPT_SHUTDOWN|SERVICE_ACCEPT_STOP

//在本例中只接受系统关机和停止服务两种控制命令

servicestatus.dwWin32ExitCode = 0

servicestatus.dwServiceSpecificExitCode = 0

servicestatus.dwCheckPoint = 0

servicestatus.dwWaitHint = 0

hstatus = ::RegisterServiceCtrlHandler("testservice", CtrlHandler)

CtrlHandler为void WINAPI CtrlHandler(DWORD request)型的函数,函数名字可以任意设定。将在下一点讲到。

Hstatus为SERVICE_STATUS_HANDLE类型的全局变量。当需要改变服务状态时SetServiceStatus()函数需要它做为参数来标识一个服务。

3. void WINAPI CtrlHandler(DWORD request),函数的主要功能是,接收系统传递的控制命令,比如当你通过sc.exe关闭服务时,该函数会收到SERVICE_CONTROL_STOP消息,你就可以对服务进行必要的管理。在本例子程序中就只接收SERVICE_ACCEPT_SHUTDOWN和SERVICE_ACCEPT_STOP消息,这是通过前面给servicestatus赋值设定的。

1、在VS2010创建windows service工程,文件--新建--项目--windows服务,名称:TestWindowsService,设置server1的ServiceName属性为TestWindowsService2、由于服务是要安装的,所以它运行的时候就需要一个安装类Installer将服务安装到计算机,新建一个后台服务安装类Install继承自Installer,安 装初始化的时候是以容器进行安装的,所以还要建立ServiceProcessInstaller和ServiceInstaller服务信息组件添加到 容器安装,在Install类增加如下代码: using Systemusing System.Collections.Generic//using System.Linqusing System.Textusing System.Configuration.Installusing System.ComponentModel namespace TestWindowsService { [RunInstaller(true)]//注意这里的属性Attribute设置 class Install : Installer { private System.ComponentModel.IContainer components = nullprivate System.ServiceProcess.ServiceProcessInstaller spInstallerprivate System.ServiceProcess.ServiceInstaller sInstallerprivate void InitializeComponent() { components = new System.ComponentModel.Container() // 创建ServiceProcessInstaller对象和ServiceInstaller对象 this.spInstaller = new System.ServiceProcess.ServiceProcessInstaller()this.sInstaller = new System.ServiceProcess.ServiceInstaller() // 设定ServiceProcessInstaller对象的帐号、用户名和密码等信息 this.spInstaller.Account = System.ServiceProcess.ServiceAccount.LocalSystemthis.spInstaller.Username = nullthis.spInstaller.Password = null // 设定服务名称 this.sInstaller.ServiceName = "TestWindowsService"sInstaller.DisplayName = "后台Windows服务"sInstaller.Description = "一个后台Windows运行的服务" // 设定服务的启动方式 this.sInstaller.StartType = System.ServiceProcess.ServiceStartMode.Automaticthis.Installers.AddRange(new System.Configuration.Install.Installer[] { this.spInstaller, this.sInstaller }) } } } 3、点击Service1.cs的视图设计器,在“单击此处切换到代码视图”上右键--添加安装程序,就出现了ProjectInstaller.cs。 点击ProjectInstaller.cs--选择serviceInstaller1--设置ServiceName为TestWindowsService,StartType为Automatic。选择serviceProcessInstaller1--设置account为LocalSystem 点击运行--出现:“无法从命令行或调试器启动服务” 4、一个空的服务基本上搞定了,只是业务没有,就看怎么在服务器上安装部署了.C#写的Windows后台服务不能直接安装,需要借助.NET Framework里面的InstallUtil.exe安装工具安装,我们可以做成一个执行CMD命令的文件BAT文件来安装启动它,命令如下: %windir%\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe �%\TestWindowsService.exe安装完成以后,我们可以在我的电脑管理服务里面看到才安装上的后台服务. 5、卸载:找到开始--运行--regedit,找到目录下的TestWindowsService: 删除后重新启动就可以了。 6、C#获取服务程序路径string PathBase = System.AppDomain.CurrentDomain.BaseDirectory //-------------------另一种安装部署时添加注册表信息实现开机自启动------------------------ 使用VS自带的打包模块可以很方便的对项目进行打包部署,同时我们也可以在安装部署时 *** 作注册表实现开机启动软件。具体实现如下: 创建安装部署这部分就不用说了,添加安装部署项目后,鼠标右键安装项目->视图->注册表, 要使软件在开机就运行,可以在HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run中 添加键值保存软件目录。在这里我们依次添加以上的项,然后在Run中添加键值,键名可以自己起,value要填软件的物理路径。物理路径是客户在部署确定 的,我们如何获取呢?这里我们可以使用[TARGETDIR]获取客户选择的路径,在加上软件的启动文件名称。比如软件启动文件的名称是 Client.exe,那么Value的值就为:[TARGETDIR]Client.exe.生成安装项目。找到bin目录下的setup.exe文件 运行,安装结束后我们可以在注册表中找到相应的键值。重启电脑系统就会自动运行我们设置的软件。

环境: vs2010,多字符集

以下是一些基本知识, 不得不耐下心去理解,这对程序理解和编写非常用帮助.

首先Microsoft Windows 服务(即,以前的 NT 服务)使您能够创建在它们自己的Windows 会话中可长时间运行的可执行应用程序。这些服务可以在计算机启动时自动启动,可以暂停和重新启动而且不显示任何用户界面。这使服务非常适合在服务器上使用,或任何时候,为了不影响在同一台计算机上工作的其他用户,需要长时间运行功能时使用。还可以在不同于登录用户的特定用户帐户或默认计算机帐户的安全上下文中运行服务。

服务是有状态的,当我们使用windows自带的服务管理程序sc.exe 查看服务状态时可以显示服务的当前状态,这个状态是由我们在程序代码中进行控制的。你最好在服务初始化的时候将服务设置为SERVICE_START_PENDING,当初始化完毕时设为SERVICE_RUNNING,这些状

态是系统自定义的状态,可通过msdn查看其他状态。这个状态信息你会在sc.exe中看到。

在编写windows服务程序过程中你需要关注的函数有:

1.首先是main函数,由于windows服务不需要界面,所以大部分程序为win32控制台应用程序,所以程序主函数为main 而不是WinMain()。在主函数要做的主要工作就是初始化一个SERVICE_TABLE_ENTRY 分派表结构体,然后调用StartServiceCtrlDispatcher()这将把调用进程的主线程转换为控制分派器。该分派器启动一个新线程,该线程运行分派表中对应于你的服务的ServiceMain()函数。ServiceMain()函数将在下面提到。

此过程示例代码如下:

SERVICE_TABLE_ENTRY entrytable[2]

entrytable[0].lpServiceName="testservice"

entrytable[0].lpServiceProc=(LPSERVICE_MAIN_FUNCTION)ServiceMain

entrytable[1].lpServiceName=NULL

entrytable[1].lpServiceProc=NULL

StartServiceCtrlDispatcher(entrytable)

在这之后系统将自动创建一个线程去执行ServiceMain函数的内容,你应该将你要执行的任务

在ServiceMain中循环,这样服务就开始运行了。

2.ServiceMain函数为void WINAPI ServiceMain(int argc, char** argv)格式的函数,函数名字可以任意定义。它的作用就是:将你需要执行的任务放到该函数中循环执行即可。这就是服务程序的工作函数。在ServiceMain执行你的任务前,需要给SERVICE_TABLE_ENTRY 分派

表结构体进行赋值,注意由于此时服务还没有开始执行你的任务所以我们将服务的状态设置为SERVICE_START_PENDING,即正在初始化。我们进行如下赋值:

servicestatus.dwServiceType = SERVICE_WIN32

servicestatus.dwCurrentState = SERVICE_START_PENDING

servicestatus.dwControlsAccepted=SERVICE_ACCEPT_SHUTDOWN|SERVICE_ACCEPT_STOP

//在本例中只接受系统关机和停止服务两种控制命令

servicestatus.dwWin32ExitCode = 0

servicestatus.dwServiceSpecificExitCode = 0

servicestatus.dwCheckPoint = 0

servicestatus.dwWaitHint = 0

hstatus = ::RegisterServiceCtrlHandler("testservice", CtrlHandler)

CtrlHandler为void WINAPI CtrlHandler(DWORD request)型的函数,函数名字可以任意设定。将在下一点讲到。

Hstatus 为SERVICE_STATUS_HANDLE 类 型 的 全 局 变 量 。 当 需 要 改 变 服 务 状 态 时SetServiceStatus()函数需要它做为参数来标识一个服务。

3. void WINAPI CtrlHandler(DWORD request),函数的主要功能是,接收系统传递的控制命令,比如当你通过sc.exe关闭服务时,该函数会收到SERVICE_CONTROL_STOP消息,你就可以对服务进行必要的管理。在本例子程序中就只接收SERVICE_ACCEPT_SHUTDOWN 和

SERVICE_ACCEPT_STOP消息,这是通过前面给servicestatus赋值设定的。 这样一个基本的服务程序就完成了。 本文结束的时候会附上如

何安装服务。

当服务程序需要使用某些功能时,由于用户的关系而受到限制,比如访问注册表的HKEY_CURRENT_USER键,使用网络等等,这时候就需要以当前登陆用户的身份去进行 *** 作,通常会创建一个进程来完成需要的功能。如果使用CreateProcess, 来创建进程的话,新创建的进程和服务程序依然是相同的用户身份,还是无法达到目的,只有使用CreateProcessAsUser了。但CreateProcessAsUser的第一个参数是HANDLE hToken,该参数通常应该用LogonUser来获得,但是LogonUser又需要用户名和用户密码,这样就很不现实。那应该怎么办呢?我想到了一个方法可以绕过LogonUser直接获得hToken。因为用户已经登陆,那么肯定有Shell(就是EXPLORER.EXE)运行了,我们可以通过遍历进程来取得Shell的hToken来运行进程。

因此需要

BOOL GetTokenByName(HANDLE &hToken,LPSTR lpName)

BOOL RunProcess(LPCSTR lpImage)两个函数

示例是关于基于opencv人脸识别, 遍历样本文件夹,删除多余的图片保留10张, 然后执行 外部自定义程序"GetFeatureDATA.exe "函数提取特征

GetFeatureDATA.exe中最头上加上#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")就可以隐藏控制台窗口

开发环境vs2010, 控制台应用程序 一个.cpp文件.

//服务程序主函数。

[cpp] view plaincopy

#include"stdio.h"

#include"vector"

#include"Tlhelp32.h"

#include<afx.h>

#define_AFXDLL

//由于做的图像识别需要opencv头文件, 需要什么文件自行更改

#include"cv.h"

#include"highgui.h"

usingnamespacestd

//你的服务程序需要以下代码

SERVICE_STATUS servicestatus

SERVICE_STATUS_HANDLE hstatus//全局变量. 是setServiceStatus()的参数, 改变服务状态

voidWINAPI ServiceMain(intargc,char**argv)

voidWINAPI CtrlHandler(DWORD request)

boolbrun=false//原来代码有的,我没有用,还是保留

//以下是以获取登录用户名

BOOL GetTokenByName(HANDLE &hToken,LPSTR lpName)

BOOL RunProcess(LPCSTR lpImage)

//自己添加的代码

inttrain_time//以分钟计

vector<CString>Vec_Dir//存放图片文件夹目录名称

vector<CString>Vec_Img//

voidTraverseDir(CString&strDir,std::vector<CString>&vecDir)

intTraverseImg(CString&strDir,std::vector<CString>&vecFile)

voidTraverseDir(CString&strDir,std::vector<CString>&vecDir)

{

WIN32_FIND_DATA FindFileData

CStringstrDirTmp

strDirTmp =strDir

strDirTmp +="\\*.*"

HANDLE hFind=::FindFirstFile(strDirTmp,&FindFileData)

if(INVALID_HANDLE_VALUE ==hFind)

{

return

}

while(TRUE)

{

if(FindFileData.dwFileAttributes &FILE_ATTRIBUTE_DIRECTORY)

{

if(FindFileData.cFileName[0]!=_T('.'))

{

strDirTmp =strDir

strDirTmp +="\\"

strDirTmp +=FindFileData.cFileName

vecDir.push_back(strDirTmp)//保存所有目录

//TraverseDir(strDirTmp,vecFile)

}

}

else//是文件

{

/*strDirTmp = strDir

strDirTmp += "\\"

strDirTmp += FindFileData.cFileName

vecFile.push_back(strDirTmp)*/

}

if(!FindNextFile(hFind,&FindFileData))

break

}

FindClose(hFind)

}

intTraverseImg(CString&strDir,std::vector<CString>&vecFile)//输入路径,得到img路径文件名 不用的请忽视

{

intImgNum=0

WIN32_FIND_DATA FindFileData

CStringstrDirTmp

strDirTmp =strDir

strDirTmp +="\\*.*"

HANDLE hFind=::FindFirstFile(strDirTmp,&FindFileData)

if(INVALID_HANDLE_VALUE ==hFind)

{

//return

}

while(TRUE)

{

if(FindFileData.dwFileAttributes &FILE_ATTRIBUTE_DIRECTORY)

{

if(FindFileData.cFileName[0]!=_T('.'))

{

/*strDirTmp = strDir

strDirTmp += "\\"

strDirTmp += FindFileData.cFileName

TraverseDir(strDirTmp,vecFile) */

}

}

else

{

strDirTmp =strDir

strDirTmp +="\\"

strDirTmp +=FindFileData.cFileName

vecFile.push_back(strDirTmp)//将图片路径传入

ImgNum++

}

if(!FindNextFile(hFind,&FindFileData))

break

}

FindClose(hFind)

returnImgNum

}

voidWINAPI ServiceMain(intargc,char**argv)

{

servicestatus.dwServiceType =SERVICE_WIN32

servicestatus.dwCurrentState =SERVICE_START_PENDING

servicestatus.dwControlsAccepted =SERVICE_ACCEPT_SHUTDOWN|SERVICE_ACCEPT_STOP//在本例中只接受系统关机和停止服务两种控制命令

servicestatus.dwWin32ExitCode =0

servicestatus.dwServiceSpecificExitCode =0

servicestatus.dwCheckPoint =0

servicestatus.dwWaitHint =0

hstatus =::RegisterServiceCtrlHandler("testservice",CtrlHandler)

if(hstatus==0)

{

return

}

//向SCM 报告运行状态

servicestatus.dwCurrentState =SERVICE_RUNNING

SetServiceStatus(hstatus,&servicestatus)

//下面就

brun=true

//以下是自己要写的代码的执行调用地方.开始任务循环了,你可以添加你自己希望服务做的工作

//SYSTEMTIME t

//GetLocalTime(&t)

//int hour = t.wHour //获取小时, 可以在固定某个小时执行程序

while(1)//

{

CStringSamplesDirPath=_T("G:\\Samples")

TraverseDir(SamplesDirPath,Vec_Dir)//获取目录名称到vec_Dir

for(inti(0)i<Vec_Dir.size()i++)//

{

intImg_Num=TraverseImg(Vec_Dir[i],Vec_Img)//某个目录下的所有图片

if(Img_Num>10)

{

for(intj=Img_Num-10-1j>=0j--)

{

remove(Vec_Img[j])

}

}

Vec_Img.clear()

}

//几种调用外部程序的方法,但除了RunProcess其他都是以system身份打开程序.

//WinExec("G:\\about_MFC\\GetFeatureDATA.exe", 0)

//system( "GetFeatureDATA.exe")

//ShellExecute(NULL,"open","G:\\about_MFC\\GetFeatureDATA.exe",NULL,NULL,SW_SHOWNORMAL)

RunProcess("G:\\about_MFC\\GetFeatureDATA.exe")

//从硬盘里读取时间来做个每隔多少时间进行

CvFileStorage*Threshold=cvOpenFileStorage("./service_time.xml",0,CV_STORAGE_READ)//读取预值

CvFileNode*ThresholdNode=cvGetFileNodeByName(Threshold,0,"circle_time")

doubleservice_time =cvReadRealByName(Threshold,ThresholdNode,"circle_time")

Sleep(service_time*60*1000)//sleep自定时间后再次执行 *** 作

}

}

BOOL GetTokenByName(HANDLE &hToken,LPSTR lpName)

{

if(!lpName)

{

returnFALSE

}

HANDLE hProcessSnap =NULL

BOOL bRet =FALSE

PROCESSENTRY32 pe32 ={0}

hProcessSnap =CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0)

if(hProcessSnap ==INVALID_HANDLE_VALUE)

return(FALSE)

pe32.dwSize =sizeof(PROCESSENTRY32)

if(Process32First(hProcessSnap,&pe32))

{

do

{

if(!strcmp(_strupr(pe32.szExeFile),_strupr(lpName)))

{

HANDLE hProcess =OpenProcess(PROCESS_QUERY_INFORMATION,

FALSE,pe32.th32ProcessID)

bRet =OpenProcessToken(hProcess,TOKEN_ALL_ACCESS,&hToken)

CloseHandle(hProcessSnap)

return(bRet)

}

}

while(Process32Next(hProcessSnap,&pe32))

bRet =TRUE

}

else

bRet =FALSE

CloseHandle(hProcessSnap)

return(bRet)

}

BOOL RunProcess(LPCSTR lpImage)

{

if(!lpImage)

{

returnFALSE

}

HANDLE hToken

if(!GetTokenByName(hToken,"EXPLORER.EXE"))

{

returnFALSE

}

STARTUPINFO si

PROCESS_INFORMATION pi

ZeroMemory(&si,sizeof(STARTUPINFO))

si.cb=sizeof(STARTUPINFO)

si.lpDesktop =TEXT("winsta0\\default")

BOOL bResult =CreateProcessAsUser(hToken,lpImage,NULL,NULL,NULL,

FALSE,NORMAL_PRIORITY_CLASS,NULL,NULL,&si,&pi)

CloseHandle(hToken)

if(bResult)

{

OutputDebugString("CreateProcessAsUser ok!\r\n")

}

else

{

OutputDebugString("CreateProcessAsUser false!\r\n")

}

returnbResult

}

然后安装服务

点开始运行cmd.exe

输入以下:

sc create your_service_name binpath= D:\backup\GetXML.exe //这步注意等号右边有个空格

sc start your_service_name //启动服务, 也可以启动任务管理器在服务一栏中找到你的服务启动或停止

sc stop testservicename //停止服务

sc delete testservicename //删除服务,该服务将在下次重启后删除,在重启之前将不能注册

同一个名字的服务。

启动服务, 停止服务,等也可以在windows任务管理器中管理


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/7784518.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-09
下一篇 2023-04-09

发表评论

登录后才能评论

评论列表(0条)

保存