玩转Windows服务系列——Windows服务小技巧

时间:2022-07-14 00:30:50

原文:玩转Windows服务系列——Windows服务小技巧

伴随着研究Windows服务,逐渐掌握了一些小技巧,现在与大家分享一下。

将Windows服务转变为控制台程序

由于默认的Windows服务程序,编译后为Win32的窗口程序。我们在程序启动或运行过程中,如果想看到一些调试信息,那么就只能通过DebugView或者输出到日志的方式了。因为如果我们通过printf或者std::cout输出调试信息的话,Win32窗口程序是无法显示的。

此时,我们是多么怀念我们的经典的控制台程序啊,它可以很方便的将我们的调试信息输出出来,简直是太方便了。既然如此,那我们就让它一秒钟变格格吧,额,应该是一秒钟变控制台。

下面分享一下我的实现代码

#ifdef _DEBUG
//Debug版本,直接输出到控制台
#define OUT(s) printf_s(s);
#define OUT_LN(s) printf_s(s##"\r\n");
#else
//非Debug版本,则输出到调试器,一般使用DebugView
#define OUT(s) OutputDebugString(s);
#define OUT_LN(s) OutputDebugString(s);
#endif class CServicesModule : public ATL::CAtlServiceModuleT< CServicesModule, IDS_SERVICENAME >
{
public :
DECLARE_LIBID(LIBID_ServicesLib)
DECLARE_REGISTRY_APPID_RESOURCEID(IDR_SERVICES, "{0794CF96-5CC5-432E-8C1D-52B980ACBE0F}")
HRESULT InitializeSecurity() throw()
{
return S_OK;
}
//服务启动
HRESULT Load();
//服务停止
HRESULT UnLoad(); HRESULT Run(_In_ int nShowCmd = SW_HIDE) throw()
{
HRESULT hr = S_OK;
OUT_LN("准备启动服务");
hr = Load();
if(hr)
{
OUT_LN("启动服务失败");
return hr;
}
OUT_LN("Services服务已启动"); hr = ATL::CAtlServiceModuleT< CServicesModule, IDS_SERVICENAME >::Run(nShowCmd); hr = UnLoad();
OUT_LN("Services服务正常退出");
return hr;
}
}; CServicesModule _AtlModule; //
#ifndef _DEBUG
//非Debug版本,编译为Win32程序
extern "C" int WINAPI _tWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/,
LPTSTR /*lpCmdLine*/, int nShowCmd)
{
return _AtlModule.WinMain(nShowCmd);
}
#else
//Debug版本,编译为控制台程序
int _tmain(int argc, _TCHAR* argv[])
{
return _AtlModule.WinMain(SW_SHOW);
}
#endif HRESULT CServicesModule::Load()
{
OUT_LN("服务正在启动");
return 0;
} HRESULT CServicesModule::UnLoad()
{
OUT_LN("服务正在停止");
return 0;
}

通过_DEBUG宏来区分是否编译成控制台程序。

当指定编译Debug版本时,可以将程序编译为控制台程序,通过RegServer注册服务,然后直接运行服务exe程序,这样通过printf输出的信息,就可以在控制台上显示了,如下图。

当指定编译Release版本时,将程序编译为Win32程序,通过Service注册服务,通过服务管理器管理服务的运行和停止。

玩转Windows服务系列——Windows服务小技巧

当然,这还不是全部,还有一个地方需要设置,下面分别是Debug和Release下的设置

玩转Windows服务系列——Windows服务小技巧

玩转Windows服务系列——Windows服务小技巧

 

当然,还有一种更简单的方法,可以将Debug和Release模式下的“子系统”项修改为“未设置”。这样编译器在编译链接时,会根据代码中的入口函数,自动将代码链接为对应的程序。如图

玩转Windows服务系列——Windows服务小技巧

 

注册服务为自动启动服务

注册服务的流程已经在前面的博客玩转Windows服务系列——Debug、Release版本的注册和卸载,及其原理中有介绍,这里就不再多说。重点说一下创建服务的Windows API CreateService。

如下是MSDN中的API声明

