用C++写windows服务

来源:互联网 发布:昵称特殊字符 数据库 编辑:程序博客网 时间:2024/05/23 23:26

之前急着为完成项目中某个功能,需要写个windows服务程序, 定时来调用另外的一个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文件.

//服务程序主函数。


#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_Dirfor(inti(0);i<Vec_Dir.size();i++)//{intImg_Num=TraverseImg(Vec_Dir[i],Vec_Img);//某个目录下的所有图片if(Img_Num>10){for(intj=Img_Num-10-1;j>=0;j--){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;}elsebRet =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任务管理器中管理
原创粉丝点击