windows服务编写和“以管理员运行”程序的方法

本文将首先解释如何 创建 一个定期查询可用物理内存并将结果写入某个文本文件的服务。然后指导你完成生成,安装和实现服务的整个过程。 


第一步:主函数和全局定义 

首先,包含所需的头文件。例子要调用 Win32 函数(windows.h )和磁盘文件写入(stdio.h ): 

接着,定义两个常量: 

#define SLEEP_TIME 5000
#define LOGFILE "C:\MyServices\memstatus.txt"
SLEEP_TIME 指定两次连续查询可用内存之间的毫秒间隔。在第二步中编写服务工作循环的时候要使用该常量。 
LOGFILE 定义日志文件的路径,你将会用 WriteToLog 函数将内存查询的结果输出到该文件,WriteToLog 函数定义如下: 

int WriteToLog(char* str)
{
     FILE* log;
     log = fopen(LOGFILE, "a+");//如果VS非TMD提示不安全,则在“属性->配置属性->c/c++->命令行”中添加“/D_CRT_SECURE_NO_WARNINGS ”
     if (log == NULL)
     return -1;
     fprintf(log, "%s ", str);
     fclose(log);
     return 0;
}
声明几个全局变量,以便在程序的多个函数之间共享它们值。此外,做一个函数的前向定义: 

SERVICE_STATUS ServiceStatus; 
SERVICE_STATUS_HANDLE hStatus; 

void ServiceMain(int argc, char** argv); 
void ControlHandler(DWORD request); 
int InitService();
  现在,准备工作已经就绪,你可以开始编码了。服务程序控制台程序的一个子集。因此,开始你可以定义一个 main 函数,它是程序的入口点。对于服务程序来说,main 的代码令人惊讶地简短,因为它只创建分派表并启动控制分派机。 

void main() 

     SERVICE_TABLE_ENTRY ServiceTable[2];
     ServiceTable[0].lpServiceName = "MemoryStatus";
     ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
    
     ServiceTable[1].lpServiceName = NULL;
     ServiceTable[1].lpServiceProc = NULL;

     // 启动服务的控制分派机线程 
     StartServiceCtrlDispatcher(ServiceTable); 
}
  一个程序可能包含若干个服务。每一个服务都必须列于专门的分派表中(为此该程序定义了一个 ServiceTable 结构数组)。这个表中的每一项都要在 SERVICE_TABLE_ENTRY 结构之中。它有两个域: 

lpServiceName: 指向表示服务名称字符串的指针;当定义了多个服务时,那么这个域必须指定; 
lpServiceProc: 指向服务主函数的指针(服务入口点); 
  分派表的最后一项必须是服务名和服务主函数域的 NULL 指针,文本例子程序中只宿主一个服务,所以服务名的定义是可选的。 
  服务控制管理器(SCM :Services Control Manager )是一个管理系统所有服务的进程。当 SCM 启动某个服务时,它等待某个进程的主线程来调用 StartServiceCtrlDispatcher 函数。将分派表传递给 StartServiceCtrlDispatcher 。这将把调用进程的主线程转换为控制分派器。该分派器启动一个新线程,该线程运行分派表中每个服务的 ServiceMain 函数(本文例子中只有一个服务)分派器还监视程序中所有服务的执行情况。然后分派器将控制请求从 SCM 传给服务。 

注意:如果 StartServiceCtrlDispatcher 函数30 秒没有被调用,便会报错,为了避免这种情况,我们必须在 ServiceMain 函数中(参见本文例子)或在非主函数的单独线程中初始化服务分派表。本文所描述的服务不需要防范这样的情况。 

  分派表中所有的服务执行完之后(例如,用户通过“ 服务” 控制面板程序停止它们),或者发生错误时。StartServiceCtrlDispatcher 调用返回。然后主进程终止。 


第二步:ServiceMain 函数 

  Listing 1 展示了 ServiceMain 的代码。该函数是服务的入口点。它运行在一个单独的线程当中,这个线程是由控制分派器创建的。ServiceMain 应该尽可能早早为服务注册控制处理器。这要通过调用 RegisterServiceCtrlHadler 函数来实现。你要将两个参数传递给此函数:服务名和指向 ControlHandlerfunction 的指针。 
  它指示控制分派器调用 ControlHandler 函数处理 SCM 控制请求。注册完控制处理器之后,获得状态句柄(hStatus )。通过调用 SetServiceStatus 函数,用 hStatus 向 SCM 报告服务的状态。 
Listing 1 展示了如何指定服务特征和其当前状态来初始化 ServiceStatus 结构,ServiceStatus 结构的每个域都有其用途: 

