如何在Delphi中启动Windows 8的服务

3

我需要使用Delphi Windows应用程序启动一个服务。在Windows 7中,它可以正常工作,但在Windows 8.1中无法工作。我使用了以下代码:

function ServiceStart(sMachine,sService : string ) : boolean;
var
  schm,schs   : SC_Handle;
  ss     : TServiceStatus;
  psTemp : PChar;
  dwChkP : DWord;
begin
  ss.dwCurrentState := 0;
  schm := OpenSCManager(PChar(sMachine),Nil,SC_MANAGER_CONNECT);
  if(schm > 0)then
  begin
    schs := OpenService(schm,PChar(sService),SERVICE_START or SERVICE_QUERY_STATUS);
    if(schs > 0)then
    begin
      psTemp := Nil;
      if(StartService(schs,0,psTemp))then
      begin
        if(QueryServiceStatus(schs,ss))then
        begin
          while(SERVICE_RUNNING <> ss.dwCurrentState)do
          begin
            dwChkP := ss.dwCheckPoint;
            Sleep(ss.dwWaitHint);
            if(not QueryServiceStatus(schs,ss))then
            begin
              break;
            end;
            if(ss.dwCheckPoint < dwChkP)then
            begin
              break;
            end;
          end;
        end;
      end;
      CloseServiceHandle(schs);
    end;
    CloseServiceHandle(schm);
  end;


  Result := SERVICE_RUNNING = ss.dwCurrentState;
end;


procedure TForm1.BBSerStatusClick(Sender: TObject);
begin
  ServiceStart('','SERVTEST');
end;

注意:SERVTEST是一项服务应用程序。 有人能帮我吗?

1
那么发生了什么?哪个部分失败了?错误代码是什么?告诉我们你的调试情况。请记住,Stack Overflow不能替代调试。 - David Heffernan
我假设你的程序正在以管理员权限运行。 - David Heffernan
以上代码在Windows 7上运行良好,但在Windows 8上,该代码仅安装服务而不启动服务。 - Amaladoss R
2
如果您尝试从服务管理中启动服务,您的服务是否会启动?您应该首先检查这个。 - SilverWarior
1
StartService 的文档明确说明 如果函数失败,返回值为0。要获取扩展错误信息,请调用 GetLastError 我没有看到调用 GetLastError 来查看为什么 StartService 没有工作。显然,它提供的 扩展错误信息 在这里会很有用,你不觉得吗? - Ken White
显示剩余3条评论
1个回答

13

我看到您使用了从这里复制的代码。

if(schm > 0)thenif(schs > 0)then应该改为if(schm <> 0)thenif(schs <> 0) then。在这种情况下,唯一的失败值是0(一些API使用INVALID_HANDLE_VALUE,但SCM API不使用)。任何其他值都是有效的句柄。句柄实际上并不是整数(尽管Delphi将它们声明为整数),因此您不应将它们视为整数。它们是任意值,不应被解释,而应按原样使用。如果未返回实际的失败值(在本例中为0),则调用无论实际返回什么值都成功。

