玩轉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服務控制台輸出信息

當然,這還不是全部,還有一個地方需要設置,下面分別是Debug和Release下的設置

Windows服務Debug配置

Windows服務Release配置

 

當然,還有一種更簡單的方法,可以將Debug和Release模式下的“子系統”項修改為“未設置”。這樣編譯器在編譯鏈接時,會根據代碼中的入口函數,自動將代碼鏈接為對應的程序。如圖

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

注冊后,效果如下

Services服務自動啟動

Services服務依賴關系

 

系列鏈接

玩轉Windows服務系列——創建Windows服務

玩轉Windows服務系列——Debug、Release版本的注冊和卸載,及其原理

玩轉Windows服務系列——無COM接口Windows服務啟動失敗原因及解決方案

玩轉Windows服務系列——服務運行、停止流程淺析

玩轉Windows服務系列——Windows服務小技巧

玩轉Windows服務系列——命令行管理Windows服務

玩轉Windows服務系列——Windows服務啟動超時時間

玩轉Windows服務系列——使用Boost.Application快速構建Windows服務

玩轉Windows服務系列——給Windows服務添加COM接口


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM