伴隨着研究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注冊服務,通過服務管理器管理服務的運行和停止。
當然,這還不是全部,還有一個地方需要設置,下面分別是Debug和Release下的設置
當然,還有一種更簡單的方法,可以將Debug和Release模式下的“子系統”項修改為“未設置”。這樣編譯器在編譯鏈接時,會根據代碼中的入口函數,自動將代碼鏈接為對應的程序。如圖
注冊服務為自動啟動服務
注冊服務的流程已經在前面的博客玩轉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服務系列——Debug、Release版本的注冊和卸載,及其原理
玩轉Windows服務系列——無COM接口Windows服務啟動失敗原因及解決方案
玩轉Windows服務系列——Windows服務啟動超時時間