1. 程式人生 > >玩轉Windows服務系列——服務執行、停止流程淺析

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

通過研究Windows服務註冊解除安裝的原理,感覺它並沒有什麼特別複雜的東西,Windows服務正在一步步退去它那神祕的面紗,至於是不是美女,大家可要睜大眼睛看清楚了。

接下來研究一下Windows服務的啟動和停止的流程。

啟動流程

啟動時自然是從程式的入口點開始

extern "C" int WINAPI _tWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, 
                                LPTSTR /*lpCmdLine*/, int nShowCmd)
{
    //這裡是程式的入口點,直接呼叫了ATL框架中的CServiceModuleT類的WinMain方法
return _AtlModule.WinMain(nShowCmd); }

接下來進入_AtlModule.WinMain檢視細節。

//處理命令列引數後,開始啟動
if (pT->ParseCommandLine(lpCmdLine, &hr) == true)
    hr = pT->Start(nShowCmd);

WinMain方法中,主要是對命令列引數進行處理後,呼叫Start方法進行啟動。

HRESULT Start(_In_ int nShowCmd) throw()
{
    T* pT = static_cast<T*>(this
); // Are we Service or Local Server CRegKey keyAppID; LONG lRes = keyAppID.Open(HKEY_CLASSES_ROOT, _T("AppID"), KEY_READ); if (lRes != ERROR_SUCCESS) { m_status.dwWin32ExitCode = lRes; return m_status.dwWin32ExitCode; } CRegKey key; lRes = key.Open(keyAppID, pT->GetAppIdT(), KEY_READ);
if (lRes != ERROR_SUCCESS) { m_status.dwWin32ExitCode = lRes; return m_status.dwWin32ExitCode; } TCHAR szValue[MAX_PATH]; DWORD dwLen = MAX_PATH; //讀取登錄檔資訊 //通過regserver方式註冊服務,則lRes為ERROR_SUCCESS //通過service方式註冊服務, 則lRes不等於ERROR_SUCCESS lRes = key.QueryStringValue(_T("LocalService"), szValue, &dwLen); m_bService = FALSE; if (lRes == ERROR_SUCCESS) m_bService = TRUE; if (m_bService) { //以Windows服務的方式執行 SERVICE_TABLE_ENTRY st[] = { { m_szServiceName, _ServiceMain }, { NULL, NULL } }; if (::StartServiceCtrlDispatcher(st) == 0) m_status.dwWin32ExitCode = GetLastError(); return m_status.dwWin32ExitCode; } // local server - call Run() directly, rather than // from ServiceMain() //以普通應用程式的方式執行 m_status.dwWin32ExitCode = pT->Run(nShowCmd); return m_status.dwWin32ExitCode; }

Start方法中會根據讀取到的登錄檔資訊,來決定是否以服務的方式執行。如果是通過RegServer方式註冊服務,則以普通程式執行;如果是通過Service方式註冊服務,則以Windows服務的方式執行。

普通程式方式執行,不在本文的討論範圍之內,下面來看一下以Windows服務方式執行的過程。

以Windows服務方式執行的話,程式會呼叫StartServiceCtrlDispatcher方法,看一下關於此方法的MSDN的解釋

Connects the main thread of a service process to the service control manager, which causes the thread to be the service control dispatcher thread for the calling process.


Remarks
When the service control manager starts a service process, it waits for the process to call the StartServiceCtrlDispatcher function. The main thread of a service process should make this call as soon as possible after it starts up (within 30 seconds). If StartServiceCtrlDispatcher succeeds, it connects the calling thread to the service control manager and does not return until all running services in the process have entered the SERVICE_STOPPED state. The service control manager uses this connection to send control and service start requests to the main thread of the service process. The main thread acts as a dispatcher by invoking the appropriate HandlerEx function to handle control requests, or by creating a new thread to execute the appropriate ServiceMain function when a new service is started.

這段話的大意是 “呼叫此方法可以與服務管理器建立連線,這樣服務管理器就可以管理服務的狀態”,實際是服務管理器發出命令後,服務可以對接收到的命令進行響應。

“當服務管理器啟動一個服務程序,服務管理器會等待服務程序呼叫StartServiceCtrlDispatcher方法。服務程序的主執行緒必須確保此方法在30秒內被儘快的執行。如果StartServiceCtrlDispatcher方法成功與服務管理器建立連線,那麼它會等到服務的狀態變為SERVICE_STOPPED後才返回”。

由此可知,當Start方法呼叫StartServiceCtrlDispatcher後,會進入到_ServiceMain方法。

void ServiceMain(
        _In_ DWORD dwArgc,
        _In_reads_(dwArgc) _Deref_pre_z_ LPTSTR* lpszArgv) throw()
{
    lpszArgv;
    dwArgc;
    // Register the control request handler
    m_status.dwCurrentState = SERVICE_START_PENDING;
    m_dwThreadID = GetCurrentThreadId();
    //註冊命令處理程式,用於響應服務管理器的控制命令
    RegisterServiceCtrlHandler(m_szServiceName, _Handler);

    //設定服務的狀態為已啟動
    SetServiceStatus(SERVICE_START_PENDING);

    m_status.dwWin32ExitCode = S_OK;
    m_status.dwCheckPoint = 0;
    m_status.dwWaitHint = 0;

    T* pT = static_cast<T*>(this);

    // When the Run function returns, the service has stopped.
    m_status.dwWin32ExitCode = pT->Run(SW_HIDE);
    //當Run方法結束後,會設定方法的狀態為已停止
    SetServiceStatus(SERVICE_STOPPED);
}

_ServiceMain方法中主要是註冊了一個服務控制命令的處理程式,然後設定服務的狀態為已啟動,然後呼叫Run方法。

HRESULT Run(_In_ int nShowCmd = SW_HIDE) throw()
{
    HRESULT hr = S_OK;
    T* pT = static_cast<T*>(this);
    //初始化Com相關的東西
    hr = pT->PreMessageLoop(nShowCmd);

    if (hr == S_OK)
    {
        //處理Msg訊息
        pT->RunMessageLoop();
    }

    if (SUCCEEDED(hr))
    {
        //釋放Com相關資源
        hr = pT->PostMessageLoop();
    }

    return hr;
}

Run方法中主要是迴圈處理Msg訊息,防止主執行緒退出。

至此,服務算是完全啟動了。

前面看到_ServiceMain方法中註冊了一個服務控制命令處理程式,接下來看一下這個方法做了什麼。

void Handler(_In_ DWORD dwOpcode) throw()
{
    T* pT = static_cast<T*>(this);

    switch (dwOpcode)
    {
        //停止命令
    case SERVICE_CONTROL_STOP:
        pT->OnStop();
        break;
        //暫停命令
    case SERVICE_CONTROL_PAUSE:
        pT->OnPause();
        break;
        //恢復命令
    case SERVICE_CONTROL_CONTINUE:
        pT->OnContinue();
        break;
    case SERVICE_CONTROL_INTERROGATE:
        pT->OnInterrogate();
        break;
    case SERVICE_CONTROL_SHUTDOWN:
        pT->OnShutdown();
        break;
    default:
        pT->OnUnknownRequest(dwOpcode);
    }
}

可以看到,這個方法根據不同的控制命令,做了不同的處理。

Windows服務的啟動流程總結

Windows服務啟動流程圖

停止流程

上面我們看到當服務接收到停止服務的命令後,Hanlder方法會通過呼叫pT->OnStop方法來進行處理。

void OnStop() throw()
{
    SetServiceStatus(SERVICE_STOP_PENDING);
    ::PostThreadMessage(m_dwThreadID, WM_QUIT, 0, 0);
}

OnStop方法中,通過SetServiceStatus方法來設定服務狀態為正在停止。並通過PostThreadMessage產生一個WM_QUIT的訊息。

void RunMessageLoop() throw()
{
    MSG msg;
    while (GetMessage(&msg, 0, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

此時RunMessageLoop中的While迴圈中斷。

While迴圈中斷後會通過SetServiceStatus設定服務狀態為已停止。

然後WinMain方法執行結束,服務進行退出。

這就是整個的服務停止流程。

Windows服務的停止流程總結

Windows服務停止流程圖

給服務新增自己的啟動和停止時的處理

既然已經對Windows服務的啟動和停止的流程有了一個大概的瞭解,那麼給服務新增自己的啟動和停止時的處理也就相對簡單了一些。

下面是我的實現程式碼

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();

    HRESULT Run(_In_ int nShowCmd = SW_HIDE) throw()
    {
        HRESULT hr = S_OK;
        OutputDebugString(_T("準備啟動服務"));
        hr = Load();
        if(hr)
        {
            OutputDebugString(_T("啟動服務失敗"));
            return hr;
        }
        OutputDebugString(_T("Services服務已啟動"));

        hr = ATL::CAtlServiceModuleT< CServicesModule, IDS_SERVICENAME >::Run(nShowCmd);

        hr = UnLoad();
        OutputDebugString(_T("Services服務正常退出"));
        return hr;
    }
};

CServicesModule _AtlModule;

//
extern "C" int WINAPI _tWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, 
                                LPTSTR /*lpCmdLine*/, int nShowCmd)
{
    return _AtlModule.WinMain(nShowCmd);
}


HRESULT CServicesModule::Load()
{
    OutputDebugString(_T("服務正在啟動"));
    return 0;
}

HRESULT CServicesModule::UnLoad()
{
    OutputDebugString(_T("服務正在停止"));
    return 0;
}

系列連結