伴隨著研究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
注冊後,效果如下