SC_HANDLE WINAPI CreateService(
_In_ SC_HANDLE hSCManager,
_In_ LPCTSTR lpServiceName,
_In_opt_ LPCTSTR lpDisplayName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwServiceType,
_In_ DWORD dwStartType,
_In_ DWORD dwErrorControl,
_In_opt_ LPCTSTR lpBinaryPathName,
_In_opt_ LPCTSTR lpLoadOrderGroup,
_Out_opt_ LPDWORD lpdwTagId,
_In_opt_ LPCTSTR lpDependencies,
_In_opt_ LPCTSTR lpServiceStartName,
_In_opt_ LPCTSTR lpPassword
);

参数太多,不一一介绍,详细介绍可以查看MSDN。

其中第六个参数,代表启动方式

dwStartType [in]
The service start options. This parameter can be one of the following values. Value Meaning
SERVICE_AUTO_START
0x00000002 A service started automatically by the service control manager during system startup. For more information, see Automatically Starting Services. SERVICE_BOOT_START
0x00000000 A device driver started by the system loader. This value is valid only for driver services. SERVICE_DEMAND_START
0x00000003 A service started by the service control manager when a process calls the StartService function. For more information, see Starting Services on Demand. SERVICE_DISABLED
0x00000004 A service that cannot be started. Attempts to start the service result in the error code ERROR_SERVICE_DISABLED. SERVICE_SYSTEM_START
0x00000001 A device driver started by the IoInitSystem function. This value is valid only for driver services.

SERVICE_AUTO_START 表示自动启动,这个参数就是我们想要的。

SERVICE_BOOT_START 也属于自动启动,但是只能用于内核服务。

SERVICE_DEMAND_START 手动启动,这是目前服务的默认启动方式。

SERVICE_DISABLED 禁止启动。

SERVICE_SYSTEM_START 属于自动启动,但只能用于内核服务。

所以,我们只需要在调用CreateService方法时,设置dwStartType参数为SERVICE_AUTO_START即可实现服务自动启动,而CreateService的其他参数,则参考现在的调用参数

::CreateService(
hSCM, m_szServiceName, m_szServiceName,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
szFilePath, NULL, NULL, _T("RPCSS\0"), NULL, NULL);

接下来,我们只需要重载命令行解析方法,添加参数用于确定是否自动启动即可。

 

注册服务时设置服务的依赖关系

设置服务的依赖关系仍然要看CreateService服务,这次我们看倒数第三个参数lpDependencies

lpDependencies [in, optional]
A pointer to a double null-terminated array of null-separated names of services or load ordering groups that the system must start before this service. Specify NULL or an empty string if the service has no dependencies. Dependency on a group means that this service can run if at least one member of the group is running after an attempt to start all members of the group. You must prefix group names with SC_GROUP_IDENTIFIER so that they can be distinguished from a service name, because services and service groups share the same name space.

lpDependencies是一个指针,指针指向了一个以’\0\0’结尾,并且以’\0’分割开的字符串,由’\0’分割开的字符串为依赖服务的名字。

比如,如果设置当前服务依赖RPCSS 和DependTest服务的,则可以这样调用CreateService方法

::CreateService(
hSCM, m_szServiceName, m_szServiceName,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
szFilePath, NULL, NULL, _T("RPCSS\0DependTest\0"), NULL, NULL);

由于字符串自身会以’\0’结尾,所以字符串内容中的结尾处只需写一个’\0’。

接下来,我们只需要重载命令行解析方法,添加参数用于确定是否自动启动即可

 

添加自定义命令行参数

添加自定义命令行参数 Auto, 用来设置启动方式为自动启动, 并且给Service参数添加依赖项,实现代码如下

// Services.cpp : WinMain 的实现

