伴隨著研究Windows服務,逐漸掌握了一些小技巧,現在與大家分享一下。
將Windows服務轉變為控制台程序
由於默認的Windows服務程序,編譯後為Win32的窗口程序。我們在程序啟動或運行過程中,如果想看到一些調試信息,那麼就只能通過DebugView或者輸出到日志的方式了。因為如果我們通過printf或者std::cout輸出調試信息的話,Win32窗口程序是無法顯示的。
此時,我們是多麼懷念我們的經典的控制台程序啊,它可以很方便的將我們的調試信息輸出出來,簡直是太方便了。既然如此,那我們就讓它一秒鐘變格格吧,額,應該是一秒鐘變控制台。
下面分享一下我的實現代碼
Collapse#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模式下的“子系統”項修改為“未設置”。這樣編譯器在編譯鏈接時,會根據代碼中的入口函數,自動將代碼鏈接為對應的程序。如圖
注冊服務為自動啟動服務
查看本欄目
添加自定義命令行參數
添加自定義命令行參數 Auto, 用來設置啟動方式為自動啟動, 並且給Service參數添加依賴項,實現代碼如下
Collapse// 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
注冊後,效果如下