|
Usuários |
|
80 Usuários Online
|
|
[Artigos]
[Intermediário] - Criando um serviço do windows no Delphi |
Publicado por rboaro : Quinta, Maio 02, 2013 - 01:54 GMT-3 (1095 leituras)
3 Comentários Enviar para um amigo Versão para impressão
|
Uma boa opção para criação de uma aplicação que precisa estar no ar o tempo todo e não deve ou não precisa ter intervenção humana, é a criação de um serviço do windows. Criando um serviço do windows podemos passar a responsabilidade de iniciar a aplicação para o próprio windows, entre outras vantagens. Então mão na massa. O Delphi já possui um esqueleto pronto para a criação de um serviço do windows, basta ir em File -> New -> Other -> Delphi Projects -> Service Application:

Essa opção irá criar um novo projeto com uma unit contendo a classe do serviço (descendente da classe TService) já com dois métodos implementados, o ServiceController e o GetServiceController, métodos necessários para a execução do serviço, não sendo necessário realizar alterações. Essá unit criada contem uma espécie de DataModule que possui alguns eventos e propriedades cruciais para o funcionamento do serviço. Vejamos alguns dos principaiseventos de execução do serviço:
OnStart: Este evento é executado assim que o serviço é iniciado.
OnExecute: Este é o evento onde será implementado a funcionalidade do serviço, ele é executado logo após o evento OnStart, ou seja, quando o serviço entrar em execução definitiva. Funciona semelhante ao execute das threads.
OnStop: Este evento é executado assim que o serviço é parado.
OnPause: Este evento é executado quando o serviço é pausado.
OnContinue: Este evento é executando quando o serviço voltar a ser executado após uma pausa.
Alem dos eventos de execução, existem também os eventos de instalação do serviço:
BeforeInstall / AfterInstall: Eventos executados antes (before) ou depois (after) da instalação do serviço
Assim como os eventos, existem propriedades cruciais para o bom funcionamento de um serviço do windows, dentre essas propriedades, destaco as seguintes:
StartType: Esta propriedade define o modo de iniciação do serviço, por padrão essa opção vem marcada como stAuto que significa que o serviço será iniciado junto com o windows. Além da opção stAuto destaco a opção stManual que define que o serviço deverá ser iniciado manualmente e a opção stDisabled que define que somente um administrador pode iniciar o serviço.
DisplayName: Esta propriedade define o nome que será apresentado para o serviço no gerenciador de serviços do windows.
Dependencies: Define uma lista de dependências para a execução do serviço, ou seja, o serviço só será iniciado se todas as suas dependências (outros serviços) já estejam iniciados.
Conhecendo os principais eventos e principais propriedades da classe TService podemos começar a implementar nosso serviço do windows.
Como exemplo, faremos um serviço de autenticação via protocolo TCP/IP. Obviamente, será um exemplo didático com o único intuito de demonstrar a criação e o funcionamento de um serviço do windows, sendo assim, não entraremos em maiores detalhes de como essa autenticação será feita.
Para que seja mais fácil a manutenção do nosso código e a realização dos testes, vamos separar o código que gerencia o serviço do windows da nossa regra de negócios, para isso, o nosso processo de “autenticação” será feito através de um DataModule com um componente IdTCPServer.
Sem nos aprofundar muito na “regra de negócio”, temos a seguinte classe em nosso data module:
type
TdmNucleo = class(TDataModule)
IdTCPServer1: TIdTCPServer;
procedure IdTCPServer1Execute(AContext: TIdContext);
private
public
function IniciarServidorTCP() : boolean;
procedure PararServidorTCP();
function Autenticar(const AUserAndPass : string):boolean;
end;
Com a seguinte implementação:
function TdmNucleo.Autenticar(const AUserAndPass: string): boolean;
begin
Result := AnsiSameText(AUserAndPass,'admin@admin');
end;
function TdmNucleo.IniciarServidorTCP : boolean;
begin
try
Self.IdTCPServer1.DefaultPort := 9056;
Self.IdTCPServer1.Active := true;
Result := true;
except
Result := false;
end;
end;
procedure TdmNucleo.PararServidorTCP;
begin
Self.IdTCPServer1.Active := false;
end;
procedure TdmNucleo.IdTCPServer1Execute(AContext: TIdContext);
const
C_PROTOCOLO_REQUEST = 'TCP.Server.Autentication.Request';
C_PROTOCOLO_RESPONSE = 'TCP.Server.Autentication.Response';
var
sMsg: string;
begin
sMsg := AContext.Connection.IOHandler.ReadLn();
if AnsiSameText(sMsg,C_PROTOCOLO_REQUEST) then
begin
sMsg := AContext.Connection.IOHandler.ReadLn();
AContext.Connection.IOHandler.WriteLn(C_PROTOCOLO_RESPONSE);
AContext.Connection.IOHandler.WriteLn(IfThen(Self.Autenticar(sMsg),'OK','FALHA'));
end;
end;
Vale ressaltar para quem não conhece o funcionamento do componente IdTCPServer, o evento OnExecute é executado constantemente enquanto o objeto está ativo (propriedade Active setada como True), sendo assim, esse evento é utilizado para ficar “ouvindo” as requisições TCP/IP na porta em que o componente está configurado (propriedade Port). Como prometido, implementação extremamente simples, para que o foco não seja desviado e por falar em foco, voltemos ao assunto do post, o serviço do windows. Com nossa regra de negócio já resolvida, vamos preparar nosso serviço. Como optamos por separar a regra de negócio do controlador do serviço, a parte de fazer o serviço funcionar se tornou extremamente fácil. Para que a nossa “autenticação” já passe a funcionar, basta escrever o evento OnStart do nosso serviço:
procedure TsrvTCPServer.ServiceStart(Sender: TService; var Started: Boolean);
begin
Started := dmNucleo.IniciarServidorTCP();
end;
Veja que se, caso ocorra um erro no método dmNucleo.IniciarServidorTCP() e este retorne False, o serviço não será iniciado, graças ao parâmetro Started do evento OnStart, ou seja, o que determina se o serviço será iniciado ou não é o valor atribuído a este parâmetro. Caso tudo corra bem, isso é o suficiente para ver nosso serviço funcionando. Porém, vamos deixar nosso serviço mais completo, vamos nos certificar que o nosso componente IdTCPServer seja fechado quando o serviço parar, para isso, vamos utilizar o evento OnStop:
procedure TsrvTCPServer.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
dmNucleo.PararServidorTCP();
end;
E para finalizar e finalmente colocar o serviço para rodar, vamos definir um texto para servir de nome do serviço no gerenciador de serviços do windows. Como em nosso exemplo estamos monitorando a porta 9056, colocaremos um nome que descreva isso. Para isso, utilizaremos o propriedade DisplayName do serviço e para tornar essa rotina dinâmica, iremos configurar essa propriedade no evento OnBeforeInstall:
procedure TsrvTCPServer.ServiceBeforeInstall(Sender: TService);
begin
Self.DisplayName := 'TCP Server (PORTA: 9056)'
end;
Caso a escolha por qual porta monitorar estivesse dentro de um arquivo INI por exemplo e sejam necessárias varias instancias desse serviço, faz todo sentido mostrar no nome do serviço a qual porta ele está responsavél, por isso, é uma boa pratica atribuir a um evento a responsabilidade de nomear o serviço e na minha opinião o OnBeforeInstall é o melhor para isso. Outro recurso interessante é adicionar a descrição do serviço, porem isso é um pouco mais complicado, sendo necessário manipular o registro do windows, para isso, o mais correto é a utilização do evento OnAfterInstall, já que só conseguiremos atualizar as configurações do registro do windows para o serviço após realizarmos a instalação do mesmo:
procedure TsrvTCPServer.ServiceAfterInstall(Sender: TService);
var
regEdit : TRegistry;
begin
regEdit := TRegistry.Create(KEY_READ or KEY_WRITE);
try
regEdit.RootKey := HKEY_LOCAL_MACHINE;
if regEdit.OpenKey('\SYSTEM\CurrentControlSet\Services\' + Name,False) then
begin
regEdit.WriteString('Description','Servidor de autenticação TCP');
regEdit.CloseKey;
end;
finally
FreeAndNil(regEdit);
end;
end;
Finalmente podemos testar nosso serviço, só é preciso realizar a sua instalação no windows, o que é muito simples, via linha de comando, basta (como administrador) executar a seguinte instrução: “PATH_COMPLETO_DO_SERVIÇO /INSTALL”, exemplo:
D:\Projetos\SrvAutenticacao\Bin\SrvAutenticacao.exe /INSTALL
Após rodar o comando acima e obter uma mensagem de sucesso, nosso serviço já está instalado, basta agora dar o seu start.

Para desinstalar o serviço, basta (também como administrador) executar esta instrução:“PATH_COMPLETO_DO_SERVIÇO /UNINSTALL”, exemplo:
D:\Projetos\SrvAutenticacao\Bin\SrvAutenticacao.exe /UNINSTALL
E assim, o básico para criação de um serviço do windows está feito. Caso alguém esteja seguindo o exemplo e gostaria de testar, basta criar um aplicativo cliente com um IdTCPClient e fazer o seguinte código:
procedure TFrmTeste.TestarServicoTCPServer;
begin
Self.IdTCPClient1.Host := 'localhost';
Self.IdTCPClient1.Port := 9056;
Self.IdTCPClient1.Connect;
Self.IdTCPClient1.Socket.WriteLn('TCP.Server.Autentication.Request');
Self.IdTCPClient1.Socket.WriteLn('admin@admin');
if AnsiSameText(Self.IdTCPClient1.Socket.ReadLn(), 'TCP.Server.Autentication.Response') then
ShowMessage(Self.IdTCPClient1.Socket.ReadLn());
end;
Para finalizar, uma das coisas chatas de se trabalhar com um serviço do windows (ou qualquer aplicação que não requer interface gráfica) é realizar o debug. Nem sempre o recurso do Attach to Process funciona como desejamos. Pensando nisso, eu geralmente crio um formulário com uma interface GUI e utilizo ele para realizar testes e depurações. Agora vem a pergunta, “como farei para usar esse formulário?”, simples, criaremos a seguinte regra, se por linha de comando nosso serviço receber como parâmetro a palavra ”GUI“, significa que usaremos a interface gráfica, caso contrário, iniciaremos como um serviço normal.
Ótimo, mas como podemos fazer isso ? simples, basta usar esse macete no arquivo DPR do projeto:
var
sModo : string;
begin
sModo := ParamStr(1);
Application.CreateForm(TdmNucleo, dmNucleo);
if AnsiSameText(sModo,'GUI') then
begin
Vcl.forms.Application.CreateForm(TfrmGUIService, frmGUIService);
Vcl.forms.Application.Run;
end
else
begin
if not Vcl.SvcMgr.Application.DelayInitialize or Vcl.SvcMgr.Application.Installing then
Vcl.SvcMgr.Application.Initialize;
Vcl.SvcMgr.Application.CreateForm(TsrvTCPServer, srvTCPServer);
Vcl.SvcMgr.Application.Run;
end;
Basicamente, através da função ParamStr() que retorna as strings da linha de comando, estamos tomando a decisão se iremos iniciar a aplicação como uma aplicação VCL comum ou um serviço do windows. É possível sempre enviar o parâmetro GUI quando estiver executando o projeto pelo Delphi. Bastar ir em Project -> Options -> Debugger -> Parameters e colocar a palavra GUI, esta opção define quais serão os parâmetros de execução do projeto. Neste meu exemplo, quando eu estiver executando o projeto com o parâmetro GUI, terei uma interface como esta:

Com isso, fica muito mais fácil de realizar as depurações. Vale ressaltar também que isso foi possível pois optamos por não deixar a regra de negocio diretamente no serviço.
Diego Garcia
http://drgarcia1986.wordpress.com
http://twitter.com/drgarcia1986
|
|
Comentários | |
| | Comentários pertencem aos seus respectivos autores. Não somos responsáveis pelo seus conteúdos. |
por: tiagoshimizu (tiagoshimizu@hotmail.com)
: Mai 02, 2013 - 03:46 (Informações sobre o membro | Enviar uma mensagem)
http://http://
|
Só resaltando, não observei se está no texto, mas na função PararServidorTCP, para a linha "AContext.Connection.IOHandler.WriteLn(IfThen(Self.Autenticar(sMsg),'OK','FALHA'));", declarar a unit
StrUtils.
Parabéns pela matéria.
|
|
|
Edição 112 |
|
|
50 Programas Fontes |
|
|
Produtos |
|
|