dwServiceType :指示服务类型,创建 Win32 服务。赋值 SERVICE_WIN32 ; 
dwCurrentState :指定服务的当前状态。因为服务的初始化在这里没有完成,所以这里的状态为 SERVICE_START_PENDING ; 
dwControlsAccepted :这个域通知 SCM 服务接受哪个域。本文例子是允许 STOP 和 SHUTDOWN 请求。处理控制请求将在第三步讨论; 
dwWin32ExitCode 和 dwServiceSpecificExitCode :这两个域在你终止服务并报告退出细节时很有用。初始化服务时并不退出,因此,它们的值为 0 ; 
dwCheckPoint 和 dwWaitHint :这两个域表示初始化某个服务进程时要30 秒以上。本文例子服务的初始化过程很短,所以这两个域的值都为 0 。 
  调用 SetServiceStatus 函数向 SCM 报告服务的状态时。要提供 hStatus 句柄和 ServiceStatus 结构。注意 ServiceStatu 一个全局变量,所以你可以跨多个函数使用它。ServiceMain 函数中,你给结构的几个域赋值,它们在服务运行的整个过程中都保持不变,比如:dwServiceType 。 
  在报告了服务状态之后,你可以调用 InitService 函数来完成初始化。这个函数只是添加一个说明性字符串到日志文件。如下面代码所示: 

// 服务初始化 
int InitService() 

     int result;
     result = WriteToLog("Monitoring started.");
     return(result); 
}
  在 ServiceMain 中,检查 InitService 函数的返回值。如果初始化有错(因为有可能写日志文件失败),则将服务状态置为终止并退出 ServiceMain : 

error = InitService(); 
if (error) 
{
     // 初始化失败,终止服务 
     ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
     ServiceStatus.dwWin32ExitCode = -1; 
     SetServiceStatus(hStatus, &ServiceStatus); 
     // 退出 ServiceMain
     return; 
}
如果初始化成功,则向 SCM 报告状态: 

// 向 SCM 报告运行状态 
ServiceStatus.dwCurrentState = SERVICE_RUNNING; 
SetServiceStatus (hStatus, &ServiceStatus);
接着,启动工作循环。每五秒钟查询一个可用物理内存并将结果写入日志文件。 

如 Listing 1 所示,循环一直到服务的状态为 SERVICE_RUNNING 或日志文件写入出错为止。状态可能在 ControlHandler 函数响应 SCM 控制请求时修改。 


第三步:处理控制请求 

  在第二步中,你用 ServiceMain 函数注册了控制处理器函数。控制处理器与处理各种 Windows 消息的窗口回调函数非常类似。它检查 SCM 发送了什么请求并采取相应行动。 
  每次你调用 SetServiceStatus 函数的时候,必须指定服务接收 STOP 和 SHUTDOWN 请求。Listing 2 示范了如何在 ControlHandler 函数中处理它们。 
  STOP 请求是 SCM 终止服务的时候发送的。例如,如果用户在“ 服务” 控制面板中手动终止服务。SHUTDOWN 请求是关闭机器时,由 SCM 发送给所有运行中服务的请求。两种情况的处理方式相同: 

写日志文件,监视停止; 
向 SCM 报告 SERVICE_STOPPED 状态; 
  由于 ServiceStatus 结构对于整个程序而言为全局量,ServiceStatus 中的工作循环在当前状态改变或服务终止后停止。其它的控制请求如:PAUSE 和 CONTINUE 在本文的例子没有处理。 
  控制处理器函数必须报告服务状态,即便 SCM 每次发送控制请求的时候状态保持相同。因此,不管响应什么请求,都要调用 SetServiceStatus 。 

第四步:安装和配置服务 

  程序编好了,将之编译成 exe 文件。本文例子创建的文件叫 MemoryStatus.exe ,将它拷贝到 C:MyServices 文件夹。为了在机器上安装这个服务,需要用 SC.EXE 可执行文件,它是 Win32 Platform SDK 中附带的一个工具。(译者注:Visaul Studio .NET 2003 IDE 环境中也有这个工具,具体存放位置在:C:Program FilesMicrosoft Visual Studio .NET 2003Common7ToolsBinwinnt )。使用这个实用工具可以安装和移除服务。其它控制操作将通过服务控制面板来完成。以下是用命令行安装 MemoryStatus 服务的方法: 

sc create MemoryStatus binpath= c:MyServicesMemoryStatus.exe
  发出此创建命令。指定服务名和二进制文件的路径(注意 binpath= 和路径之间的那个空格)。安装成功后,便可以用服务控制面板来控制这个服务。用控制面板的工具栏启动和终止这个服务。 

 MemoryStatus 的启动类型是手动,也就是说根据需要来启动这个服务。右键单击该服务,然后选择上下文菜单中的“ 属性” 菜单项,此时显示该服务的属性窗口。在这里可以修改 启动类型以及其它设置。你还可以从“ 常规” 标签中启动/ 停止服务。以下是从系统中移除服务的方法: 