#include "stdafx.h"
#include "resource.h"
#include "Services_i.h" using namespace ATL; #include <stdio.h> #ifdef _DEBUG
#define OUT(s) printf_s(s);
#define OUT_LN(s) printf_s(s##"\r\n");
#else
#define OUT(s) OutputDebugString(s);
#define OUT_LN(s) OutputDebugString(s);
#endif class CServicesModule : public ATL::CAtlServiceModuleT< CServicesModule, IDS_SERVICENAME >
{
public :
DECLARE_LIBID(LIBID_ServicesLib)
DECLARE_REGISTRY_APPID_RESOURCEID(IDR_SERVICES, "{0794CF96-5CC5-432E-8C1D-52B980ACBE0F}")
HRESULT InitializeSecurity() throw()
{
// TODO : 调用 CoInitializeSecurity 并为服务提供适当的安全设置
// 建议 - PKT 级别的身份验证、
// RPC_C_IMP_LEVEL_IDENTIFY 的模拟级别
// 以及适当的非 NULL 安全描述符。 return S_OK;
}
//服务启动
HRESULT Load();
//服务停止
HRESULT UnLoad(); // Parses the command line and registers/unregisters the rgs file if necessary
bool ParseCommandLine(
_In_z_ LPCTSTR lpCmdLine,
_Out_ HRESULT* pnRetCode) throw();
//注册服务
BOOL Install() throw(); //重写此方法,主要是为了调用重写的Install方法
inline HRESULT RegisterAppId(_In_ bool bService = false) throw()
{
if (!Uninstall())
return E_FAIL; CServicesModule::UpdateRegistryAppId(TRUE); CRegKey keyAppID;
keyAppID.Open(HKEY_CLASSES_ROOT, _T("AppID"), KEY_WRITE); CRegKey key; key.Create(keyAppID, CServicesModule::GetAppIdT()); key.DeleteValue(_T("LocalService")); key.SetStringValue(_T("LocalService"), m_szServiceName); // Create service
if (!Install())
return E_FAIL;
return S_OK;
} HRESULT Run(_In_ int nShowCmd = SW_HIDE) throw()
{
HRESULT hr = S_OK;
OUT_LN("准备启动服务");
hr = Load();
if(hr)
{
OUT_LN("启动服务失败");
return hr;
}
OUT_LN("Services服务已启动"); hr = ATL::CAtlServiceModuleT< CServicesModule, IDS_SERVICENAME >::Run(nShowCmd); hr = UnLoad();
OUT_LN("Services服务正常退出");
return hr;
} private:
_TCHAR dependServices[256];
DWORD size;
DWORD dwStartType;
}; CServicesModule _AtlModule; //
#ifndef _DEBUG
extern "C" int WINAPI _tWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/,
LPTSTR /*lpCmdLine*/, int nShowCmd)
{
return _AtlModule.WinMain(nShowCmd);
}
#else
int _tmain(int argc, _TCHAR* argv[])
{
return _AtlModule.WinMain(SW_SHOW);
}
#endif HRESULT CServicesModule::Load()
{
memset(dependServices, 0, sizeof(dependServices));
dwStartType = SERVICE_DEMAND_START; OUT_LN("服务正在启动");
return 0;
} HRESULT CServicesModule::UnLoad()
{
OUT_LN("服务正在停止");
return 0;
} // Parses the command line and registers/unregisters the rgs file if necessary
bool CServicesModule::ParseCommandLine(
_In_z_ LPCTSTR lpCmdLine,
_Out_ HRESULT* pnRetCode) throw()
{
if (!CAtlExeModuleT<CServicesModule>::ParseCommandLine(lpCmdLine, pnRetCode))
return false; TCHAR szTokens[] = _T("-/");
*pnRetCode = S_OK;
LPCTSTR lpszToken = FindOneOf(lpCmdLine, szTokens);
while (lpszToken != NULL)
{
if (WordCmpI(lpszToken, _T("Service"))==0)
{
lpszToken += _tcslen(_T("Service"));
//循环读取依赖项
while (true)
{
LPCTSTR lpszTokenBegin = lpszToken;
while(isprint(*lpszToken) && *lpszToken != _T(' ') && *lpszToken != _T('-') && *lpszToken != _T('/'))
{
lpszToken++;
}
DWORD tokenSize = (lpszToken - lpszTokenBegin);
memcpy_s(dependServices + size, sizeof(dependServices) - size, lpszTokenBegin, tokenSize * sizeof(_TCHAR));
size += tokenSize;
dependServices[size] = _T('\0');
if(tokenSize)
{
size++;
} while(isprint(*lpszToken) && *lpszToken == ' ')
{
lpszToken++;
}
if((*lpszToken == _T('\0') || *lpszToken == _T('-') || *lpszToken == _T('/')))
{
dependServices[size + 1] = _T('\0');
break;
}
}
*pnRetCode = this->RegisterAppId(true);
if (SUCCEEDED(*pnRetCode))
*pnRetCode = this->RegisterServer(TRUE);
return false;
} //设置启动类型
if (WordCmpI(lpszToken, _T("Auto"))==0)
{
dwStartType = SERVICE_AUTO_START;
}
lpszToken = FindOneOf(lpszToken, szTokens);
}
return true;
} BOOL CServicesModule::Install() throw()
{
if (IsInstalled())
return TRUE; // Get the executable file path
TCHAR szFilePath[MAX_PATH + _ATL_QUOTES_SPACE];
DWORD dwFLen = ::GetModuleFileName(NULL, szFilePath + 1, MAX_PATH);
if( dwFLen == 0 || dwFLen == MAX_PATH )
return FALSE; // Quote the FilePath before calling CreateService
szFilePath[0] = _T('\"');
szFilePath[dwFLen + 1] = _T('\"');
szFilePath[dwFLen + 2] = 0; SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); //创建服务、根据命令行设置启动方式,设置依赖关系
SC_HANDLE hService = ::CreateService(
hSCM, m_szServiceName, m_szServiceName,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
dwStartType, SERVICE_ERROR_NORMAL,
szFilePath, NULL, NULL, dependServices, NULL, NULL); if (hService == NULL)
{
::CloseServiceHandle(hSCM);
return FALSE;
} ::CloseServiceHandle(hService);
::CloseServiceHandle(hSCM);
return TRUE;
}

 