ss.dwCurrentState的处理也有点问题。不要在ss.dwCurrentState不是SERVICE_RUNNING时循环,而应在ss.dwCurrentStateSERVICE_START_PENDING时循环。如果出现问题,服务从未进入SERVICE_RUNNING状态,则循环将持续运行,除非QueryServiceStatus()本身失败。我不建议依赖ss.dwCheckPoint,因为并不是所有服务都正确实现它(事实上,Delphi自己的TService没有 - 请参见QC#1006 TService.ReportStatus报告了错误的CheckPoint)。

尝试以下内容。它区分SCM API故障和服务启动故障,但还进行了额外的错误检查以处理某些实际上并非致命错误的错误:

function ServiceStart(sMachine, sService : string) : Boolean;
var
  schm, schs : SC_HANDLE;
  ss : TServiceStatus;
begin
  schm := OpenSCManager(PChar(sMachine), nil, SC_MANAGER_CONNECT);
  if (schm = 0) then RaiseLastOSError;
  try
    schs := OpenService(schm, PChar(sService), SERVICE_START or SERVICE_QUERY_STATUS);
    if (schs = 0) then RaiseLastOSError;
    try
      // NOTE: if you use a version of Delphi that incorrectly declares
      // StartService() with a 'var' lpServiceArgVectors parameter, you
      // can't pass a nil value directly in the 3rd parameter, you would
      // have to pass it indirectly as either PPChar(nil)^ or PChar(nil^)
      if not StartService(schs, 0, nil) then
      begin
        Result := ERROR_SERVICE_ALREADY_RUNNING = GetLastError();
        if not Result then RaiseLastOSError;
        Exit;
      end;
      repeat
        if not QueryServiceStatus(schs, ss) then
        begin
          if (ERROR_SERVICE_NOT_ACTIVE <> GetLastError()) then RaiseLastOSError;
          Result := False;
          Exit;
        end;
        if (SERVICE_START_PENDING <> ss.dwCurrentState) then Break;
        Sleep(ss.dwWaitHint);
      until False;
      Result := SERVICE_RUNNING = ss.dwCurrentState;
    finally
      CloseServiceHandle(schs);
    end;
  finally
    CloseServiceHandle(schm);
  end;
end;

或者,这里是微软示例的一个(修改过的)版本,还包括在启动服务之前处理服务处于SERVICE_STOP_PENDING状态的情况(我删除了基于dwCheckPoint处理的超时逻辑):

启动服务:

function ServiceStart(sMachine, sService : string) : Boolean;
var
  schSCManager,
  schService : SC_HANDLE;
  ssStatus : TServiceStatus;
begin
  // Get a handle to the SCM database.

  schSCManager := OpenSCManager(PChar(sMachine), nil, SC_MANAGER_CONNECT);
  if (schSCManager = 0) then RaiseLastOSError; 
  try
    // Get a handle to the service.

    schService := OpenService(schSCManager, PChar(sService), SERVICE_START or SERVICE_QUERY_STATUS);
    if (schService = 0) then RaiseLastOSError;
    try
      // Check the status in case the service is not stopped.

      if not QueryServiceStatus(schService, ssStatus) then
      begin
        if (ERROR_SERVICE_NOT_ACTIVE <> GetLastError()) then RaiseLastOSError;
        ssStatus.dwCurrentState := SERVICE_STOPPED;
      end;

      // Check if the service is already running

      if (ssStatus.dwCurrentState <> SERVICE_STOPPED) and ssStatus.dwCurrentState <> SERVICE_STOP_PENDING) then
      begin
        Result := True;
        Exit;
      end;

      // Wait for the service to stop before attempting to start it.

      while (ssStatus.dwCurrentState = SERVICE_STOP_PENDING) do
      begin
        // Do not wait longer than the wait hint. A good interval is
        // one-tenth of the wait hint but not less than 1 second
        // and not more than 10 seconds.

        dwWaitTime := ssStatus.dwWaitHint div 10;

        if (dwWaitTime < 1000) then
          dwWaitTime := 1000
        else if (dwWaitTime > 10000) then
          dwWaitTime := 10000;

        Sleep(dwWaitTime);

        // Check the status until the service is no longer stop pending.

        if not QueryServiceStatus(schService, ssStatus) then
        begin
          if (ERROR_SERVICE_NOT_ACTIVE <> GetLastError()) then RaiseLastOSError;
          Break;
        end;
      end;

      // Attempt to start the service.

      // NOTE: if you use a version of Delphi that incorrectly declares
      // StartService() with a 'var' lpServiceArgVectors parameter, you
      // can't pass a nil value directly in the 3rd parameter, you would
      // have to pass it indirectly as either PPChar(nil)^ or PChar(nil^)
      if not StartService(schService, 0, nil) then RaiseLastOSError;

      // Check the status until the service is no longer start pending.

      if not QueryServiceStatus(schService, ssStatus) then
      begin
        if (ERROR_SERVICE_NOT_ACTIVE <> GetLastError()) then RaiseLastOSError;
        ssStatus.dwCurrentState := SERVICE_STOPPED;
      end;

      while (ssStatus.dwCurrentState = SERVICE_START_PENDING) do
      begin
        // Do not wait longer than the wait hint. A good interval is
        // one-tenth the wait hint, but no less than 1 second and no
        // more than 10 seconds.

        dwWaitTime := ssStatus.dwWaitHint div 10;

        if (dwWaitTime < 1000) then
          dwWaitTime := 1000
        else if (dwWaitTime > 10000) then
          dwWaitTime := 10000;

        Sleep(dwWaitTime);

        // Check the status again.

        if not QueryServiceStatus(schService, ssStatus) then
        begin
          if (ERROR_SERVICE_NOT_ACTIVE <> GetLastError()) then RaiseLastOSError;
          ssStatus.dwCurrentState := SERVICE_STOPPED;
          Break;
        end;
      end;

      // Determine whether the service is running.

      Result := (ssStatus.dwCurrentState = SERVICE_RUNNING);
    finally
      CloseServiceHandle(schService); 
    end;
  finally
    CloseServiceHandle(schSCManager);
  end;
end;

1
SC_HANDLE是有符号的还是无符号的? - David Heffernan
1
在 C 语言中,Win32 API 中的 SC_HANDLE 根据是否定义了严格模式 STRICT 而声明为 struct SC_HANDLE__ *void*。 在 Delphi 中,像 SC_HANDLE 这样的 Win32 API 句柄被声明为简单的 THandle,这取决于平台是 Cardinal 还是 NativeUInt - Remy Lebeau
在这种情况下,>0<>0 在语义上是相同的。我完全同意 <>0 在美学上更好,但这并不会解释任何行为改变。 - David Heffernan
Remy,如果可以的话,我会接受你的答案,但我只能给你一个+1。我测试了你的第一个“ServiceStart”例程,它完美地工作了。谢谢!你觉得,如果我开始一个新的问题,你能发表一个如何停止服务的答案吗?我看到在op复制他的示例的链接中也有一个Stop过程,但如果你说它有漏洞,我就不相信那段代码了。 :) - Marus Gradinaru
1
@MarusNebunu:只需使用与上面类似的代码,将“start”部分替换为相应的“stop”部分(SERVICE_START-> SERVICE_STOPStartService()-> ControlService(SERVICE_CONTROL_STOP)SERVICE_START_PENDING-> SERVICE_STOP_PENDINGSERVICE_RUNNING-> SERVICE_STOPPED等)。 - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接