Um servidor Datasnap é na excência um servidor HTTP nativo…. SIM é possível implementar um HTTP Server no mesmo servidor de Datasnap.

1. Criar um Servidor Datasnap padrão

Ao criar um Servidor DataSnap (pelo expert do Delphi), várias UNITs são adicionadas.
Se observar a UNIT que nos importa aqui é a WebModuleUnit1;

Toda vez que é feito uma chamada a um recurso na porta do HTTP do servidor e for um arquivo será executado o evento “WebModuleBeforeDispatch” o que permite inserir códigos que execute algum procedimento diferente daqueles previstos.

No nosso caso queremos chamar um arquivo externo “index.html”, “jquery.js”, “minhafoto.png”, “my.pdf”… enfim, um arquivo que esteja armazenado na pasta base do site “www”.

2. Inserindo código para suporte ao HTTP Server

Tentando facilitar o trabalho introduzi um novo componente através da  “UNIT DataSnap.HTTPModule;”

[code lang=”pascal”]

// incluir na USES
uses … DataSnap.HTTPModule…
..

// Incluir um Private para o objeto
{Componente que implementa o código de resposta para o HTTP Server – por Amarildo Lacerda}
FHttpModule:TDataSnapHTTPModule;
..

// alterar o Create para
procedure TWebModule1.WebModuleCreate(Sender: TObject);
begin
{
incia o componente indicando a pasta onde será hospedado a URLBase para oa arquivos de HTTP Server
[pastaBase]/index.html
}
FHttpModule:=TDataSnapHTTPModule.Create(self);
FHttpModule.URLPath := ExtractFilePath(paramStr(0)) + ‘www’;
ForceDirectories(FHttpModule.URLPath);

FServerFunctionInvokerAction := ActionByName(‘ServerFunctionInvokerAction’);
end;

// alterar
procedure TWebModule1.WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
{ Redireciona a chamada HTML, JavaScript ou outro tipo de arquivo que exista na pasta padrão – por Amarildo Lacerda}
FHttpModule.GoURLDocument(Request, Response, Handled);
if Handled then
exit;

if FServerFunctionInvokerAction <> nil then
FServerFunctionInvokerAction.Enabled := AllowServerFunctionInvoker;
end;

[/code]

Pronto… Seu Servidor Datasnap já sabe como responder como um HTTP Server nativo.

Digite: http://localhost:8080 – e o servidor irá procurar por uma página padrão: index.html

Código de Exemplo: Datasnap.HttpModule

Para aqueles momentos que quero varrer todos os componetes que estão no FORM para pegar quem são e aplicar um operador   AS que tal usar um metódo anônimo para isto….

Forma tradicional

A forma tradicional de se varrer todos os componentes que estão sobre responsabilidade do FORM é fazer um loop com FOR e avaliar um-a-um até encontrar o que se deseja.

[code lang=”pascal”]

var &nbsp;i:integer;
begin
for i := 0 to FormX.ComponentCount-1 do
begin
if FormX.Components[i] is TQuery then // quero pegar so as TQuery
with FormX.Components[i] as TQuery do
begin
close; // fechar a TQuery
end;
end;

end;
[/code]

 

Loop de Componentes com Anonimous Methods

Utilizando “Anonimous Methods” é um modelo não tradicional de olhar a mesma questão. Entender a estrutura de um “Anonimous Method” pode simplificar muitas operações de codifição do dia-a-dia e como exemplo de fundo vamos varrer os componentes dos formulários…

  • Criando um HELPER para separar a lógica (o LOOP)

    [code lang=”pascal”]
    type
    TComponentHelper = class Helper for TComponent

    public
    procedure ComponentsLoop(AProc: TProc&lt;TComponent&gt;; AChild: boolean = false);
    end;

    procedure TComponentHelper.ComponentsLoop(AProc: TProc&lt;TComponent&gt;; AChild: boolean = false);
    var
    i: integer;
    begin
    if assigned(AProc) then
    for i := 0 to ComponentCount – 1 do
    begin
    AProc(Components[i]);
    if AChild and (Components[i].ComponentCount &gt; 0) then
    Components[i].ComponentsLoop(AProc, AChild);
    end;
    end;
    [/code]

  • Executando o LOOP utilizando o método anônimo do HELPER

    [code lang=”pascal”]

    procedure TForm39.Button1Click(Sender: TObject);
    begin
    Memo1.Lines.Clear;
    ComponentsLoop(
    Procedure(AComp: TComponent)
    begin
    // fecha todos os TDataset (TQuery)
    if AComp is TDataset then
    with AComp as TDataset do
    close;

    end, false);

    end;

    [/code]

 

Usando  Anonimous LOOP para um TList (genérico)

Quando vezes precisou fazer um loop em um TList….

  • Implementando um LOOP “anonimous” para o TList na classe HELPER

    [code lang=”pascal”]
    procedure TComponentHelper.ListLoop(AList: TList; AProc: TProc&lt;TObject&gt;);
    var
    i: integer;
    begin
    for i := 0 to AList.Count – 1 do
    AProc(AList[i]);
    end;

    [/code]

  • Aplicando o LOOP “anonimous” em um TList

    [code lang=”pascal”]
    procedure TForm39.Button3Click(Sender: TObject);
    var
    FList: TList;
    begin
    memo1.Lines.Clear;
    FList := TList.create;
    try
    PopularALista(FList);

    // fazer um LOOP usando Anonimous
    ListLoop(FList,
    procedure(AObj: TObject)
    begin
    memo1.Lines.Add( AObj.ClassName );
    end);

    finally
    FList.Free;
    end;
    end;

    [/code]

Parece Simples !!! Quantas vezes precisou escrever um LOOP em um TDataset (aka TQuery, TClientDataset)???

Código Exemplo  Helper para TDataset

 

 

Parte 1 – Criando o Host | Parte 2 – Criando o Plugin | Parte 3 – Item de Menu

Os artigos anteriores mostrei como o plugin assina à um item de menu do aplicativo host.  Agora é a vez de um plugin que assina ao serviço de janela, permitindo que seja embutido (embedded) como um componente da janela.

embedded

Criar o Plugin

  1. Criar um projeto DLL em branco;
  2. Adicionar ao projeto um FORM a ser inserido no formulário do aplicativo HOST;
  3. Incluir as UNITs: plugin.Service, plugin.Control no FORM;
  4. na Seção Initialization registrar o Plugin na lista de plugins disponíveis na DLL:
    RegisterPlugin(TPluginControlService.create(TForm1,1,0,'my control embedded'));
  5. Copiar a DLL do projeto para a pasta de “Plugins” do aplicativo HOST;

Exemplo:

[code lang=”pascal”]

{$R *.dfm}

uses plugin.Service, plugin.Control;

……..

initialization

RegisterPlugin(TPluginControlService.create(TForm1,9999,1,’my control embedded’));

finalization

end.
[/code]

 

Inserindo o Plugin em uma janela do HOST

  1.  Se o projeto HOST ainda não tem recurso para suportar plugin – arrastar o componente  TPluginManager para o formulário principal;TPluginManager
  2. Escolher um formulário onde deseja inserir o plugin controle (embedded) – arrastar um TPanel para o local onde o plugin será inserido, para estabelecer a área a ser ocupada pelo plugin (interno no plugins ele executa Align=alClient no formulário);
  3. Incluir na uses a UNIT no formulário: plugin.Manager (a mesma utilizada pelo component TPluginManager);
  4. No evento OnShow do formulário fazer chamada ao Manager do plugin para encontrar o plugin ser incorporado:
      GetPluginManager.EmbedControl(Panel1.Handle,9999,1);

Observando os parâmetros da procedure

TPluginManager.EmbedControl(AParentHandle: THandle;
AControlID, AControlType: Int64);

cabe considerar:

  • AParentHandle -> é o handle do TPanel onde o plugin será incorporado;
  • AControlID -> é um identificador para carregar um determinado plugin em específico – identifica o ID da janela que irá consumir o plugin (valor fixo nos dois lados);
  • AControlType -> um ID para separar os vários plugins que uma mesma janela pode consumir;

Quando chama

GetPluginManager.EmbedControl(Panel1.Handle,9999,1); 
deve inserir no Panel1 um plugin de controle identificado por 9999 que contenha serviços do tipo 1;

