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 > 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)
….