自定义命令行参数演示

注册服务时使用如下命令行

Services.exe -Auto -service CryptSvc RPCSS DcomLaunch

注册后,效果如下

玩转Windows服务系列——Windows服务小技巧

玩转Windows服务系列——Windows服务小技巧

 

系列链接

玩转Windows服务系列——创建Windows服务

玩转Windows服务系列——Debug、Release版本的注册和卸载,及其原理

玩转Windows服务系列——无COM接口Windows服务启动失败原因及解决方案

玩转Windows服务系列——服务运行、停止流程浅析

玩转Windows服务系列——Windows服务小技巧

玩转Windows服务系列——命令行管理Windows服务

玩转Windows服务系列——Windows服务小技巧的更多相关文章

  1. 玩转Windows服务系列——Windows服务小技巧

    伴随着研究Windows服务,逐渐掌握了一些小技巧,现在与大家分享一下. 将Windows服务转变为控制台程序 由于默认的Windows服务程序,编译后为Win32的窗口程序.我们在程序启动或运行过程 ...

  2. 玩转Windows服务系列——Windows服务启动超时时间

    最近有客户反映,机房出现断电情况,服务器的系统重新启动后,数据库服务自启动失败.第一次遇到这种情况,为了查看是不是断电情况导致数据库文件损坏,从客户的服务器拿到数据库的日志,进行分析. 数据库工作机制 ...

  3. go微服务系列&lpar;二&rpar; - 服务注册&sol;服务发现

    目录 1. 服务注册 1.1 代码演示 1.2 在go run的时候传入服务注册的参数 2. 服务发现均衡负载 2.1 均衡负载算法 2.2 服务发现均衡负载的演示 1. 服务注册 1.1 代码演示 ...

  4. go微服务系列&lpar;三&rpar; - 服务调用&lpar;http&rpar;

    1. 关于服务调用 2. 基本方式调用服务 3. 服务调用正确姿势(初步) 3.1 服务端代码 3.2 客户端调用(重要) 1. 关于服务调用 这里的服务调用,我们调用的可以是http api也可以是 ...

  5. 玩转Windows服务系列&mdash&semi;&mdash&semi;给Windows服务添加COM接口

    当我们运行一个Windows服务的时候,一般情况下,我们会选择以非窗口或者非控制台的方式运行,这样,它就只是一个后台程序,没有界面供我们进行交互. 那么当我们想与Windows服务进行实时交互的时候, ...

  6. 玩转Windows服务系列——使用Boost&period;Application快速构建Windows服务

    玩转Windows服务系列——创建Windows服务一文中,介绍了如何快速使用VS构建一个Windows服务.Debug.Release版本的注册和卸载,及其原理和服务运行.停止流程浅析分别介绍了Wi ...

  7. 玩转Windows服务系列——Debug、Release版本的注册和卸载,及其原理

    Windows服务Debug版本 注册 Services.exe -regserver 卸载 Services.exe -unregserver Windows服务Release版本 注册 Servi ...

  8. 玩转Windows服务系列——创建Windows服务

    创建Windows服务的项目 新建项目->C++语言->ATL->ATL项目->服务(EXE) 这样就创建了一个Windows服务项目. 生成的解决方案包含两个项目:Servi ...

  9. 玩转Windows服务系列——无COM接口Windows服务启动失败原因及解决方案

    将VS创建的Windows服务项目编译生成的程序,通过命令行 “服务.exe -Service”注册为Windows服务后,就可以通过服务管理器进行管理了. 问题 通过服务管理器进行启动的时候,发现服 ...