<a href="https://github.com/amarildolacerda/helpers/tree/master/plugin">Ver código de exemplo</a>

 

Parte 1 – Criando o Host | Parte 2 – Criando o Plugin

Registrando MenuItem no Host

No artigo anterior foi apresentado o exemplo de um plugin que assina o serviço de Menu do Host chamando o método:

[code]
PluginApplication.RegisterMenuItem(‘mnPlugins’, ‘Meu menu que adiciona um plugin’, self as IPluginMenuItem);
[/code]

 

Ao receber a chamada ao método do PluginApplication.RegisterMenuItem o host deverá coletar informações sobre o plugin bem como executar ações para disponibilizar o recurso solicitado.

Tratando-se de MenuItem, a implementação do host será criar o item de menu no local adequado para que seja visível ao usuário.

Um exemplo de como criar o item de menu no host:

 

[code lang=”pascal”]

// um item de menu para guardar a interface que o plugin assina
TPluginMenuItemInterf = class(TMenuItem)
protected
FProc: TProc<TObject>; // anonimous
procedure DoClick(Sender: TObject);
public
PluginMenuItem: IPluginMenuItem;
constructor Create(AOwner: TComponent; AProc: TProc<TObject>); overload;
end;

// implementação no TPluginManager -> para criar um item de menu no host
procedure TPluginManager.NewMenuItem(AMainMenu: TMainMenu;
ADefaultMenu: TMenuItem; const APath, ACaption: string;
ADoExecute: IPluginMenuItem; AProc: TProc<TObject>);
var
it: TPluginMenuItemInterf;
itClient: TMenuItem;
begin
inc(itCount);
// procura o menu para mostrar
itClient := AMainMenu.FindItem(APath);
if itClient = nil then
itClient := ADefaultMenu; // se nao encontrou pega um padrao

if not assigned(AProc) then
AProc := (
procedure(Sender: TObject)
begin
with TPluginMenuItemInterf(Sender) do
PluginMenuItem.DoClick(0);
end);

// cria o menu
it := TPluginMenuItemInterf.Create(AMainMenu, AProc);
it.Name := ‘mnPlugin_’ + formatDatetime
(‘hhmmsszzz_’ + intToStr(itCount), now);
it.PluginMenuItem := ADoExecute;
it.Caption := (it.PluginMenuItem as IPluginMenuItem).GetCaption;
// adiciona o menu na lista
itClient.Add(it);

end;

[/code]

 

Pensando em facilitar a integração no HOST é possível registrar o componente na aba do Delphi – Arrastar e soltar o componente TPluginManager no formulário principal do HOST.

PluginManager

Explorando código padrão para o evento do TPluginManager.RegisterMenuItem  irá notar que já foi implementado as funcionalidades para inserir o plugin no menu do aplicativo. Caso deseje mudar o comportamento do evento padrão, basta implementar o evento do componente com funcionalidade personalizadas…

 

Fontes: Código base para o Plugin

Hoje fiquei com vontade de escrever algo para “EU” mesmo. Um preguiçoso incondicional… Sabe aquele cara que não gosta de repetir tudo de novo… então… Já notou como RTTI fica tudo repetitivo… cansei.

Se um objeto tem um propriedade Caption, para atribuir um valor para ele usando RTTI é preciso iniciar um Context, fazer um loop para checar se a propriedade existe, ver o tipo… cansei…

Agora vou fazer assim:

label1.ContextProperties['caption'] := 'meu caption';

Alguém vai dizer que para fazer para um TLabel é moleza… tudo bem… e se fosse um objeto que não sei se tem a propriedade “caption”… tudo bem – isto não é problema meu… o objeto que se encarregue de tratar… não quero aquele erro infame na minha janela…

Vi a palestra do Thulio na Conference 2016 e ele nem piscou, mandou um “dinossauro” na sala quando falava de MongoDB – beleza meu amigo, vamos tocando aqui.

Quer saber se TMongoConnection tem uma propriedade “loginprompt” – não precisava, mas se estiver na dúvida então faz assim:

if MongoConnection.IsContextProperty('loginprompt') then
        fazUmEstragoNoDB;

Humm… pensei melhor e talvez não era uma propriedade o que esperava… na verdade era uma variável do objeto:

Opa, então faz assim:

form1.ContextFields['usuario'] := 'Eu mesmo';

Por fim, pensei em listar todas a propriedades do objeto em um memo…

Boa, isto é moleza:

label1.GetContextPropertiesItems( memo1.lines );

Se fosse uma lista de variáveis do objeto, da para fazer assim:

form1.ContextGetFieldsList( memo1.lines );

Difícil…. então deixa de falar que RTTI é complicado, bora usar aí nos seus projetos mais ousados.
Quer ver mais…. olha aqui nos fontes: https://github.com/amarildolacerda

Parte 1 – Criando o Host

Na parte 1 deste artigo tracei as linhas básicas para a implementação do Aplicativo Principal HOST – se ainda não o leu, será mais produtivo fazê-lo antes de continuar.

Criando um Plugin
O Plugin em si consiste em uma DLL que “exports” 2 entradas:

  1. function LoadPlugin(AAplication: IPluginApplication): IPluginItems;
  2. procedure UnloadPlugin;

A base para a criação do plugin é a unit: plugin.Service

LoadPlugin
“LoadPlugin” é utilizado quando o HOST inicia o plugin; O parâmetro IPluginApplication representa os serviços publicados no HOST disponíveis para assinatura pelos plugins. Assim todos os serviços que o HOST implementar deve se publicada para que sejam visíveis aos plugins.

Nos casos em que houver necessidades de estender os serviços publicados pelo HOST, IPluginApplication será a interface base publicada adicionada de novos serviços…   IMyPluginApplication = interface( IPluginInterface) …

O Plugin registra-se à lista de plugins 

IPluginItems – é uma lista de plugins disponíveis na DLL;

Para que IPluginItems seja populada o plugin poderá escolher entre os serviços padrões, aquele a que deseja assinar.

[code lang=”pascal”]
initialization
RegisterPlugin(TPluginMenuItemService.Create(TForm1,”,1, ‘Menu base de sample’));

[/code]

DoStart
Após a inicialização do LoadPlugin, ocorrerá um evento “DoStart” que inicializa cada um dos plugins da lista registrada. É neste momento que o plugin fará o pedido de assinatura  no HOST… Tudo isto é feito internamente pelo objeto “TPluginMenuItemService” do exemplo.

[code lang=”pascal”]
procedure TPluginMenuItemService.DoStart;
begin
inherited;
PluginApplication.RegisterMenuItem(FMenuItemName, GetCaption, self);
end;
[/code]

Um esqueleto para um plugin de MenuItem

[code lang=”pascal”]
unit uMenuItemSimple;

interface

uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics,
plugin.Interf, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
Vcl.ExtCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
LabeledEdit1: TLabeledEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }

public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

uses plugin.Service, plugin.MenuItem, plugin.Control;

procedure TForm1.Button1Click(Sender: TObject);
begin
close;
end;

initialization

RegisterPlugin(TPluginMenuItemService.Create(TForm1,”,1, ‘Menu base de sample’));

finalization

end.
[/code]

 

Introdução
Tomando emprestado o WIKI “plugin” é um componente computacional que adiciona recursos a um programa existente. Quando um programa suporta “plugins” ele permite ser customizado para responder a necessidades não previstas no projeto original.

Uma interface de “plugin” deve prever a possibilidade de um conjunto de código ou janela permitir ser inserida em partes do programa principal.

Em um primeiro instante – é comum encontrar “plugins” que publicam alguma função ou procedimento a ser chamado pelo aplicativo principal. Este modelo limita as funcionalidade dos “plugins” que em geral ficam mais estáticos a serem um item de menu ou uma ou outra funcionalidade.

O “plugin” que irá assinar algum serviço do aplicativo

A idéia que motiva esta publicação é a construção de um modelo de aplicativo principal HOST que publica serviços implementados em seu código e ficam disponíveis para que os “plugins” possam assinar estes serviços.

Então o HOST se torna um “publisher” é o plugin um “subscriber”, onde o plugin que solicita a assinatura de um determinado serviço do HOST.