sc delete MemoryStatus
指定 “delete” 选项和服务名。此服务将被标记为删除,下次西通重启后,该服务将被完全移除。 
第五步:测试服务 

  从服务控制面板启动 MemoryStatus 服务。如果初始化不出错,表示启动成功。过一会儿将服务停止。检查一下 C:MyServices 文件夹中 memstatus.txt 文件的服务输出。
  为了测试 MemoryStatus 服务在出错情况下的行为,可以将 memstatus.txt 文件设置成只读。这样一来,服务应该无法启动。 
  去掉只读属性,启动服务,在将文件设成只读。服务将停止执行,因为此时日志文件写入失败。如果你更新服务控制面板的内容,会发现服务状态是已经停止。 

#include "stdafx.h"
#include <windows.h>
#pragma comment(lib, "Advapi32")
#define SLEEP_TIME 5000
#define LOGFILE "d:\memstatus.txt"

SERVICE_STATUS ServiceStatus; 
SERVICE_STATUS_HANDLE hStatus; 
void ServiceMain(int argc, char** argv); 
void ControlHandler(DWORD request); 
int InitService();
int WriteToLog(char* str);

int main(int argc, char* argv[])
{
    SERVICE_TABLE_ENTRY ServiceTable[2];
    ServiceTable[0].lpServiceName = "MemoryStatus";
    ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
    
    ServiceTable[1].lpServiceName = NULL;
    ServiceTable[1].lpServiceProc = NULL;
    // 启动服务的控制分派机线程
    StartServiceCtrlDispatcher(ServiceTable); 

  //注册并启动服务,需要以管理员身份运行VS
   system("sc create abcde binPath= "E:\visual studio 2012\Projects\sc_study\Debug\sc_study.exe "");//服务名任意
    system("sc start abcde");
 // system("sc stop abcde");
 // system("sc delete abcde");
    system("pause"); }
void ServiceMain(int argc, char** argv) { int error; ServiceStatus.dwServiceType = SERVICE_WIN32; ServiceStatus.dwCurrentState = SERVICE_START_PENDING; ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwServiceSpecificExitCode = 0; ServiceStatus.dwCheckPoint = 0; ServiceStatus.dwWaitHint = 0; hStatus = RegisterServiceCtrlHandler( "MemoryStatus", (LPHANDLER_FUNCTION)ControlHandler); if (hStatus == (SERVICE_STATUS_HANDLE)0) { // Registering Control Handler failed return; } // Initialize Service error = InitService(); if (!error) { // Initialization failed ServiceStatus.dwCurrentState = SERVICE_STOPPED; ServiceStatus.dwWin32ExitCode = -1; SetServiceStatus(hStatus, &ServiceStatus); return; } // We report the running status to SCM. ServiceStatus.dwCurrentState = SERVICE_RUNNING; SetServiceStatus (hStatus, &ServiceStatus); MEMORYSTATUS memory; // The worker loop of a service while (ServiceStatus.dwCurrentState == SERVICE_RUNNING) { char buffer[16]; GlobalMemoryStatus(&memory); sprintf_s(buffer, "%d", memory.dwAvailPhys); int result = WriteToLog(buffer); if (result) { ServiceStatus.dwCurrentState = SERVICE_STOPPED; ServiceStatus.dwWin32ExitCode = -1; SetServiceStatus(hStatus, &ServiceStatus); return; } Sleep(SLEEP_TIME); } return; } void ControlHandler(DWORD request) { switch(request) { case SERVICE_CONTROL_STOP: WriteToLog("Monitoring stopped."); ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwCurrentState = SERVICE_STOPPED; SetServiceStatus (hStatus, &ServiceStatus); return; case SERVICE_CONTROL_SHUTDOWN: WriteToLog("Monitoring stopped."); ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwCurrentState = SERVICE_STOPPED; SetServiceStatus (hStatus, &ServiceStatus); return; default: break; } // Report current status SetServiceStatus (hStatus, &ServiceStatus); return; } int WriteToLog(char* str) { FILE* log; log = fopen(LOGFILE, "a+"); if (log == NULL) return -1; fprintf(log, "%s ", str); fclose(log); return 0; } int InitService(){ WriteToLog("Monitoring started."); return true; }

 以管理员身份运行程序的步骤:

1、在VS“配置属性”->"连接器"->"清单文件"->"UAC执行级别"选择requireAdministrator

2、重新编译工程,此时生成的exe图标上就出现个小盾牌,运行时就提示以管理员身份运行了

原文地址:https://www.cnblogs.com/duyy/p/3746695.html