Como depurar um Serviço do Windows (Windows Service)
Escrito por Carlos B. Feitoza Filho | |
Categoria: Artigos | |
Categoria Pai: Addicted 2 Delphi! | |
Acessos: 10232 |
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 :)