Com a mecânica em que o “plugin” assina aos serviços do aplicativo servidor permitirá que o “plugin” tome a decisão sobre qual tipo de serviço ele quer assinar no aplicativo principal . Um exemplo é um “plugin” assinar  para ser um item do menu –  em outros casos poderá assinar funcionalidade de uma “aba” de janela ou adicionar “frames” a alguma interface do usuário – entregando mais poder de escolha ao “plugin”.

Segurança x Flexibilidade

Ainda que “flexibilidade” seja o principal objetivo, segurança segue o mesmo caminho da flexibilidade. Há que se questionar o quanto um HOST pode ser seguro o bastante para não permitir um assinante malicioso. Não tenho resposta para a questão, já que um “plugin” maldoso poderia assinar a serviços para aplicar táticas maliciosas – questão que precisa ser avaliada.

diagrama

Uma interface para publicar serviços

Considerando que o programa principal tem um formulário:

TMeuMenu = class(TForm)

end;

o que precisamos fazer é dotar o formulário principal de uma interface que permita publicar os seus serviços:

[code lang=”pascal”]
IPluginApplication = interface
[‘{6ED989EA-E8B5-4435-A0BC-33685CFE7EEB}’]
procedure RegisterMenuItem(const AParentMenuItemName, ACaption: string; ADoExecute: IPluginMenuItem);
procedure RegisterToolbarItem(const AParentItemName, ACaption: string; ADoExecute: IPluginToolbarItem);
procedure RegisterAttributeControl(const AType,ASubType: Int64; ADoExecute: IPluginControl);
end;
[/code]

Com isto o formulário passa a implementar a interface de serviço de “plugins” da seguinte forma:

TMeuMenu = class(TForm, IPluginApplication)
….
end;

 

Registrando o Plugin para o Aplicativo Principal

Antes de carregar um “plugin” o aplicativo host precisa conhece-lo (uma lista de plugins registrados). Uma forma simplista, é guardar uma lista de “plugins” disponíveis em um arquivo INI do host. A lista é necessária para que o aplicativo possa fazer carga dos seus “plugins”. Uma vez feito a carga do plugin, estes  irão assinar os serviços a que desejam interagir com o host.

O framework utiliza a implementação de DLLs para troca de código com o aplicativo principal, publicando duas chamadas – uma para carga inicial da DLL e outra para quando o aplicativo principal esta encerrando o uso do plugin:

[code]
function LoadPlugin(AAplication: IPluginApplication): IPluginItems;
procedure UnloadPlugin;

exports LoadPlugin, UnloadPlugin;
[/code]

Carregando o Plugin
Sabedor destas duas entradas disponíveis no “plugin”, resta escrever os métodos de carga do plugin (ver TPluginManager nos fontes):

[code lang=”pascal”]
function LoadPluginService(APlugin: string;
AAppliction: IPluginApplication): Integer;
var
F: function(APApplication: IPluginApplication): IPluginItems;
H: THandle;
begin
result := -1;
H := LoadLibrary(PWideChar(APlugin));
if H &gt; 0 then
begin
try
@F := GetProcAddress(H, ‘LoadPlugin’);
if assigned(F) then
result := LPluginManager.FPlugins.Add(H, F(AAppliction))
else
raise Exception.Create(‘Não carregou o plugin’);
except
FreeLibrary(H);
end;
end;
end;
[/code]

Note que a função “LoadPlugin” do plugin espera que seja enviado um IPluginApplication e retorna uma lista de plugins existente na mesma DLL, já que uma DLL pode conter 1 ou mais plugins.

O IPluginApplication, representa a interface do HOST que publica os serviços que os plugins poderão assinar no HOST – Internamente a DLL irá montar uma lista de plugins disponíveis. Cada plugin da DLL se encarrega de assinar os serviços do HOST.

Estendendo os serviços do HOST

A interface que publica os serviços no HOST é o IPluginApplication que já possui três serviços básicos para a assinatura sendo eles:

  1. MenuItem – Assinatura para se registrar em um item de menu do HOST;
  2. ToolbarItem – Assinatura para se registrar em um item da Toolbar;
  3. AttributeControl – Uma assinatura a utilizar o plugin como um “control” ou atributo de um janela.

Caso deseje implementar outros serviços para o HOST, estendendo suas funcionalidades é possível estender a interface IPluginApplication e implementa-la no HOST:

[code]
IMyPluginApplication  =  interface(IPluginAppliction)
procedure RegisterXXX(….);
end;

// no host
TMyMainMenu = class(TForm, IMyPluginApplication )

end;
[/code]

 

Parte 2 – Construindo o Plugin

(No final o código estará disponível no GIT)
….

Preparando para gerar imagem QRCode no gerador de relatórios saí procurando o que havia disponível.
Não foi difícil chegar em: ZXing

O código ZXing para Delphi é o mesmo utilizado no ACBr, razão que preferi utilizar o código distribuído junto com o ACBr, visto que já esta incorporado ao nosso código (você pode preferir utilizar o código original – sem problema).

Gerando QRCode para qualquer imagem

Procedimento para gerar o codigo QRCode com base no ZXing:

[code lang=”pascal”]

// usando ACBR
uses ACBrDelphiZXingQRCode;
// se nao usa ACBR, pode usar o componente original :
// uses DelphiZXingQRCode;
// https://github.com/debenu/DelphiZXingQRCode/

procedure QrCodeToCanvas(AWidth, AHeight: Integer; ATexto:String; ACanvas: TCanvas);
var
bitmap: TBitmap;
qr: TDelphiZXingQRCode;
r: Integer;
c: Integer;
scala: Double;
begin
bitmap := TBitmap.create;
try
qr := TDelphiZXingQRCode.create;
try
qr.Data := ATexto;

// ajuta o tamanho do bitmap para o tamanho do qrcode
bitmap.SetSize(qr.Rows, qr.Columns);

// copia o qrcode para o bitmap
for r := 0 to qr.Rows – 1 do
for c := 0 to qr.Columns – 1 do
if qr.IsBlack[r, c] then
bitmap.Canvas.Pixels[c, r] := clBlack
else
bitmap.Canvas.Pixels[c, r] := clWhite;

// prepara para redimensionar o qrcode para o tamanho do canvas
if (AWidth < bitmap.Height) then
begin
scala := AWidth / bitmap.Width;
end
else
begin
scala := AHeight / bitmap.Height;
end;

// transfere o bitmap para a imagem
ACanvas.StretchDraw(Rect(0, 0, Trunc(scala * bitmap.Width),
Trunc(scala * bitmap.Height)), bitmap);

finally
qr.Free;
end;
finally
bitmap.Free;
end;
end;

[/code]

Utilizando o código para gerar a imagem:

[code lang=”pascal”]

type
TForm10 = class(TForm)
Image1: TImage;
LabeledEdit1: TLabeledEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form10: TForm10;

implementation

{$R *.dfm}

// transferindo o QRCode para a imagem (Canvas)
procedure TForm10.Button1Click(Sender: TObject);
begin
QrCodeToCanvas(Image1.Width, Image1.Height,LabeledEdit1.Text, Image1.Canvas);
end;

[/code]

Exemplo

 

Componente para QuickReport

Para integrar o QrCode com QuickReport deve registrar componente que herde a estrutura quickreport no IDE.

Criar uma Package nova ou adicionar a uma já existe o código:

[code lang=”pascal”]

procedure Register;
begin
RegisterComponents(‘QReport’, [TqrQRCode, TqrDBQRCode]);
end;

[/code]

Ver Código completo

 

 

Cenário

Tenho um aplicativo que roda como executável windows executando atividades que não requerem intervenção do usuário para o seu funcionamento. Se manter o aplicativo minimizado fica muito fácil de fecha-lo por engano, parando o serviço que se encontra em execução. Uma opções é deixar o aplicativo na bandeja (aqueles ICON no canto direito do windows) – a desvantagem neste caso é que depende que seja feito login para  “INICIAR” a execução do aplicativo.
Nestas situações criar um serviço que sobe junto com o windows pode ser uma alternativa interessante – ponto que irei focar neste POST.

Considerações sobre  um formulário preexistente

Considerações sobre as diferenças em executar um APP com formulário X Serviço que não possui um formulário:
1) Considere um formulário padrão de execução para um aplicativo que mostra o formulário; Quando se usa como aplicativo ele inicia a execução pelo evento onFormShow, mas quando inicia como serviço não é aconselhável gerar o evento FormShow, já que executando como serviço não há um terminal/janela para mostrar as mensagens.
2) Outra consideração importante é não permitir chamadas para funções que mostram mensagens para o usuário (ex: showmessage); Uma alternativa é criar um FAKE para a função ShowMessage (… e outras) direcionando a mensagem para uma saída em disco (um LOG);