随机推荐

  1. Dojo的subscribe和publish的简单使用

    问题描述: 假设在你的页面有一个对话框,对话框中包含FilteringSelect这样的需要store的控件,在打开对话框时需要对这个控件做两件事情,一是给该控件的store填充数据,二是要给该控件设 ...

  2. Python循环语句

    1.Python循环类型 1.while循环:在某条件下,循环执行某段程序 a. while语句有两个重要命令:continue,break来跳出循环. continue用来跳出该次循环 break用 ...

  3. 第七周PSP

    团队项目PSP 一:表格     C类型 C内容 S开始时间 E结束时间 I时间间隔 T净时间(mins) 预计花费时间(mins) 讨论 讨论用户界面 8:20 10:34 20 58 68 分析与 ...

  4. 理解RxJava&colon;&lpar;四&rpar;Reactive Android

    在前三部分,我在通用层面介绍了RxJava的工作原理.但是作为一个Android开发者,如何在工作中使用它呢?下面是一些给Android开发者的RxJava的具体应用. RxAndroid RxAnd ...

  5. centos 6&period;5下安装mysql&plus;nginx&plus;redmine 3&period;1&period;0 笔记

    centos 6.5下安装mysql+nginx+redmine 3.1.0 笔记 目录[-] 过程 1.安装RVM 2.利用rvm安装 Ruby 1.9.3 并设为默认 3.安装rails 4.安装 ...

  6. &lbrack;STL&rsqb;算法的泛化过程

    “选择了错误的算法,便注定了失败的命运”.最近对这句话感触颇深,经常因为一开始思路错误,修改半天,到头来却都是无用功,所以学好算法势在必行. 算法的泛化过程 如何设计一个算法,使他适用于任何(大多数) ...

  7. Qt学习之路(2)------Qt中的字符串类

    QString QString的一些基本用法 basic.cpp #include <QTextStream> int main(void) { QTextStream out(stdou ...

  8. linux服务器的性能分析与优化(十三)

    [教程主题]:1.linux服务器的性能分析与优化 [主要内容] [1]影响Linux服务器性能的因素 操作系统级 Ø CPU 目前大部分CPU在同一时间只能运行一个线程,超线程的处理器可以在同一时间 ...

  9. redis 管道技术 pipeline 简介

    redis数据库的主要瓶颈是网络速度,其次是内存与cpu.在应用允许的情况下,优先使用pipeline批量操作.pipeline批量发出请求/一次性获取响应:不是发出多个请求,每个请求都阻塞等待响应, ...

  10. 说一说ASP&period;NET web&period;config 加密及解密方法 (代码)

    /// <summary> /// 保护web.config的加密和解密 /// </summary> public class ProtectHelper { /// &lt ...