Como depurar um Serviço do Windows (Windows Service)

Categoria: Artigos
Categoria Pai: Addicted 2 Delphi!
Acessos: 10232
Imagem meramente ilustrativa

Sem dúvida uma das características mais evidentes e úteis do Delphi é o seu depurador integrado à IDE. Se você é um bom programador fará bom uso dessa característica até o ponto em que você se torna dependente dela. Se isso é bom ou ruim, não cabe a ninguém julgar, pois características que nos ajudam a programar melhor e mais rápido são sempre bem vindas.

Certa vez me pediram para criar um Serviço de Windows. Era a primeira vez que eu estava desenvolvendo este tipo de coisa, logo, pesquisei na ajuda do próprio Delphi e vi que era bem simples. Como bom programador Delphi, eu fui em File > New > Other > Delphi Projects > Service Application e desenvolvi toda a lógica do serviço, só que ao pressionar F9, descobri que não era possível executar um TService. Ao pesquisar, descobri então que o mesmo precisaria ser instalado para que seu evento OnExecute (onde a lógica reside) fosse executado de fato.

Como sou muito fuçador, eu resolvi pesquisar muito na Internet, até que descobri um meio de realizar a depuração de um Serviço de Windows sem a necessidade de instalá-lo, então eu evoluí o exemplo que eu encontrei, até transformá-lo em um TForm simples com controles para, iniciar, parar e pausar qualquer serviço, e mais: o código para isso funcionar pode ser posto diretamente no código-fonte de um projeto já existente e detecta automaticamente quando estamos executando o serviço por meio da IDE do Delphi, altera a forma de inicialização do serviço e mostra o TForm com os controles. Ao executar o mesmo serviço por fora do Delphi, o mesmo se comporta como um serviço qualquer.

Como utilizar a unit UFormServiceTester?

A fim de não tornar este artigo longo, vou direto ao ponto. Ao criar um TService no Delphi, o seu arquivo de projeto (dpr) vai conter o seguinte código:

program MeuServico;

uses
  SvcMgr,
  UServico in 'UServico.pas' {Servico: TServico};

{$R *.RES}

begin
  // Windows 2003 Server requires StartServiceCtrlDispatcher to be
  // called before CoRegisterClassObject, which can be called indirectly
  // by Application.Initialize. TServiceApplication.DelayInitialize allows
  // Application.Initialize to be called from TService.Main (after
  // StartServiceCtrlDispatcher has been called).
  //
  // Delayed initialization of the Application object may affect
  // events which then occur prior to initialization, such as
  // TService.OnCreate. It is only recommended if the ServiceApplication
  // registers a class object with OLE and is intended for use with
  // Windows 2003 Server.
  //
  // Application.DelayInitialize := True;
  //
  if not Application.DelayInitialize or Application.Installing then
    Application.Initialize;

  Application.CreateForm(TServico, Servico);
  Application.Run;
end.

Remova as linhas comentadas deste fonte (para melhor legibilidade) e adicione ao projeto a unit UFormServiceTester. O fonte acima vai ficar como segue:

program MeuServico;

uses
  SvcMgr,
  UServico in 'UServico.pas' {Servico: TServico},
  UFormServiceTester in 'UFormServiceTester.pas' {FormServiceTester};

{$R *.RES}

begin
  if not Application.DelayInitialize or Application.Installing then
    Application.Initialize;

  Application.CreateForm(TServico, Servico);
  Application.CreateForm(TFormServiceTester, FormServiceTester);
  Application.Run;
end.

Note que o Delphi, tentou adicionar o TFormServiceTester de forma que ele seja automaticamente criado (linha 15). Isso não está correto, logo, simplesmente remova esta linha até que o fonte fique exatamente como abaixo:

program MeuServico;

uses
  SvcMgr,
  UServico in 'UServico.pas' {Servico: TServico},
  UFormServiceTester in 'UFormServiceTester.pas' {FormServiceTester};

{$R *.RES}

begin
  if not Application.DelayInitialize or Application.Installing then
    Application.Initialize;

  Application.CreateForm(TServico, Servico);
  Application.Run;
end.

Neste ponto nós temos o mesmo serviço original, só que com uma unit adicional e que não faz nada. Vamos agora utilizar efetivamente a classe TFormServiceTester. Observe o fonte abaixo e deixe seu fonte tal como ele:

program MeuServico;

uses
  SvcMgr,
  Forms,
  UServico in 'UServico.pas' {Servico: TServico},
  UFormServiceTester in 'UFormServiceTester.pas' {FormServiceTester};

{$R *.RES}

begin
  if DebugHook <> 0 then
  begin
    Forms.Application.Initialize;
    FormServiceTester := TFormServiceTester.Create(TServico);
    Forms.Application.Run;
  end
  else
  begin
    if not SvcMgr.Application.DelayInitialize or SvcMgr.Application.Installing then
      SvcMgr.Application.Initialize;

    SvcMgr.Application.CreateForm(TServico, Servico);
    SvcMgr.Application.Run;
  end;
end.

Vou explicar rapidamente alguns elementos do fonte acima. Primeiramente note que na linha 5 foi incluída a unit Forms. Esta unit é necessária para que o form TFormServiceTester possa ser criado, pois ela contém o objeto global Application que define um programa simples de Delphi. Isso é necessário para que o serviço se comporte como um programa qualquer, mas introduz um problema. Na linha 4 existe a unit SvcMgr, a qual é responsável por definir o serviço do windows. Esta unit também contém um objeto global de nome Application, ou seja, temos dois objetos globais de nome Application; um deles define um programa simples e o outro define um serviço do Windows. Para resolver este problema e diferenciar quem é quem, precisamos qualificar o objeto Application, incluindo como prefixo o nome da unit a qual ele pertence. É por isso que nas linhas 14 a 16 nós vemos Forms.Application e nas linhas 20 a 24 nós vemos SvcMgr.Application. Assim nós sabemos exatamente quem é quem. Ao falar Forms.Application nos referimos a um programa tradicional e ao falar em SvcMgr.Application, nos referimos a um Windows Service.

Ao executarmos este programa dentro da IDE do delphi, ele tem que se comportar como um programa simples e exibir TFormServiceTester como form principal, e ao executar ele fora do Delphi ele deve se comportar como um serviço do Windows. Para que isso seja possível é utilizada a variável global DebugHook (linha 12). Caso o valor dessa variável em tempo de execução seja diferente de zero, significa que estamos executando o projeto dentro da IDE do Delphi, em outras palavras, o estamos debugando. Caso esta variável tenha um valor zero, estamos executando o programa fora do Delphi. Com a utilização da variável DebugHook fica fácil dividir o código em duas partes. Caso estejamos executando o programa a partir da IDE do Delphi, as linhas 14 a 16 serão executadas. Note que olhando estas linhas de forma isolada nos vemos algo muito parecido com aquilo que veríamos ao criar um projeto novo no Delphi. Caso estejamos executando o programa fora do Delphi, as linhas 20 a 24 serão executadas. Elas representam exatamente aquilo que havia neste fonte (dpr) antes da adaptação para utilização de TFormServiceTester.

É isso! Aproveite o código anexado a este artigo e comece ainda hoje a debugar seus serviços de Windows. Não serão mais toleradas desculpas depois da leitura deste artigo :)

  Arquivos anexados  
Arquivo Descrição Tamanho Modificado em
Download this file (UFST.zip) UFST Esta é a unit UFormServiceTester.pas juntamente com seu dfm correspondente. Com ela, é possível depurar um serviço do Windows 40 KB 20/09/2016 às 17:50