2013年5月2日星期四

服务的基本框架

/*
文本就命名为:RegisterServiceCtrlHandler.Cpp吧!
made by correy
made at 2013.05.02
QQ:112426112
Email:kouleguan at hotmail dot com
Homepage:http://correy.webs.com

微软上有个服务的例子:A basic Windows service in C++,是用类写的:
http://code.msdn.microsoft.com/windowsdesktop/CppWindowsService-cacf4948

这个修改自msdn,比上面的简单,容易理解:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb540475(v=vs.85).aspx
其余可参考的还有:这是些辅助的功能。
http://msdn.microsoft.com/en-us/library/windows/desktop/bb540473(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/bb540474(v=vs.85).aspx

运行和调试方法:
命令行参数:install
然后:net start svcname
再:net stop svcname
或者:sc query svcname等。
*/

#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
//#include "sample.h"

#define SVCNAME TEXT("SvcName")

SERVICE_STATUS          gSvcStatus;
SERVICE_STATUS_HANDLE   gSvcStatusHandle;
HANDLE                  ghSvcStopEvent = NULL;

VOID SvcInstall(void);
VOID WINAPI SvcCtrlHandler( DWORD );
VOID WINAPI SvcMain( DWORD, LPTSTR * );

VOID ReportSvcStatus( DWORD, DWORD, DWORD );
VOID SvcInit( DWORD, LPTSTR * );
VOID SvcReportEvent( LPTSTR );

// Purpose: Entry point for the process
void __cdecl _tmain(int argc, TCHAR *argv[])
{
     DebugBreak();

    // If command-line parameter is "install", install the service. 如果命令行参数是:install就安装服务。  
    if( lstrcmpi( argv[1], TEXT("install")) == 0 )
    {
        SvcInstall();
        return;
    } else if ( lstrcmpi( argv[1], TEXT("start")) == 0 ) {
        //DoStartSvc();      
        return;
    } //等等,可以添加很多操作。

    // Otherwise, the service is probably being started by the SCM. 其他的就运行下面,下面的代码是作为服务运行的。

    // TO_DO: Add any additional services for the process to this table.
    SERVICE_TABLE_ENTRY DispatchTable[] =
    {
        { SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain },
        { NULL, NULL }
    };

    // This call returns when the service has stopped. The process should simply terminate when the call returns.
    if (!StartServiceCtrlDispatcher( DispatchTable ))
    {
        SvcReportEvent(TEXT("StartServiceCtrlDispatcher"));
    }
}

VOID SvcInstall()// Purpose: Installs a service in the SCM database
{
    SC_HANDLE schSCManager;
    SC_HANDLE schService;
    TCHAR szPath[MAX_PATH];

    if( !GetModuleFileName( NULL, szPath, MAX_PATH ) )  {
        printf("Cannot install service (%d)\n", GetLastError());
        return;
    }

    // Get a handle to the SCM database.
    schSCManager = OpenSCManager(
        NULL,                    // local computer
        NULL,                    // ServicesActive database
        SC_MANAGER_ALL_ACCESS);  // full access rights
    if (NULL == schSCManager) {
        printf("OpenSCManager failed (%d)\n", GetLastError());
        return;
    }

    // Create the service
    schService = CreateService(
        schSCManager,              // SCM database
        SVCNAME,                   // name of service
        SVCNAME,                   // service name to display
        SERVICE_ALL_ACCESS,        // desired access
        SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, // service type
        SERVICE_DEMAND_START,      // start type
        SERVICE_ERROR_NORMAL,      // error control type
        szPath,                    // path to service's binary
        NULL,                      // no load ordering group
        NULL,                      // no tag identifier
        NULL,                      // no dependencies
        NULL,                      // LocalSystem account
        NULL);                     // no password
    if (schService == NULL) {
        printf("CreateService failed (%d)\n", GetLastError());
        CloseServiceHandle(schSCManager);
        return;
    } else {
        printf("Service installed successfully\n");
    }

    CloseServiceHandle(schService);
    CloseServiceHandle(schSCManager);
}

// Purpose: Entry point for the service
//
// Parameters:
//   dwArgc - Number of arguments in the lpszArgv array
//   lpszArgv - Array of strings. The first string is the name of the service and subsequent strings are passed by the process
//     that called the StartService function to start the service.
VOID WINAPI SvcMain( DWORD dwArgc, LPTSTR *lpszArgv ) //net start svcname会走到这里。
{
    //运行这行代码需要开启Interactive Services Detection服务,并把服务设置为可交互的。
    //MessageBox(0,0,0,0);

    DebugBreak();//这个简单,附加即可。

    // Register the handler function for the service
    gSvcStatusHandle = RegisterServiceCtrlHandler( SVCNAME, SvcCtrlHandler);
    if( !gSvcStatusHandle ) {
        SvcReportEvent(TEXT("RegisterServiceCtrlHandler"));
        return;
    }
   
    gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; // These SERVICE_STATUS members remain as set here
    gSvcStatus.dwServiceSpecificExitCode = 0;
   
    ReportSvcStatus( SERVICE_START_PENDING, NO_ERROR, 3000 );// Report initial status to the SCM
   
    SvcInit( dwArgc, lpszArgv );// Perform service-specific initialization and work.
}

// Purpose: The service code
//
// Parameters:
//   dwArgc - Number of arguments in the lpszArgv array
//   lpszArgv - Array of strings. The first string is the name of the service and subsequent strings are passed by the process
//     that called the StartService function to start the service.
VOID SvcInit( DWORD dwArgc, LPTSTR *lpszArgv)
{
    // TO_DO: Declare and set any required variables.
    //   Be sure to periodically call ReportSvcStatus() with SERVICE_START_PENDING. If initialization fails, call ReportSvcStatus with SERVICE_STOPPED.

    // Create an event. The control handler function, SvcCtrlHandler,signals this event when it receives the stop control code.
    ghSvcStopEvent = CreateEvent(
        NULL,    // default security attributes
        TRUE,    // manual reset event
        FALSE,   // not signaled
        NULL);   // no name
    if ( ghSvcStopEvent == NULL) {
        ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
        return;
    }
   
    ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 );// Report running status when initialization is complete.

    // TO_DO: Perform work until service stops.
    //添加代码,大部分是开线程。

    //等待net stop svcname操作等。
    while(1)
    {      
        WaitForSingleObject(ghSvcStopEvent, INFINITE);// Check whether to stop the service.
        ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
        return;
    }
}

// Purpose:
//   Sets the current service status and reports it to the SCM.
//
// Parameters:
//   dwCurrentState - The current state (see SERVICE_STATUS)
//   dwWin32ExitCode - The system error code
//   dwWaitHint - Estimated time for pending operation, in milliseconds
VOID ReportSvcStatus( DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint)
{
    static DWORD dwCheckPoint = 1;

    // Fill in the SERVICE_STATUS structure.
    gSvcStatus.dwCurrentState = dwCurrentState;
    gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
    gSvcStatus.dwWaitHint = dwWaitHint;

    if (dwCurrentState == SERVICE_START_PENDING) {
        gSvcStatus.dwControlsAccepted = 0;
    } else {
        gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
    }

    if ( (dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED) ) {
        gSvcStatus.dwCheckPoint = 0;
    } else {
        gSvcStatus.dwCheckPoint = dwCheckPoint++;
    }
   
    SetServiceStatus( gSvcStatusHandle, &gSvcStatus );// Report the status of the service to the SCM.
}

// Purpose:      Called by SCM whenever a control code is sent to the service using the ControlService function.
// Parameters:   dwCtrl - control code
VOID WINAPI SvcCtrlHandler( DWORD dwCtrl )
{
    // Handle the requested control code.
    switch(dwCtrl)
    {
    case SERVICE_CONTROL_STOP: //net stop svcname等类似的操作会走到这里。
        ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
        SetEvent(ghSvcStopEvent);// Signal the service to stop.
        return;
    case SERVICE_CONTROL_INTERROGATE:
        // Fall through to send current status.
        break;
    default:
        break;
    }

    ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);
}

// Purpose:      Logs messages to the event log
// Parameters:   szFunction - name of function that failed
// Remarks:      The service must have an entry in the Application event log.
VOID SvcReportEvent(LPTSTR szFunction)
{
    HANDLE hEventSource;
    LPCTSTR lpszStrings[2];
    TCHAR Buffer[80];

    hEventSource = RegisterEventSource(NULL, SVCNAME);
    if( NULL != hEventSource )
    {
        StringCchPrintf(Buffer, 80, TEXT("%s failed with %d"), szFunction, GetLastError());

        lpszStrings[0] = SVCNAME;
        lpszStrings[1] = Buffer;

        ReportEvent(hEventSource,        // event log handle
            EVENTLOG_ERROR_TYPE, // event type
            0,                   // event category
            (DWORD)0xC0020100L,  //SVC_ERROR,           // event identifier
            NULL,                // no security identifier
            2,                   // size of lpszStrings array
            0,                   // no binary data
            lpszStrings,         // array of strings
            NULL);               // no binary data
        DeregisterEventSource(hEventSource);
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
改进点的如下:(没有测试!)
*/

#include <windows.h>
#include <strsafe.h>

#define SVCNAME TEXT("SvcName")

SERVICE_STATUS          gSvcStatus;
SERVICE_STATUS_HANDLE   gSvcStatusHandle;
HANDLE                  ghSvcStopEvent = NULL;

VOID SvcInstall()// Purpose: Installs a service in the SCM database
{
    SC_HANDLE schSCManager;
    SC_HANDLE schService;
    TCHAR szPath[MAX_PATH];

    if( !GetModuleFileName( NULL, szPath, MAX_PATH ) )  {
        printf("Cannot install service (%d)\n", GetLastError());
        return;
    }
   
    schSCManager = OpenSCManager(// Get a handle to the SCM database.
        NULL,                    // local computer
        NULL,                    // ServicesActive database
        SC_MANAGER_ALL_ACCESS);  // full access rights
    if (NULL == schSCManager) {
        printf("OpenSCManager failed (%d)\n", GetLastError());
        return;
    }
   
    schService = CreateService( // Create the service
        schSCManager,              // SCM database
        SVCNAME,                   // name of service
        SVCNAME,                   // service name to display
        SERVICE_ALL_ACCESS,        // desired access
        SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, // service type
        SERVICE_DEMAND_START,      // start type
        SERVICE_ERROR_NORMAL,      // error control type
        szPath,                    // path to service's binary
        NULL,                      // no load ordering group
        NULL,                      // no tag identifier
        NULL,                      // no dependencies
        NULL,                      // LocalSystem account
        NULL);                     // no password
    if (schService == NULL) {
        printf("CreateService failed (%d)\n", GetLastError());
        CloseServiceHandle(schSCManager);
        return;
    } else {
        printf("Service installed successfully\n");
    }

    CloseServiceHandle(schService);
    CloseServiceHandle(schSCManager);
}

VOID ReportSvcStatus( DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint)
/*
Purpose: Sets the current service status and reports it to the SCM.
Parameters:
   dwCurrentState - The current state (see SERVICE_STATUS)
   dwWin32ExitCode - The system error code
   dwWaitHint - Estimated time for pending operation, in milliseconds
*/
{
    static DWORD dwCheckPoint = 1;
   
    gSvcStatus.dwCurrentState = dwCurrentState;// Fill in the SERVICE_STATUS structure.
    gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
    gSvcStatus.dwWaitHint = dwWaitHint;

    if (dwCurrentState == SERVICE_START_PENDING) {
        gSvcStatus.dwControlsAccepted = 0;
    } else {
        gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
    }

    if ( (dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED) ) {
        gSvcStatus.dwCheckPoint = 0;
    } else {
        gSvcStatus.dwCheckPoint = dwCheckPoint++;
    }
   
    SetServiceStatus( gSvcStatusHandle, &gSvcStatus );// Report the status of the service to the SCM.
}

VOID SvcReportEvent(LPTSTR szFunction)
// Purpose:      Logs messages to the event log
// Parameters:   szFunction - name of function that failed
// Remarks:      The service must have an entry in the Application event log.
{
    HANDLE hEventSource;
    LPCTSTR lpszStrings[2];
    TCHAR Buffer[80];

    hEventSource = RegisterEventSource(NULL, SVCNAME);
    if( NULL != hEventSource )
    {
        StringCchPrintf(Buffer, 80, TEXT("%s failed with %d"), szFunction, GetLastError());

        lpszStrings[0] = SVCNAME;
        lpszStrings[1] = Buffer;

        ReportEvent(hEventSource,// event log handle
            EVENTLOG_ERROR_TYPE, // event type
            0,                   // event category
            (DWORD)0xC0020100L,  //SVC_ERROR,           // event identifier
            NULL,                // no security identifier
            2,                   // size of lpszStrings array
            0,                   // no binary data
            lpszStrings,         // array of strings
            NULL);               // no binary data
        DeregisterEventSource(hEventSource);
    }
}

VOID WINAPI SvcCtrlHandler( DWORD dwCtrl )
// Purpose:      Called by SCM whenever a control code is sent to the service using the ControlService function.
// Parameters:   dwCtrl - control code
{  
    switch(dwCtrl) // Handle the requested control code.
    {
    case SERVICE_CONTROL_STOP: //net stop svcname等类似的操作会走到这里。
        ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
        SetEvent(ghSvcStopEvent);// Signal the service to stop.
        return;
    case SERVICE_CONTROL_INTERROGATE:// Fall through to send current status.        
        break;
    default:
        break;
    }

    ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);
}

VOID WINAPI SvcMain( DWORD dwArgc, LPTSTR *lpszArgv ) //net start svcname会走到这里。
/*
Purpose: Entry point for the service
Parameters:
   dwArgc - Number of arguments in the lpszArgv array
   lpszArgv - Array of strings.
              The first string is the name of the service and subsequent strings are passed by the process that called the StartService function to start the service.
*/
{  
    gSvcStatusHandle = RegisterServiceCtrlHandler( SVCNAME, SvcCtrlHandler);
    if( !gSvcStatusHandle ) { // Register the handler function for the service:注册处理服务消息的回调函数.
        SvcReportEvent(TEXT("RegisterServiceCtrlHandler"));
        return;
    }
   
    gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; // These SERVICE_STATUS members remain as set here
    gSvcStatus.dwServiceSpecificExitCode = 0;
   
    ReportSvcStatus( SERVICE_START_PENDING, NO_ERROR, 3000 );// Report initial status to the SCM
   
    // Perform service-specific initialization and work.
    // TO_DO: Declare and set any required variables.
    // Be sure to periodically call ReportSvcStatus() with SERVICE_START_PENDING. If initialization fails, call ReportSvcStatus with SERVICE_STOPPED.

    // Create an event. The control handler function, SvcCtrlHandler,signals this event when it receives the stop control code.
    ghSvcStopEvent = CreateEvent(
        NULL,    // default security attributes
        TRUE,    // manual reset event
        FALSE,   // not signaled
        NULL);   // no name
    if ( ghSvcStopEvent == NULL) {
        ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
        return;
    }
   
    ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 );// Report running status when initialization is complete.

    // TO_DO: Perform work until service stops.
    //添加代码,大部分是开线程。
   
    while(1)//等待net stop svcname操作等。
    {      
        WaitForSingleObject(ghSvcStopEvent, INFINITE);// Check whether to stop the service.
        ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
        return;
    }
}

