zoukankan      html  css  js  c++  java
  • windows服务程序开发详解

    写于2014.06.11 9:32。 前两天被要求将看门狗做成服务,着实费了好大的力气,现将遇到的问题和所得的知识记录于下。


    windows服务程序具体是什么我也说不清楚,只知道它偷偷地运行于后台、脱离了控制台也就没有了可视化输出(当然在调试时你可以生成.txt文件查看输出)、经过设置随电脑开启而启动。


    开始——管理工具——服务,即打开了电脑的服务控制管理器,里面有各种已经安装的服务和服务当前的状态。好了,前面的铺垫差不多了,现在主要讲讲如何将普通程序做成服务以及可能遇到的问题。


    开发工具:VS2010

    新建一个cpp文件,写一个简单的main函数,别忘了还有头文件#include <Windows.h>和引用库#pragma comment(lib, "Advapi32")。


    所有工作的第一步便是安装服务

    void InstallService()
    {
    	SC_HANDLE schSCManager;
    	SC_HANDLE schService;
    	char ch[300] = {0};
    
    	//第一参数NULL,GetModuleFileName取出当前进程的exe文件全路径,如E:projectsWin32Service2ReleaseWin32Service2.exe
    	if(!GetModuleFileName(NULL, ch, 300))
    	{
    		printf("Cannot install service (%d)
    ", GetLastError());
    		return;
    	}
    
    	//获取指定SCM(服务控制管理器)的句柄,参数1指定计算机名称、为NULL则取本机SCM句柄,参数3为权限、SC_MANAGER_ALL_ACCESS即可
    	schSCManager = OpenSCManager( 
    		NULL,                    // local computer
    		NULL,                    // ServicesActive database 
    		SC_MANAGER_ALL_ACCESS);  // full access rights 
    
    	if (NULL == schSCManager) 
    	{
    		printf("OpenSCManager failed (%d)
    ", GetLastError());
    		return;
    	}
    
    	// Create the service
    	/*
    	于指定的SCM上创建一个服务,参数1即SCM句柄,参数2是要创建的服务名称,参数3是服务在SCM中显示的名字,参数4权限。
    	参数5服务类型标注该进程中有几个服务,即StartServiceCtrlDispatcher(ServiceTable)数组SERVICE_TABLE_ENTRY ServiceTable[]中
    	注册了几个服务,1个则SERVICE_WIN32_OWN_PROCESS独享进程,多于1个SERVICE_WIN32_SHARE_PROCESS共享进程。
    	参数6为启动方式,SERVICE_AUTO_START则电脑启动后服务自动开启。
    	参数7直接SERVICE_ERROR_NORMAL
    	参数8最关键就是服务要执行的exe文件的全路径,如E:projectsWin32Service2ReleaseWin32Service2.exe,服务启动后会自动运行该exe文件。
    	*/
    	schService = CreateService( 
    		schSCManager,              // SCM database 
    		serviceName.c_str(),                   // name of service 
    		"AAAAA",                   // service name to display 
    		SERVICE_ALL_ACCESS,        // desired access 
    		SERVICE_WIN32_OWN_PROCESS, // service type 
    		SERVICE_AUTO_START,      // start type 
    		SERVICE_ERROR_NORMAL,      // error control type 
    		ch,                    // path to service's binary 
    		NULL,                      // no load ordering group 
    		NULL,                      // no tag identifier 
    		NULL,                      // no dependencies 
    		NULL,                      // LocalSystem account 
    		NULL);                     // no password 
    
    	if (schService == NULL) 
    	{
    		printf("CreateService failed (%d)
    ", GetLastError()); 
    		CloseServiceHandle(schSCManager);
    		return;
    	}
    	else printf("Service installed successfully
    "); 
    
    	//所有SCM句柄和服务句柄都要妥善关闭
    	//否则在后续DeleteService卸载服务时SCM仅标记该服务要卸载但不执行,等所有打开该服务的句柄均关闭后才执行卸载、或等电脑重启
    	CloseServiceHandle(schService); 
    	CloseServiceHandle(schSCManager);
    }

    好了到这里就成功的在SCM中创建了一个服务,打开SCM即可看见。这个服务在启动时会根据函数CreateService中的参数ch找到指定的exe文件作为服务程序开始运行。那是不是随随便便的一个exe文件都可以作为服务程序来运行呢?当然不会这么简单。

    为了验证这种情况,专门写了一个程序测试,被安装的服务的可执行文件为E:\projects\attemp3\Debug\attemp3.exe

    attempt.exe文件:

    int main()
    {
    std::ofstream out("D:\ddd.txt");
    if(out.is_open())
    {
    	while(1)
    		out<<"nihaoma"<<std::endl;
    }
    return 0;
    }
    然后在SCM中启动服务,D盘中生成了ddd.txt而且一直向ddd.txt写入“nihaoma”,此时打开任务管理器可以看到attemp3.exe在运行。一小段时间后,SCM中该服务的启动结果为


    同时attemp3.exe停止运行。



    那么服务程序应该按照什么格式来写呢?

    int main(int argc, char* argv[])
    {
    	SERVICE_TABLE_ENTRY ServiceTable[2];
    	ServiceTable[0].lpServiceName = "";
    	ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
    
    	ServiceTable[1].lpServiceName = NULL;
    	ServiceTable[1].lpServiceProc = NULL;
    	// 启动服务的控制分派机线程
    	StartServiceCtrlDispatcher(ServiceTable); 
    
    	std::cout<<"input the char : "<<std::endl;
    	std::string type;
    	while(std::cin>>type)
    	{
    		if(type == "i")
    			InstallService();
    		else if(type == "u")
    			UninstallService();
    		else if(type == "stop")
    			StopService();
    		else if(type == "start") 
    			StartService2();
    		else if(type == "q")
    			break;
    	}
    
    	return 0;
    }
    这就是服务程序的main函数,3到10行是服务程序的主体,12到26行用于服务控制。第5行中的ServiceMain为函数指针,完整形式为

    void ServiceMain(int argc, char** argv) 

    只要完成该函数的编写一个服务程序就算成了。


    就目前来看,整个故事是这样的:SCM启动这个服务即启动了一个进程来执行上述的main函数,SCM会苦苦等待刚刚提拔的进程来调用StartServiceCtrlDispatcher。因为通过这个调用,SCM会与进程的主线程建立起连接,用这个连接SCM可以向主线程发各种控制命令从而达到控制整个进程的目的。再来看这个进程的主线程调用StartServiceCtrlDispatcher后会发生什么,主线程会为上面SERVICE_TABLE_ENTRY ServiceTable[]数组中的每个服务开一个线程来跑对应的服务入口函数如上面的第5行的ServiceMain、并且陷入StartServiceCtrlDispatcher中直到所有服务进入SERVICE_STOPPED状态才返回,在这个过程中主线程会接收来自SCM的各种控制命令。用MSDN的话来说,此时的主线程就如同一个调度器,根据来自SCM的控制命令调用对应服务的处理函数,这都是后话了。每个新开的线程都从自己对应的入口函数开始、继续向下执行。每个服务都对应数组ServiceTable中的一项,每一项都包含了服务的名称和服务的入口函数,名称对应CreateService中创建的服务名称。当启动的服务只有一个时,名称就没有什么意义了,这就是上面第4行为“”的原因。


    这么一大段看得头都大了,不要紧只要记住在main开始后早点调StartServiceCtrlDispatcher,第3行的参数通常只有两项,一个是唯一的服务,一个是NULL用于标识结束。后面有个完整的例子看了会清楚很多。下面来讲服务入口函数ServiceMain。

    void ServiceMain(int argc, char** argv) 
    { 
    	int error; 
    
    	ServiceStatus.dwServiceType = SERVICE_WIN32; 
    	ServiceStatus.dwCurrentState = SERVICE_START_PENDING; 
    	ServiceStatus.dwControlsAccepted   = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
    	ServiceStatus.dwWin32ExitCode = 0; 
    	ServiceStatus.dwServiceSpecificExitCode = 0; 
    	ServiceStatus.dwCheckPoint = 0; 
    	ServiceStatus.dwWaitHint = 0; 
    
    	hStatus = RegisterServiceCtrlHandler("", (LPHANDLER_FUNCTION)ControlHandler); 
    	if (hStatus == (SERVICE_STATUS_HANDLE)0) 
    	{
    		// Registering Control Handler failed
    		return; 
    	} 
    	// Initialize Service 
    	error = InitService(); 
    	if (!error) 
    	{
    		// Initialization failed
    		ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
    		ServiceStatus.dwWin32ExitCode = -1; 
    		SetServiceStatus(hStatus, &ServiceStatus); 
    		return; 
    	} 
    	// We report the running status to SCM. 
    	ServiceStatus.dwCurrentState = SERVICE_RUNNING; 
    	SetServiceStatus (hStatus, &ServiceStatus);
    
    	//
    	//do something
    	//
    
    	return; 
    }
    第5行的ServiceStatus为SERVICE_STATUS类型的服务状态变量,因为多个函数都可能用到所以在程序开头的地方声明为全局变量。 
    第13行的hStatus为SERVICE_STATUS_HANDLE类型的服务状态句柄, 同样声明为全局变量。

    第5行设置了该服务的类型,是多个服务共享一个进程还是只有一个服务,

    SERVICE_WIN32 = (SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS) 即类型为模棱两可的中庸。

    第6行设置了服务当前状态,为正在启动状态、就是说还没跑起来,状态用来实时向大总管SCM汇报。

    第7行告诉说我都接受哪些个控制命令,我只接受停止和关机控制命令,其他的命令发过来我一概不鸟。这是为13行做准备呢。


    13行比较重要,这是为该服务注册控制处理函数,ControlHandler为函数指针,这就是我们上面讲StartServiceCtrlDispatcher时主线程能够根据SCM发过来的控制命令调用相应处理的原因。因为在这用RegisterServiceCtrlHandler为服务注册了控制处理函数当然能够根据控制命令来执行相关操作咯。还是跟上面一样,因为整个进程中只有一个服务所以第一个参数为空,如果有多个服务则必须有服务的名称才能知道这个函数注册到哪个服务。同时RegisterServiceCtrlHandler应该在ServiceMain靠前的地方调用、不要耽搁。

    调用结束后返回服务状态句柄hStatus,配合SetServiceStatus、ServiceStatus,这个时候才可以向SCM汇报状态。


    20行中的InitService为自定义函数用来给自己的主程序作初始化工作。如果初始化花费时间较长,可以在19行调用一次SetServiceStatus用来向SCM报告自己正在努力运行中,怕SCM以为你死了要给你收尸哟。


    当一切搞定后30行和31行向SCM报告“我已经开始运行,你安息吧”


    这之后就是主体程序部分,你想让服务跑起来后做哪些事都写在这里、通常是一个无限循环,如每隔一秒向指定文件写东西什么的啦。这里写成死循环也可以,当SCM想停止服务时会杀死该进程,到时候系统自动来收尸,也不用在循环中判断服务状态了。



    接下来轮到控制处理函数了

    void ControlHandler(DWORD request) 
    { 
    	switch(request) 
    	{ 
    	case SERVICE_CONTROL_STOP: 
    		ServiceStatus.dwWin32ExitCode = 0; 
    		ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
    		SetServiceStatus (hStatus, &ServiceStatus);
    		{
    			std::ofstream out("E:\projects\Win32Service2\Win32Service2\e.txt", std::ios::app);
    			if(out.is_open())
    			{
    				out<<"NIHAOMA"<<std::endl;
    				out.close();
    			}
    		}
    		return; 
    
    	case SERVICE_CONTROL_SHUTDOWN: 
    		ServiceStatus.dwWin32ExitCode = 0; 
    		ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
    		SetServiceStatus (hStatus, &ServiceStatus);
    		return; 
    
    	default:
    		break;
    	} 
    
    	// Report current status
    	SetServiceStatus (hStatus, &ServiceStatus);
    
    	return; 
    }

    主线程收到控制命令后就会查看这个中央一号文件是发给哪个服务的,然后找到给这个服务注册的控制处理函数、调用它,参数是控制命令的类型。在这里停止和关机命令做的操作差不多,都是给SCM报告了STOPPED状态,都没有做后续收尾工作、像释放什么啦、关闭什么啦都没做,因为进程马上会终止,到时候全世界都会毁灭、屁股擦得再干净也得见上帝。这应该不是个好习惯,最好还是在程序即将终止时做一些善后。

    唯一的区别是服务收到停止命令会打开一个txt文件向里面写句“nihaoma”。在服务即将终止时你希望做哪些处理都可以写到这里。


    #include <Windows.h>
    #include <iostream>
    #include <string>
    
    #pragma comment(lib, "Advapi32")
    
    
    SERVICE_STATUS ServiceStatus; 
    SERVICE_STATUS_HANDLE hStatus; 
    void ServiceMain(int argc, char** argv); 
    void ControlHandler(DWORD request); 
    
    int main(int argc, char* argv[])
    {
    	SERVICE_TABLE_ENTRY ServiceTable[2];
    	ServiceTable[0].lpServiceName = "";
    	ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
    
    	ServiceTable[1].lpServiceName = NULL;
    	ServiceTable[1].lpServiceProc = NULL;
    	// 启动服务的控制分派机线程
    	StartServiceCtrlDispatcher(ServiceTable); 
    
    	.......
    
    	return 0;
    }
    
    void ServiceMain(int argc, char** argv) 
    { 
    	int error; 
    
    	ServiceStatus.dwServiceType = SERVICE_WIN32; 
    	ServiceStatus.dwCurrentState = SERVICE_START_PENDING; 
    	ServiceStatus.dwControlsAccepted   = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
    	ServiceStatus.dwWin32ExitCode = 0; 
    	ServiceStatus.dwServiceSpecificExitCode = 0; 
    	ServiceStatus.dwCheckPoint = 0; 
    	ServiceStatus.dwWaitHint = 0; 
    
    	hStatus = RegisterServiceCtrlHandler("", (LPHANDLER_FUNCTION)ControlHandler); 
    	if (hStatus == (SERVICE_STATUS_HANDLE)0) 
    	{
    		// Registering Control Handler failed
    		return; 
    	} 
    	// Initialize Service 
    	error = InitService(); 
    	if (!error) 
    	{
    		// Initialization failed
    		return; 
    	} 
    	// We report the running status to SCM. 
    	ServiceStatus.dwCurrentState = SERVICE_RUNNING; 
    	SetServiceStatus (hStatus, &ServiceStatus);
    
    	//
    	//now, do whatever you want
    	//
    
    	return; 
    }
    
    void ControlHandler(DWORD request) 
    { 
    	switch(request) 
    	{ 
    	case SERVICE_CONTROL_STOP: 
    		ServiceStatus.dwWin32ExitCode = 0; 
    		ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
    		SetServiceStatus (hStatus, &ServiceStatus);
    		
    		//
    		//now, do something
    		//
    		return; 
    
    	case SERVICE_CONTROL_SHUTDOWN: 
    		ServiceStatus.dwWin32ExitCode = 0; 
    		ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
    		SetServiceStatus (hStatus, &ServiceStatus);
    		return; 
    
    	default:
    		break;
    	} 
    
    	// Report current status
    	SetServiceStatus (hStatus, &ServiceStatus);
    
    	return; 
    }
    按照这种格式写出来的程序就是服务了。

    让服务跑起来?最直观的做法就是F5直接运行了。但~~是~~服务根本没有运行,SCM中没有这个服务,任务管理器也看不到对应的.exe文件在跑。怎么回事呢?是这样的,当直接在VS下运行时,程序确实是从main函数开始跑的,到第22行调用StartServiceCtrlDispatcher的时候并没有像前面说到的那样,主线程陷入这次调用、根据SERVICE_TABLE_ENTRY注册的服务分别单开一个线程开始跑ServiceMain入口函数......实际的情况是StartServiceCtrlDispatcher等待10秒左右就返回了,返回值0,调用失败。用GetLastError()查看错误类型,1063,“服务进程无法连接到服务控制器上”,即对应MSDN上的ERROR_FAILED_SERVICE_CONTROLLER_CONNECT,错误原因:程序没有被当做一个服务来运行,反而作为控制台应用程序来跑。


    所以说我们真正想做的事情在ServiceMain中,但如果直接在VS中点执行StartServiceCtrlDispatcher并不会创建新线程跑ServiceMain,直接就错误返回了。


    这个时候你就面临着两种做法,我先说第一种,将上述的程序生成.exe文件。然后在另一个程序的main函数里调用InstallService,然后打开SCM找到该服务手动点“开启”,这样服务才算真正在后台跑起来了。显然有点麻烦。


    我来说第二种方法,这就涉及到服务控制了

    //开启服务
    void StartService2()
    {
    	//打开SCM,取得SCM句柄
    	SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    	if(schSCManager == NULL)
    	{
    		printf("OpenSCManager failed (%d)
    ", GetLastError()); 
    		return;
    	}
    
    	//打开服务,取得服务句柄。serviceName为要打开的服务名称,对应着CreateService创建服务时起的名字。
    	SC_HANDLE schService = OpenService(schSCManager, serviceName.c_str(), SC_MANAGER_ALL_ACCESS);
    	if(schService == NULL)
    	{
    		printf("OpenService failed (%d)
    ", GetLastError());
    		CloseServiceHandle(schSCManager);
    		return;
    	}
    
    	//开启服务
    	SERVICE_STATUS serviceStatus;
    	if(StartService(schService, NULL, NULL))
    	{
    		//查询服务当前的状态,等待启动服务
    		while(QueryServiceStatus(schService, &serviceStatus))
    		{
    			if(serviceStatus.dwCurrentState == SERVICE_RUNNING)
    				break;
    			else
    				Sleep(100);
    		}
    		std::cout<<"Service Start Successfully"<<std::endl;
    	}
    
    	//妥善关闭所有打开的句柄,防止待会卸载服务时卸载不掉的麻烦
    	CloseServiceHandle(schService);
    	CloseServiceHandle(schSCManager);
    }
    
    
    //停止服务
    void StopService()
    {
    	SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    	if(schSCManager == NULL)
    	{
    		printf("OpenSCManager failed (%d)
    ", GetLastError()); 
    		return;
    	}
    
    	SC_HANDLE schService = OpenService(schSCManager, serviceName.c_str(), SC_MANAGER_ALL_ACCESS);
    	if(schService == NULL)
    	{
    		printf("OpenService failed (%d)
    ", GetLastError());
    		CloseServiceHandle(schSCManager);
    		return;
    	}
    
    	//关闭服务
    	SERVICE_STATUS serviceStatus;
    	if(ControlService(schService, SERVICE_CONTROL_STOP, &serviceStatus))
    	{
    		//查询服务当前的状态,等待停止服务
    		while(QueryServiceStatus(schService, &serviceStatus))
    		{
    			if(serviceStatus.dwCurrentState == SERVICE_STOPPED)
    				break;
    			else
    				Sleep(100);
    		}
    		std::cout<<"Service Stop Successfully"<<std::endl;
    	}
    
    	CloseServiceHandle(schService);
    	CloseServiceHandle(schSCManager);
    }
    
    
    //卸载服务
    void UninstallService()
    {
    	SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    	if(schSCManager == NULL)
    	{
    		printf("OpenSCManager failed (%d)
    ", GetLastError()); 
    		return;
    	}
    
    	SC_HANDLE schService = OpenService(schSCManager, serviceName.c_str(), SC_MANAGER_ALL_ACCESS);
    	if(schService == NULL)
    	{
    		printf("OpenService failed (%d)
    ", GetLastError());
    		CloseServiceHandle(schSCManager);
    		return;
    	}
    
    	//卸载服务
    	if(DeleteService(schService))
    		std::cout<<"uninstall service ok"<<std::endl;
    	else
    		std::cout<<"uninstall service fail"<<std::endl;
    
    	CloseServiceHandle(schService);
    	CloseServiceHandle(schSCManager);
    }
    开启、停止、卸载的过程都差不多:

    先获得SCM句柄;

    然后用SCM句柄,获得服务句柄;

    最后用服务句柄控制服务。


    好了现在完整的服务程序代码是这样的

    #include <Windows.h>
    #include <iostream>
    #include <string>
    
    #pragma comment(lib, "Advapi32")
    
    
    //全局变量
    SERVICE_STATUS ServiceStatus; 
    SERVICE_STATUS_HANDLE hStatus; 
    std::string serviceName = "MyService9";
    std::string displayName = "BBBB";
    
    //服务程序函数
    void ServiceMain(int argc, char** argv); 
    void ControlHandler(DWORD request); 
    
    //服务控制程序函数
    void InstallService();
    void  UninstallService();
    void StopService();
    void StartService2();
    
    //自定义函数
    int Init();
    
    
    
    int main(int argc, char* argv[])
    {
    	SERVICE_TABLE_ENTRY ServiceTable[2];
    	ServiceTable[0].lpServiceName = "";
    	ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
    
    	ServiceTable[1].lpServiceName = NULL;
    	ServiceTable[1].lpServiceProc = NULL;
    	// 启动服务的控制分派机线程
    	StartServiceCtrlDispatcher(ServiceTable); 
    
    
    	std::cout<<"install the service input—————>i"<<std::endl;
    	std::cout<<"uninstall the service input————>u"<<std::endl;
    	std::cout<<"start the service input——————>start"<<std::endl;
    	std::cout<<"stop the service input——————->stop"<<std::endl;
    	std::cout<<"quit input ————————————->q"<<std::endl;
    	std::cout<<"——>";
    	std::string type;
    	while(std::cin>>type)
    	{
    		if(type == "i")
    			InstallService();
    		else if(type == "u")
    		{
    			StopService();
    			UninstallService();
    		}
    		else if(type == "stop")
    			StopService();
    		else if(type == "start") 
    			StartService2();
    		else if(type == "q")
    			break;
    		
    		std::cout<<"——>";
    	}
    
    	return 0;
    }
    
    
    /*******************************************服务程序*******************************************/
    
    
    void ServiceMain(int argc, char** argv) 
    { 
    	int error; 
    
    	ServiceStatus.dwServiceType = SERVICE_WIN32; 
    	ServiceStatus.dwCurrentState = SERVICE_START_PENDING; 
    	ServiceStatus.dwControlsAccepted   = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
    	ServiceStatus.dwWin32ExitCode = 0; 
    	ServiceStatus.dwServiceSpecificExitCode = 0; 
    	ServiceStatus.dwCheckPoint = 0; 
    	ServiceStatus.dwWaitHint = 0; 
    
    	hStatus = RegisterServiceCtrlHandler("", (LPHANDLER_FUNCTION)ControlHandler); 
    	if (hStatus == (SERVICE_STATUS_HANDLE)0) 
    	{
    		// Registering Control Handler failed
    		return; 
    	} 
    
    	// Initialize Service 
    	error = Init(); 
    	if (!error) 
    	{
    		// Initialization failed
    		ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
    		ServiceStatus.dwWin32ExitCode = -1; 
    		SetServiceStatus(hStatus, &ServiceStatus); 
    		return; 
    	} 
    
    	// We report the running status to SCM. 
    	ServiceStatus.dwCurrentState = SERVICE_RUNNING; 
    	SetServiceStatus (hStatus, &ServiceStatus);
    
    	//now, do what you want
    	......
    
    	return; 
    }
    
    void ControlHandler(DWORD request) 
    { 
    	switch(request) 
    	{ 
    	case SERVICE_CONTROL_STOP: 
    		ServiceStatus.dwWin32ExitCode = 0; 
    		ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
    		SetServiceStatus (hStatus, &ServiceStatus);
    
    		//做一些善后工作
    		......
    
    		return; 
    
    	case SERVICE_CONTROL_SHUTDOWN: 
    		ServiceStatus.dwWin32ExitCode = 0; 
    		ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
    		SetServiceStatus (hStatus, &ServiceStatus);
    		return; 
    
    	default:
    		break;
    	} 
    
    	// Report current status
    	SetServiceStatus (hStatus, &ServiceStatus);
    	return; 
    }
    
    
    /*******************************************服务控制程序*******************************************/
    
    
    //安装服务
    void InstallService()
    {
    	SC_HANDLE schSCManager;
    	SC_HANDLE schService;
    	char ch[300] = {0};
    
    	//第一参数NULL,GetModuleFileName取出当前进程的exe文件全路径,如E:projectsWin32Service2ReleaseWin32Service2.exe
    	if(!GetModuleFileName( NULL, ch, 300))
    	{
    		printf("Cannot install service (%d)
    ", GetLastError());
    		return;
    	}
    
    	//获取指定SCM(服务控制管理器)的句柄,参数1指定计算机名称、为NULL则取本机SCM句柄,参数3为权限、SC_MANAGER_ALL_ACCESS即可
    	schSCManager = OpenSCManager( 
    		NULL,                    // local computer
    		NULL,                    // ServicesActive database 
    		SC_MANAGER_ALL_ACCESS);  // full access rights 
    
    	if (NULL == schSCManager) 
    	{
    		printf("OpenSCManager failed (%d)
    ", GetLastError());
    		return;
    	}
    
    	// Create the service
    	schService = CreateService( 
    		schSCManager,              // SCM database 
    		serviceName.c_str(),                   // name of service 
    		displayName.c_str(),                   // service name to display 
    		SERVICE_ALL_ACCESS,        // desired access 
    		SERVICE_WIN32_OWN_PROCESS, // service type 
    		SERVICE_AUTO_START,      // start type 
    		SERVICE_ERROR_NORMAL,      // error control type 
    		"E:\projects\attemp3\Debug\attemp3.exe",                    // path to service's binary 
    		NULL,                      // no load ordering group 
    		NULL,                      // no tag identifier 
    		NULL,                      // no dependencies 
    		NULL,                      // LocalSystem account 
    		NULL);                     // no password 
    
    	if (schService == NULL) 
    	{
    		printf("CreateService failed (%d)
    ", GetLastError()); 
    		CloseServiceHandle(schSCManager);
    		return;
    	}
    	else printf("Service installed successfully
    "); 
    
    	CloseServiceHandle(schService); 
    	CloseServiceHandle(schSCManager);
    }
    
    
    //卸载服务
    void UninstallService()
    {
    	SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    	if(schSCManager == NULL)
    	{
    		printf("OpenSCManager failed (%d)
    ", GetLastError()); 
    		return;
    	}
    
    	SC_HANDLE schService = OpenService(schSCManager, serviceName.c_str(), SC_MANAGER_ALL_ACCESS);
    	if(schService == NULL)
    	{
    		printf("OpenService failed (%d)
    ", GetLastError());
    		CloseServiceHandle(schSCManager);
    		return;
    	}
    
    	//卸载服务
    	if(DeleteService(schService))
    		std::cout<<"uninstall service ok"<<std::endl;
    	else
    		std::cout<<"uninstall service fail"<<std::endl;
    
    	CloseServiceHandle(schService);
    	CloseServiceHandle(schSCManager);
    }
    
    
    //开启服务
    void StartService2()
    {
    	//打开SCM,取得SCM句柄
    	SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    	if(schSCManager == NULL)
    	{
    		printf("OpenSCManager failed (%d)
    ", GetLastError()); 
    		return;
    	}
    
    	//打开服务,取得服务句柄。serviceName为要打开的服务名称,对应着CreateService创建服务时起的名字。
    	SC_HANDLE schService = OpenService(schSCManager, serviceName.c_str(), SC_MANAGER_ALL_ACCESS);
    	if(schService == NULL)
    	{
    		printf("OpenService failed (%d)
    ", GetLastError());
    		CloseServiceHandle(schSCManager);
    		return;
    	}
    
    	//开启服务
    	SERVICE_STATUS serviceStatus;
    	if(StartService(schService, NULL, NULL))
    	{
    		//查询服务当前的状态,等待启动服务
    		while(QueryServiceStatus(schService, &serviceStatus))
    		{
    			if(serviceStatus.dwCurrentState == SERVICE_RUNNING)
    				break;
    			else
    				Sleep(100);
    		}
    		std::cout<<"Service Start Successfully"<<std::endl;
    	}
    
    	//妥善关闭所有打开的句柄,防止待会卸载服务时卸载不掉的麻烦
    	CloseServiceHandle(schService);
    	CloseServiceHandle(schSCManager);
    }
    
    
    //停止服务
    void StopService()
    {
    	SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    	if(schSCManager == NULL)
    	{
    		printf("OpenSCManager failed (%d)
    ", GetLastError()); 
    		return;
    	}
    
    	SC_HANDLE schService = OpenService(schSCManager, serviceName.c_str(), SC_MANAGER_ALL_ACCESS);
    	if(schService == NULL)
    	{
    		printf("OpenService failed (%d)
    ", GetLastError());
    		CloseServiceHandle(schSCManager);
    		return;
    	}
    
    	//关闭服务
    	SERVICE_STATUS serviceStatus;
    	if(ControlService(schService, SERVICE_CONTROL_STOP, &serviceStatus))
    	{
    		//查询服务当前的状态,等待停止服务
    		while(QueryServiceStatus(schService, &serviceStatus))
    		{
    			if(serviceStatus.dwCurrentState == SERVICE_STOPPED)
    				break;
    			else
    				Sleep(100);
    		}
    		std::cout<<"Service Stop Successfully"<<std::endl;
    	}
    
    	CloseServiceHandle(schService);
    	CloseServiceHandle(schSCManager);
    }
    
    
    /*******************************************自定义函数*******************************************/
    
    //用来给109行自己程序运行前做些初始化工作,如果初始化很简单就没必要定义此函数
    int Init(){
    	return true;
    }
    至此所有的故事都呈现出来了,让我来还原一下真实的情况:

    1.当程序作为一个普通的控制台应用程序运行后,马上到达了StartServiceCtrlDispatcher,函数返回0表示连接SCM失败,我们对此不做任何处理。程序继续向下运行到了选择输入操作,我们输入“i”进行服务安装。


    2.在InstallService函数中我们用CreateService系统调用成功的注册了一个服务,还为服务取了名字,最关键的是我们将服务启动后要执行的exe指定为正在运行的exe。有疑问不要紧,后面会水落石出。然后while又提示我们选择输入操作,我们输入“start”让服务运行起来。


    3.好戏来了,StartService2中我们通过StartService系统调用来开启服务,这就好比在SCM中手动开启服务一样。SCM开了个进程开始跑CreateService中指定的exe,也就是我们正在运行的程序文件。但~~~是~~~这次是SCM以服务的形式跑这个程序,进程的主线程从main函数开始向下进行,很快遇到StartServiceCtrlDispatcher,调用成功!主线程开了新线程跑我们在2中安装好的服务;新线程从ServiceMain开始运行,上来就为自己的服务注册了控制处理函数,向SCM报告自己已经处于RUNNING状态后开始做我们交待给它的任务;主线程陷入StartServiceCtrlDispatcher调用中等待新线程跑的服务进入STOPPED状态、在等待服务结束的过程中主线程会处理发过来的控制命令然后调用新线程刚刚注册的控制处理函数来工作。这一切都在SCM新开的进程中按部就班地进行着。


    4.而我们的程序确是在VS开的进程中运行着,因为两者不想干扰所以我们的故事还没完。在执行完StartService2后while又提示我们选择输入操作,我们输入"q"跳出while循环,我们的程序运行完了。而我们开启的服务仍旧马不停蹄地运行在后台,我们的退出它丝毫不受影响也丝毫不关心。


    5.我们再次打开程序,如前面讲的一样又到了选择输入操作了。只要有正确的serviceName我们就能找到刚才的服务。这个服务程序此时仍旧优哉游哉地运行着。我们选择"stop",在StopService函数中调用了ControlService(, SERVICE_CONTROL_STOP, )系统调用,这就好比SCM给服务发了SERVICE_CONTROL_STOP命令,陷入StartServiceCtrlDispatcher的主线程以SERVICE_CONTROL_STOP为参数调用ControlHandler,在ControlHandler中做各种善后,然后等到ControlHandler函数执行完毕,主线程从StartServiceCtrlDispatcher返回,然后继续执行之后的语句。我们看41行StartServiceCtrlDispatcher之后就是选择输入操作。


    6.程序以服务而非控制台的形式运行于后台,所有的输出都不会出现、继而所有等待输入的地方都以EOF(文件结束符)作为输入。std::cin>>type作为循环判断条件,当我们使用一个istream对象作为条件时,其效果是检测流的状态。如果流是有效的,即流未遇到错误,那么检测成功。当遇到EOF或一个无效的输入时(如type是整数而输入的是字符串),istream对象的状态会变为无效。处于无效状态的istream对象会使条件变为假。服务程序会将所有等待输入的地方填充EOF从而使得while判断失效跳出循环,继续运行选择输入操作后面的语句,遇return程序运行结束。SCM就毙了整个进程,覆巢之下无完卵,主线程连同正孜孜不倦地完成我们交待的任务的新线程统统嗝屁了、根本都不用通知让新线程退出。


    7.将下面的代码放入39行,就可以证明6中的结论,等待输入的字符串test被输入EOF,故std::cin.eof()返回TRUE表明输入结束,在对应目录下生成了l.txt。

            std::string test;
    	if((std::cin>>test).eof())
    	{
    		std::ofstream oo("E:\projects\Win32Service2\Win32Service2\l.txt", std::ios::app);
    		if(oo.is_open())
    		{
    			oo<<"NIHAOMA"<<std::endl;
    			oo.close();
    		}
    	}
    	else
    	{
    		std::ofstream oo("E:\projects\Win32Service2\Win32Service2\m.txt", std::ios::app);
    		if(oo.is_open())
    		{
    			oo<<"NIHAOMA"<<std::endl;
    			oo.close();
    		}
    	}



    8.卸载服务时要先停止后卸载。



    最后再透漏点天机

    WSADATA ws;
    
    	if(WSAStartup(MAKEWORD(2, 2), &ws) != 0)
    	{
    		std::cout<<"WSAStartup error : "<<GetLastError()<<std::endl;
    		return;
    	}
    
    	log4cxx::PropertyConfigurator::configure("myconfig.txt");
    	logger = log4cxx::Logger::getLogger("mylogger");
    	
    	NetModule netModule;
    	netModule.Start();
    这就是我在看门狗中交代给新线程帮我做的事,这段代码原原本本的放在108、109行,netModule.Start()开始了IOCP服务器端的无限循环。第9行为log4cxx读取配置文件。运行程序后


    安装、开启成功,打开任务管理器根本没有我的WatchDog3。打开SCM,找到WatchDog3,显示没有开启,手动开启后


    在看门狗中加入SetUnhandledExceptionFilter在程序崩溃时生成dump文件来还原现场。打开dump后又是不能调试,只是给出了唯一线索


    好诡异的错误,周二让我头疼了一晚上,终于在最后时刻让我解决了。

    故事原来是这样滴,服务开启后会到指定的地方运行exe文件,比如D:workspacewindowsSysIVMSBranchesProjectWatchDogServerReleasewatchDog.exe。而不管可执行程序的位置如何,所有服务启动时的当前目录都是C:WINDOWSsystem32。服务会在该目录下搜索配置文件而看门狗和log4cxx的配置文件都与exe程序放在上面的路径下导致读取配置失败,log4cxx就开始报各种诡异错误。


    这时只要把需要的配置文件放到C:WINDOWSsystem32服务就可以正常运行了,一切如此easy。我在程序中加入了下面的代码,将当前工作目录改到exe目录下从而只要把配置文件和exe、库文件放在一起就行了,省了很多麻烦。

    char ch[300] = {0}; 
    	if( !GetModuleFileName( NULL, ch, 300 ) )
    		return;
    
    	std::string str = ch;
    	size_t index = str.find_last_of("\");
    	str = str.substr(0, index+1);
    
    	if(!SetCurrentDirectory(str.c_str()))
    		return;

    综上所述,如果不更改当前目录就需要把配置文件放到C:WINDOWSsystem32。动态库就不用了,动态库永远都和exe文件放在一起。
  • 相关阅读:
    APP界面设计之尺寸介绍
    设计师应该知道的那些事儿(一)
    PS制作高光导航背景
    URL长度限制
    问自己的技术问题
    JavaScript实现生成GUID(全局统一标识符)
    JS操作数组,for循环新技能get
    win7/Win8/Win10, IIS7.5/IIS8/IIS10 配置伪静态
    .NET WebAPI生成Excel
    .net WebAPI 传递对象参数
  • 原文地址:https://www.cnblogs.com/chaikefusibushiji/p/7475639.html
Copyright © 2011-2022 走看看