[code lang=”pascal”]
implementation

{$R *.dfm}

procedure TForm10.Execute;
begin // procedure a ser executa quando estiver chamando como serviço
// nao executa o SHOW do formulário
FServico := true;
Init;
end;

procedure TForm10.FormCreate(Sender: TObject);
begin
FContador := 0;
FServico := false;
end;

procedure TForm10.FormShow(Sender: TObject);
begin
// quando é executa como Aplicativo, usa o show para dar inicio ao funcinamento
FServico := false;
init;
end;

procedure TForm10.Init;
begin
Timer1.Enabled := true;
end;

procedure TForm10.Log(texto: string);
begin
TThread.Queue(nil,
procedure // sincroniza a escrita no memo1 – previne chamada multi-thread
begin
Memo1.Lines.Add(texto); // mostra o texto em um memo.
end);
end;

procedure TForm10.Timer1Timer(Sender: TObject);
begin
inc(FContador);
Log(‘Chamada: ‘ + intToStr(FContador));
end;

[/code]

Criando um “Service Application” padrão 

Criar um projeto padrão do DELPHI do tipo “Service Application” que será a base para iniciar o nosso serviço;

Com base no projeto padrão, no evento “onStart” criar uma instância para o formulário padrão do aplicativo. Importante neste ponto é trocar o ShowModal por Execute; ou seja, não vamos mostrar o fomulário – somente inciar sua execução;

[code lang=”pascal”]

var
Service10: TService10;

implementation

{$R *.dfm}
uses UnitApp;

var LAppForm : TForm10;

procedure ServiceController(CtrlCode: DWord); stdcall;
begin
Service10.Controller(CtrlCode);
end;

function TService10.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;

procedure TService10.ServiceStart(Sender: TService; var Started: Boolean);
begin
LAppForm := TForm10.create(nil);
LAppForm.execute;
end;

procedure TService10.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
LAppForm.free;
end;

[/code]

Alteração no formulário para LOG em arquivo

[code lang=”pascal”]

procedure TForm10.Log(texto: string);
procedure LogServico;
begin
System.TMonitor.Enter(FLock); // controle de acesso ao arquivo de LOG
try
WriteLogText( ‘AppService.log’,texto);
finally
System.TMonitor.Exit(FLock);
end;
end;

begin
if FServico then
LogServico
else
TThread.Queue(nil,
procedure // sincroniza a escrita no memo1 – previne chamada multi-thread
begin
Memo1.Lines.Add(texto); // mostra o texto em um memo.
end);
end;
[/code]

Alterando o projeto para selecionar o Formulário  Alternando com Serviço

Como padrão um projeto “Service Application” inicializa o objeto TService e executa chamando RUN.
Quando o windows inicializa o serviço faz uma chamada /start para o projeto, então o primeiro passo é testar se o serviço já foi instalado “/install”. Se foi instalado executa o serviço, se não foi instalado ainda ou utilizar o parâmetro /app passar para executar o formulário.

[code lang=”pascal”]
program ProjAppService;

uses
Vcl.SvcMgr,
uServiceApp,
System.SysUtils,
UnitApp,
UnitService in ‘UnitService.pas’ {AppService: TService};

const
NomeServico = ‘AppService’;

{$R *.RES}

begin

{ parametros
/app -> executa como aplicativo
/install -> instala como serviço
/uninstall -> desinstala o serviço
}

if IsServiceInstalled(NomeServico) and
(not(FindCmdLineSwitch(‘app’, [‘-‘, ‘\’, ‘/’], true))) then
begin // é serviço
try
if not Application.DelayInitialize or Application.Installing then
Application.Initialize;
Application.CreateForm(TAppService, AppService);
AppService.name := NomeServico;
Application.Run;
finally
end;
end
else
begin // é app
Application.CreateForm(TForm10, Form10);
form10.ShowModal;
end;

end.
[/code]

 

 

Projeto de Exemplo