void __cdecl _tmain(int argc, TCHAR *argv[]) // Purpose: Entry point for the process
{    
    if( lstrcmpi( argv[1], TEXT("install")) == 0 )
    {// If command-line parameter is "install", install the service. 如果命令行参数是:install就安装服务。
        SvcInstall();
        return;
    } else if ( lstrcmpi( argv[1], TEXT("start")) == 0 ) {
        //DoStartSvc();      
        return;
    } //等等,可以添加很多操作。

    // Otherwise, the service is probably being started by the SCM. 其他的就运行下面,下面的代码是作为服务运行的。  
    SERVICE_TABLE_ENTRY DispatchTable[] =
    {
        { SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain }, // TO_DO: Add any additional services for the process to this table.
        { NULL, NULL }
    };    
    if (!StartServiceCtrlDispatcher( DispatchTable )) //启动服务,即服务入口.
    {// This call returns when the service has stopped. The process should simply terminate when the call returns.
        SvcReportEvent(TEXT("StartServiceCtrlDispatcher"));
    }
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
一些说明:

其实服务很简单:
1.程序的入口调用StartServiceCtrlDispatcher.
2.服务的入口调用RegisterServiceCtrlHandler.
3.服务的线程(即服务入口函数)要有循环,等待等函数,不然服务结束了.
4.服务的入口函数一般开启有线程.一个服务主线程很简单.
5.服务一般没有界面,避免处理消息.

关于服务的保护的一点内容:
1.保护服务不被停止.
   需要在服务的处理服务消息的回调函数中屏蔽掉这些消息(SERVICE_CONTROL_STOP),
   而且还要在服务的入口中设置SERVICE_STATUS类型的变量的dwControlsAccepted值中一定不要包含SERVICE_ACCEPT_STOP.
   这样做了,会导致在服务控制面板里面的此服务的停止按钮灰化,和net/sc stop命令失败.
2.保护服务不被卸载.
  一个办法是用驱动保护注册表,建议用注册表回调.说明:隐藏会有一些意外的效果,如查看,还有就是不能手工启动(计算机启动的时候可以启动的).
3.关于服务弹消息框的设置要点:
  1).vista以前只要设置服务为可交互式的(可手工修改,包括界面和注册表,也可以编程的时候修改代码),加MessageBox即可.
  2).vista及以后到win 8之前,这需要开启interactive service detection服务.
      命令行的操作是:net start ui0detect.
      这时弹出的消息不在本桌面,要切换桌面方能看到.
  3).win 8及以后.
      需要先修改HKLM\SYSTEM\CurrentControlSet\Control\Windows\NoInteractiveServices的值为0,原先默认的是1.
      再开启交互式的服务,不然会出错误的.
4.关于调试服务(包括服务的启动),请在搜索本站点.

made by correy
made at 2013.12.26
email:kouleguan at hotmail dot com
homepage:http://correy.webs.com
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

没有评论:

发表评论