Windows 编程之服务-1

来源:互联网 发布:宜兴俊知集团招聘2016 编辑:程序博客网 时间:2024/06/05 23:44

一、 编写服务程序

一个服务程序至少包括一个入口函数、服务主函数和控制处理函数。

1. 入口函数

 入口函数是可执行程序执行的起点,服务程序的入口函数与一般的可执行程序的入口函数 没有区别。

2. 服务主函数

一个服务程序必须具有服务主函数,服务主函数 是服务启动时执行的入口,也是服务的主线程执行起点。 

① 服务主函数 ServiceMain 

服务主函数一般称作ServiceMain函数。其函数名称没有任何要求,只是起参数接口和调用类型必须与要求一致。

ServiceMain函数原型如下:

VOID WINAPI ServiceMain(

DWORD dwArgc,

LPTSTR *argv);

服务主函数的参数与 main 函数的参数使用方法类似,但是服务主函数的参数不是通过在命 

令行启动时设定的,而是通过 SCM 的相关 API 进行传递的(StartService 函数)。

② 向 SCM 注册服务的主函数 StartServiceCtrIDispatcher

SCM 要对服务进行管理,就必须知道服务程序的服务主函数。服务程序通过调用 StartServiceCtrlDispatcher API函数设置服务主函数,同时通知SCM。 

v StartServiceCtrlDispatcher 函数原型如下:

BOOL WINAPI StartServiceCtrlDispatcher(

    _In_ CONST  SERVICE_TABLE_ENTRYW    *lpServiceStartTable

    );

结构 SERVICE_TABLE_ENIRY 的原型如下: 

typedef struct _SERVICE_TABLE_ENTRYW {

    LPWSTR                      lpServiceName;

    LPSERVICE_MAIN_FUNCTIONW    lpServiceProc;

}SERVICE_TABLE_ENTRYW, *LPSERVICE_TABLE_ENTRYW;

其中 lpServiceName 为服务名称,lpServiceProc 为指向 ServiceMain 的函数指针。只 要将函数的指针赋值给 lpServiceProc,再调用 StartServiceCtrlDispatcher,这个函数就 成为了服务主函数。

3. 控制处理函数

① 控制处理函数Handle

 控制处理函数用于处理SCM向服务传递的服务控制请求。控制处理函数Handler原型如下:

VOID WINAPI ServiceHandler(DWORD opcode);

② 注册控制管理函数

API 函数 RegisterServiceCtrlHandler 用于向 SCM 设置一个服务的控制处理函数。 

WINAPI

RegisterServiceCtrlHandlerW(

    _In_    LPCWSTR                         lpServiceName,

    _In_    __callback LPHANDLER_FUNCTION   lpHandlerProc

    );

 其中 lpServiceName 为服务名称,lpHandlerProc 为 Handler 函数指针。

二、 实现对服务的控制和管理

使用服 务管理 API 自行编写一个服务管理程序。服务管理程序为系统新建服务,负责启动停止服务 程序,并负责管理服务程序的属性等。 

1. 创建、删除服务

① OpenSCManager

WINAPI OpenSCManagerW(

    _In_opt_        LPCWSTR      lpMachineName,

    _In_opt_        LPCWSTR      lpDatabaseName,

    _In_            DWORD        dwDesiredAccess

    );

v 参数     

lpMachineName:输入参数,机器名,如果是本机则使用NULL。   

lpDatabaseName:输入参数,SCM 数据库名,设置为 NULL 或设置为 SERVICES ACTIVE DATABASE,效果等同。     

dwDesiredAccess:输入参数,需要的权限,可设置为 SC MANAGER ALL ACCESS、 SC_MANAGER_CREATE_SERVICESC_MANAGERCONNECT 等。     

v 返回值     

SC_HANDLE 类型,SCM 的句柄。

② CreateService

向系统创建服务,创建时指定服务的属性,函数原型如下:

SC_HANDLE CreateService(
SC_HANDLE hSCManager, //服务控制管理程序维护的登记数据库的句柄,由系统函数OpenSCManager 返回
LPCTSTR lpServiceName, //以NULL 结尾的服务名,用于创建登记数据库中的关键字
LPCTSTR lpDisplayName, //以NULL 结尾的服务名,用于用户界面标识服务
DWORD dwDesiredAccess, //指定服务返回类型
DWORD dwServiceType, //指定服务类型
DWORD dwStartType, //指定何时启动服务
DWORD dwErrorControl, //指定服务启动失败的严重程度
LPCTSTR lpBinaryPathName, //指定服务程序二进制文件的路径
LPCTSTR lpLoadOrderGroup, //指定顺序装入的服务组名
LPDWORD lpdwTagId, //忽略,NULL
LPCTSTR lpDependencies, //指定启动该服务前必须先启动的服务或服务组
LPCTSTR lpServiceStartName, //以NULL 结尾的字符串,指定服务帐号。如是NULL,则表示使用LocalSystem帐号
LPCTSTR lpPassword //以NULL 结尾的字符串,指定对应的口令。为NULL表示无口令。但使用LocalSystem时填NULL
);

 

v 参数

CreateService 函数的参数很多,但是设置方法都很简单,直接从参数名就可大体知道参数的意义。 

v 返回值 

返回 SC_HANDLE 类型,服务的句柄。

③ OpenService

打开服务,获取服务句柄,函数原型如下: 

WINAPI OpenServiceW(

    _In_            SC_HANDLE               hSCManager,

    _In_            LPCWSTR                lpServiceName,

    _In_            DWORD                   dwDesiredAccess

    );

④ DeleteService

删除服务,以服务句柄为参数,函数原型如下: 

WINAPI DeleteService(

    _In_        SC_HANDLE   hService

    );

2. 启动、停止服务、向服务发送控制请求

① StartService

启动服务,并设置服务主函数的参数。

BOOL WINAPI StartService(

  _In_      SC_HANDLE hService,

  _In_      DWORD dwNumServiceArgs,

  _In_opt_  LPCTSTR *lpServiceArgVectors

);

参数hService为服务句柄,后两个参数为服务主函数的参数,可以设置为 NULL,返回值表 示是否成功。 

② ControlService

向服务发送控制请求码。并获取服务状态。 

BOOL WINAPI ControlService(

  _In_   SC_HANDLE hService,

  _In_   DWORD dwControl,

  _Out_  LPSERVICE_STATUS lpServiceStatus

);

参数 hService 为服务句柄,dwControl 为控制码,lpServiceStatus 为返回的服务状态。 如果需要停止服务,需要使用 ControlService 向服务发送 SERVICE CONTROL_STOP 控制码。 

③ QueryServiceStatus

获得当前服务的状态。

BOOL WINAPI QueryServiceStatus(

  _In_   SC_HANDLE hService,

  _Out_  LPSERVICE_STATUS lpServiceStatus

);

3. SERVICE_STATUS结构各成员解析

在编写Windows服务的时候,需要调用API函数::SetServiceStatus()向服务控制管理器(SCM)提交更新当前服务的状态信息,其第2个参数为指向SERVICE_STATUS结构的指针,SERVICE_STATUS结构中包含了表示当前服务状态的信息,对其各成员一一分析:

typedef struct _SERVICE_STATUS {
  DWORD dwServiceType;
  DWORD dwCurrentState;
  DWORD dwControlsAccepted;
  DWORD dwWin32ExitCode;
  DWORD dwServiceSpecificExitCode;
  DWORD dwCheckPoint;
  DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;

dwServiceType:指明服务可执行文件的类型。如果可执行文件中只有一个单独的服务,就把这个成员设置成SERVICE_WIN32_OWN_PROCESS;如果拥有多个服务的话,就设置成SERVICE_WIN32_SHARE_PROCESS。除了这两个标志之外,如果你的服务需要和桌面发生交互(当然不推荐这样做),就要用“|”运算符附加上SERVICE_INTERACTIVE_PROCESS。这个成员的值在服务的生存期内绝对不应该改变。

dwCurrentState:用于通知SCM此服务的现行状态。为了报告服务仍在初始化,应该把这个成员设置成SERVICE_START_PENDING

dwControlsAccepted:指明服务接受什么样的控制通知。如果允许一个服务控制程序(SCP)去暂停/继续服务,就把它设成SERVICE_ACCEPT_PAUSE_CONTINUE。很多服务不支持暂停或继续,就必须自己决定在服务中它是否可用。如果你允许一个SCP去停止服务,就要设置它为SERVICE_ACCEPT_STOP。如果服务要在操作系统关闭的时候得到通知,设置它为SERVICE_ACCEPT_SHUTDOWN可以收到预期的结果。这些标志可以用“|”运算符组合。

dwWin32ExitCodedwServiceSpecificExitCode:是允许服务报告错误的关键,如果希望服务去报告一个Win32错误代码(预定义在WinError.h中),它就设置dwWin32ExitCode为需要的代码。一个服务也可以报告它本身特有的、没有映射到一个预定义的Win32错误代码中的错误。为了这一点,要把dwWin32ExitCode设置为ERROR_SERVICE_SPECIFIC_ERROR,然后还要设置成员dwServiceSpecificExitCode为服务特有的错误代码。当服务运行正常,没有错误可以报告的时候,就设置成员dwWin32ExitCodeNO_ERROR

dwCheckPointdwWaitHint:是一个服务用来报告它当前的事件进展情况的。当成员dwCurrentState被设置成SERVICE_START_PENDING的时候,应该把dwCheckPoint设成0dwWaitHint设成一个经过多次尝试后确定比较合适的超时毫秒数,这样服务才能高效运行。一旦服务被完全初始化,就应该重新初始化SERVICE_STATUS结构的成员,更改dwCurrentStateSERVICE_RUNNING,然后把dwCheckPointdwWaitHint都改为0

三、 践行渐远

实践环境:visual studio 2012  C++

编写一个服务应用.exe文件,可以用命令行建立、启动、停止、卸载服务。

可以在服务管理器上控制简单服务。


需要写的部分

1. 定义全局变量

#define LOGFILE "C:\\Myservice\\myservice.txt"

SERVICE_STATUS ServiceStatus;

SERVICE_STATUS_HANDLE ServiceStatusHandle;

TCHAR ServiceName[] = L"TestService";

2.声明几类函数

/*

 *服务主函数

 *服务控制护理函数

 *服务初始化函数

 *辅助函数

*/

int WriteToLog(char* str);

VOID WINAPI ServiceMain(DWORD argc,LPTSTR *argv);

VOID WINAPI ServiceHandler(DWORD opcode);

VOID  ServiceInitialization(DWORD argc ,LPTSTR *argv);

BOOL IsServiceInstalled();

BOOL InstallService();

BOOL UninstallService();

BOOL ServiceCtrlStart();

BOOL ServiceCtrlStop();

3.函数实现

① int WriteToLog(char* str);

int WriteToLog(char* str) 

SYSTEMTIME m_time;
FILE* log; 
printf("%s\n",str);
log = fopen(LOGFILE, "a+"); 
if (log == NULL) 
{
printf("write log failure\n");
return -1; 
}
GetLocalTime(&m_time);
fprintf(log, "%d-%d-%d  %d:%d:%d   **   %s\n", m_time.wYear,m_time.wMonth,m_time.wDay,m_time.wHour,m_time.wMinute,m_time.wSecond,str); 
fclose(log); 
return 0; 
}

这里面主要用的函数是:

fopen-以不同的方式打开文件、GetLocalTime-获得系统当前时间

主要目的:编写日志

② VOID WINAPI ServiceMain(DWORD argc,LPTSTR *argv);

VOID WINAPI ServiceMain(DWORD argc,LPTSTR *argv)
{
ServiceInitialization( argc ,argv);
//注册控制管理函数
ServiceStatusHandle = RegisterServiceCtrlHandler(ServiceName,ServiceHandler);
if(ServiceStatusHandle == NULL)
{
WriteToLog(" RegisterServiceCtrlHandler failure !");
return ;
}
else
{
WriteToLog(" RegisterServiceCtrlHandler successfully !");
}



//设置运行状态
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
if(!SetServiceStatus(ServiceStatusHandle,&ServiceStatus))//启动运行状态
{
WriteToLog(" SetServiceStatus failure");
}
//---------------------------------------------------------------------------------------

     //进行自己想要运行的操作


//-----------------------------------------------------------------------------------
return ;
}

这里面需要掌握的函数:

RegisterServiceCtrlHandler、SetServiceStatus以及SERVICE_STATUS结构的初始化。

③ VOID WINAPI ServiceHandler(DWORD opcode);

VOID WINAPI ServiceHandler(DWORD opcode)
{
switch (opcode)
{
case SERVICE_CONTROL_STOP:
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
if(!SetServiceStatus(ServiceStatusHandle,&ServiceStatus))
{
WriteToLog(" SetServiceStatus failure");
}
WriteToLog("stop success !");
return ;
case SERVICE_CONTROL_SHUTDOWN:
ServiceStatus.dwCurrentState = SERVICE_CONTROL_SHUTDOWN;
if(!SetServiceStatus(ServiceStatusHandle,&ServiceStatus))
{
WriteToLog(" SetServiceStatus failure");
}
WriteToLog("shutdown success !");
return ;
default:
WriteToLog("Bad service request");
}
return ;
}

④ VOID  ServiceInitialization(DWORD argc ,LPTSTR *argv);

VOID ServiceInitialization(DWORD argc ,LPTSTR *argv)

{

   //各种初始化工作

}

⑤ BOOL IsServiceInstalled();

BOOL IsServiceInstalled()
{
BOOL bResult = FALSE;
SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCM != NULL)
{
SC_HANDLE hService = ::OpenService(hSCM, ServiceName, SERVICE_QUERY_CONFIG);
if (hService != NULL)
{
bResult = TRUE;
::CloseServiceHandle(hService);
}
::CloseServiceHandle(hSCM);
}
if(bResult)
{
printf("have installed service.\n");
}
else
{
printf("no installed service.\n");
}
return bResult;
}

⑥ BOOL InstallService();

BOOL InstallService()
{
if (IsServiceInstalled())
return FALSE;
//open the SCM
SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCM == NULL)
{
return FALSE;
}


// Get the executable file path
TCHAR szFilePath[MAX_PATH];
::GetModuleFileName(NULL, szFilePath, MAX_PATH);

//create a service
SC_HANDLE hService = ::CreateService(
hSCM,
ServiceName,
ServiceName,
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL,
szFilePath,
NULL,
NULL,
NULL,
NULL,
NULL);
if (hService == NULL)
{
::CloseServiceHandle(hSCM);
return FALSE;
}
::CloseServiceHandle(hService);
::CloseServiceHandle(hSCM);
return TRUE;
}

⑦ BOOL UninstallService()

BOOL UninstallService()
{
if (!IsServiceInstalled())
return FALSE;
SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCM == NULL)
{
return FALSE;
}
SC_HANDLE hService = ::OpenService(hSCM, ServiceName, SERVICE_STOP | DELETE);
if (hService == NULL)
{
::CloseServiceHandle(hSCM);
return FALSE;
}
SERVICE_STATUS status;
::ControlService(hService, SERVICE_CONTROL_STOP, &status);
//delete service
BOOL bDelete = ::DeleteService(hService);
::CloseServiceHandle(hService);
::CloseServiceHandle(hSCM);
if (bDelete)
return TRUE;
WriteToLog("Service could not be deleted");
return FALSE;
}

