如何使用Delphi调试Windows服务?

27

有没有一种用Delphi完全调试Windows服务的方法?

7个回答

26
你可以使用Colin Wilson的NT低级实用工具中的unitDebugService.pas文件(该页面已经消失,可以在wayback machine上找到)。
然后在DPR中:
begin
  if (paramCount > 0) and (SameText(ParamStr(1), '-DEBUG')) then
  begin
    FreeAndNil (Application);
    Application := TDebugServiceApplication.Create(nil);
  end;

  //... the rest of the normal DPR code
end.

通过设置项目调试器参数,您可以在Delphi中进行调试运行,也可以将EXE用作服务或使用-DEBUG开关从命令行运行。


26

其实很简单。只需使用标准的DEBUG编译器指令,将服务启动为控制台应用程序而不是服务即可。

program MyServiceApp;

{$ifdef DEBUG}
  {$APPTYPE CONSOLE}
{$endif}

uses
  System.SysUtils,

[..]

begin
  {$ifdef DEBUG}
  try
    // In debug mode the server acts as a console application.
    WriteLn('MyServiceApp DEBUG mode. Press enter to exit.');

    // Create the TService descendant manually.
    ServerContainer1 := TServerContainer.Create(nil);

    // Simulate service start.
    ServerContainer1.ServiceStart(ServerContainer1, MyDummyBoolean);

    // Keep the console box running (ServerContainer1 code runs in the background)
    ReadLn;

    // On exit, destroy the service object.
    FreeAndNil(ServerContainer1);
  except
    on E: Exception do
    begin
      Writeln(E.ClassName, ': ', E.Message);
      WriteLn('Press enter to exit.');
      ReadLn;
    end;
  end;
  {$else}
  // Run as a true windows service (release).
  if not Application.DelayInitialize or Application.Installing then
    Application.Initialize;
  Application.CreateForm(TServerContainer, ServerContainer1);
  Application.Run;
  {$endif}
end.

这是非常好的方法!我们需要编写代码,但不能使用第三方单元、函数或复杂的Windows注册表配置。 - Carlos B. Feitoza Filho
1
Delphi 10.2中的TServerContainer在哪里?属于哪个单元? - Shahram Banazadeh
1
@ShahramBanazadeh TServerContainer是您为服务指定的类名。默认情况下是TService1。 - Massimo Fazzolari
除了从控制台运行,服务在第3环而不是第2环中运行,对吗? - J...
MyDummyBoolean是什么?只是任何东西吗? - Dreamer64
@Dreamer64 在这种情况下,ServiceStart方法需要一个发送器和一个“started”变量,因此“MyDummyBoolean”接收服务是否已启动的信息,在代码中仅用于调用该方法。 - Joao Ishiwatari

15

使用运行 -> 附加进程。这样您可以在不对其代码进行任何更改的情况下调试服务。唯一棘手的部分可能是调试服务启动代码,因为附加可能需要一些时间,并且启动必须在30秒内发生(尽管您可以调整 Windows 以允许更长时间)。您可以使用延迟(睡眠...)来允许您及时附加,或者如果您只需要查看发生了什么,可以使用 OutputDebugString() 打印到调试输出(使用 Delphi 事件视图查看)。


我已经尝试过这个,但只出现了带有汇编代码的CPU窗口。 - Daniel Grillo
“附加到进程”功能正常。当调试器首次连接到服务进程时,CPU 窗口确实会出现,但您可以简单地将其关闭并按 F9 或运行按钮以继续正常执行服务,然后您可以像任何其他项目一样调试其代码。 - Remy Lebeau
1
这个运行得很好。如果你不想弹出CPU窗口,可以取消“附加后暂停”复选框的勾选。 - GolezTrol

6
是的,有这样一种方法:调试服务:一种简单的方式 如果您是使用Delphi创建服务的话,那么也许您会对每次启动、重启、杀死和附加到服务进程应用程序所需的耗时感到烦恼。不过,有解决办法。
您无需这样做。相反,将Delphi作为系统应用程序运行,并对服务代码进行一些小的适应即可。

RunAsSys听起来不错。您链接的文章中的示例代码使用条件定义来选择“模式”。您知道是否可以在不重新构建应用程序的情况下选择调试模式吗?即使用命令行参数进入调试模式,而不是作为服务运行? - Marjan Venema
我不确定,但似乎你可以自己为它添加那个功能。 - Mick