⑧ BOOL ServiceCtrlStart();

BOOL ServiceCtrlStart()
{
BOOL bRet;
SC_HANDLE hSCM;
SC_HANDLE hService;
hSCM=OpenSCManager(NULL,NULL,SC_MANAGER_CONNECT);
if (hSCM!=NULL)
{
hService=OpenService( hSCM, ServiceName, SERVICE_START);
if (hService!=NULL)
{
//开始Service
bRet = StartService(hService,0,NULL);
CloseServiceHandle(hService);
}
else
bRet = FALSE;
CloseServiceHandle(hSCM);
}else
{
bRet = FALSE;
}
return bRet;
}

⑨ BOOL ServiceCtrlStop();

BOOL ServiceCtrlStop()
{
BOOL bRet;
SC_HANDLE hSCM;
SC_HANDLE hService;
SERVICE_STATUS ServiceStatus;
hSCM=OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
if (hSCM!=NULL)
{
hService=OpenService( hSCM, ServiceName, SERVICE_STOP|SERVICE_QUERY_STATUS );
if (hService!=NULL)
{
QueryServiceStatus( hService, &ServiceStatus );
if (ServiceStatus.dwCurrentState == SERVICE_RUNNING)
bRet = ControlService( hService, SERVICE_CONTROL_STOP, &ServiceStatus );
else
bRet = FALSE;
CloseServiceHandle( hService );
}
else
bRet = FALSE;
CloseServiceHandle( hSCM );
}
else
bRet = FALSE;


return bRet;
}

4.入口函数main

int main( int argc, char* argv[])
{
SERVICE_TABLE_ENTRY st[2];
st[0].lpServiceName  = ServiceName;
st[0].lpServiceProc = ServiceMain;
st[1].lpServiceName = NULL;
st[1].lpServiceProc = NULL;



LPSTR lpCmdLine = argv[1];


if ( argc == 2 )
{
BOOL bRet;
if(strcmp(lpCmdLine, "install") == 0 )
{
bRet = InstallService();
if ( bRet == TRUE )
wprintf( L"Install service %s success\n", ServiceName );
else
wprintf( L"Install service %s failed\n", ServiceName );
}
else
{
if(strcmp(lpCmdLine, "uninstall") == 0 )
{
bRet = UninstallService();
if ( bRet == TRUE )
wprintf(L"Uninstall service %s success\n", ServiceName );
else
wprintf( L"Uninstall service %s failed\n", ServiceName );
}
else
{
if (strcmp(lpCmdLine, "start") == 0)
{
bRet = ServiceCtrlStart();
if ( bRet == TRUE )
wprintf( L"Start service %s success\n", ServiceName );
else
wprintf( L"Start service %s failed\n", ServiceName );
}
else
{
if (strcmp(lpCmdLine, "stop") == 0)
{
bRet = ServiceCtrlStop();
if ( bRet == TRUE )
wprintf( L"Stop service %s success\n", ServiceName );
else
wprintf( L"Stop service %s failed\n", ServiceName );
}
else if (!::StartServiceCtrlDispatcher(st))
WriteToLog("Register Service Main Function Error!");
}
}
}
}


else
{
if (!::StartServiceCtrlDispatcher(st))
{    
WriteToLog("Register Service Main Function Error!");
}
}
return 0;
}



编译连接后生成个.exe文件,用管理员权限在.exe所在目录打开命令提示符

输入以下命令控制服务:

      ***.exe install  Enter

      ***.exe start Enter

      ***.exe stop Enter

      ***.exe uninstall  Enter

关于一个简单的服务就到此结束了!

下一次会讲解如何自定义服务控制码。

原创粉丝点击