6
我尝试过这个方法,但只出现CPU窗口和汇编代码。
那么你只需要解决这个问题。
基本上,调试Win2服务有几种方法:
- 使用“附加到进程”命令将调试器附加到已运行的服务。如果您需要在开头附加,则可以插入启动延迟以获得时间。但是,您还必须调整系统以增加服务超时时间。 - 使用{{link1:“Image File Execution Options”注册表键}}来强制在服务启动时运行Delphi的调试器。同样,系统超时的考虑也适用。 - 将服务临时转换为普通应用程序,并在调试器下正常运行。您可以重新启动不同用户帐户下的IDE以获取更多“服务”的特权。
如果由于某种原因,在开始调试后您只能看到您的服务的CPU视图 - 这意味着Delphi的调试器找不到您的服务的调试信息。这是另一个问题,您应该寻找解决方案。
通常,您需要执行以下操作:
  1. 确保您的服务应用程序的输出文件夹设置为系统将加载它的文件夹。即,如果您的服务位于C:\ Windows \ System32中,则将输出文件夹设置为C:\ Windows \ System32。不要从输出文件夹复制.exe文件到其他位置。对于64位系统,您可以尝试别名(sysnative / SysWOW64)或真实名称。我认为最好将输出路径设置为项目文件夹,并重新注册要从项目文件夹加载的服务。
  2. (可选)将DCU的输出路径设置为与.exe文件相同的文件夹。
  3. 删除所有DCU文件。
  4. 确保在项目选项的“编译器”页面上启用了调试选项。
  5. (可选)此外,您还可以在“链接器”页面上包括TD32 / RSM / MAP选项。
  6. 确保没有IDE专家/扩展程序会修改.exe文件、调试信息或这些文件的文件修改日期。
  7. 确保其他位置没有旧文件(DCU / exe)。
  8. 进行完整重建(项目/全部构建)。
  9. 运行您的服务。

3

有的。

在你的dpr中:

Application.CreateForm(TMyService, MyService);

_ServiceInDebugMode := SysUtils.FindCmdLineSwitch('DEBUG', True);
if _ServiceInDebugMode then
  DebugService(MyService)
else
  SvcMgr.Application.Run;

DebugService是一个过程,它创建了一个调试表单、一个服务控制线程,并通过调用Forms.Application.Run启动所有内容。

您可以将服务控制线程与Windows的SCM(服务控制管理器)进行比较,将调试表单与与SCM通信的应用程序进行比较(例如services.msc),以启动和停止服务。该服务还应具有自己的线程(服务线程),以响应来自SCM或我们的服务控制线程的控制代码以及一个或多个单独的线程来执行其实际工作。您需要为实际工作使用单独的线程(而不是在TService派生类的事件处理程序中编码),以确保TService本身运行的线程始终空闲以响应来自SCM的控制代码,并且即使工作线程被冻结,您仍然可以停止和启动服务。

这种方法也允许您调试服务应用程序代码,但涉及相当数量的代码和将一些钩子放入Windows API函数中才能正常工作。这里无法在短时间内展示太多。也许我会在某天写一篇文章来详细介绍。

与此同时,如果您不想全部自己编写代码,则有两个选择。要么使用诸如SVCOM或Mick提到的库等,它们可以为您完成所有工作;要么在调试模式下完全绕过服务代码,并将其“简单”地启动为“普通”窗体应用程序。您将需要从TService派生类的代码/事件处理程序中解开您服务的实际功能,但出于上述原因,我建议这样做。


我有一个带有表单和服务的相同应用程序。使用表单可以工作,但是使用服务却无法工作。 - Daniel Grillo
@Marjan Venema DebugService 位于哪个单元中? - Spongebob Comrade
@SpongebobComrade 没有现成的。这是你自己编写的东西。它应该在代码下的第一段落中提到要做什么。查看其他答案以获取更多信息。 - Marjan Venema

0
需要知道的是,您可以轻松调试服务的安装和卸载。
以管理员身份运行Delphi,将"/install"或"/uninstall"命令行参数添加到"运行/参数..."对话框中的"参数"设置中,并使用调试运行服务应用程序。

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