UNIVERSIDADE FEDERAL DE SANTA CATARINA
SISTEMAS DE INFORMAÇÃO
FABIANO VICENTE SIEGEL
PLUGINS
UMA ALTERNATIVA PARA O DESACOPLAMENTO, MODULARIZAÇÃO E
EXTENSÃO DE FUNCIONALIDADES NO DESENVOLVIMENTO DE SOFTWARE
FLORIANÓPOLIS
2009
FABIANO VICENTE SIEGEL
PLUGINS
UMA ALTERNATIVA PARA O DESACOPLAMENTO, MODULARIZAÇÃO E
EXTENSÃO DE FUNCIONALIDADES NO DESENVOLVIMENTO DE SOFTWARE
Trabalho de conclusão de curso apresentado
como parte dos requisitos para obtenção de
grau
em
Bacharel
em
Sistemas
de
Informação, da Universidade Federal de
Santa Catarina.
Orientador:
FRANK AUGUSTO SIQUEIRA
FLORIANÓPOLIS
2009
FABIANO VICENTE SIEGEL
PLUGINS
UMA ALTERNATIVA PARA O DESACOPLAMENTO, MODULARIZAÇÃO E
EXTENSÃO DE FUNCIONALIDADES NO DESENVOLVIMENTO DE SOFTWARE
Trabalho de conclusão de curso apresentado
como parte dos requisitos para obtenção de
grau
em
Bacharel
em
Sistemas
de
Informação, da Universidade Federal de
Santa Catarina.
Banca Examinadora
Orientador: Profº Frank Augusto Siqueira
Profº Raul Sidnei Wazlawick
DEDICATÓRIA
Dedico este trabalho a Deus, que nos momentos de dificuldade me mostra a
oportunidade de melhorar, e aos meus pais, que através de seus exemplos de vida
me ensinam que o Ser é mais importante que o Ter.
AGRADECIMENTO
Agradeço ao meu orientador, Prof. Dr. Frank Siqueira, pela confiança depositada e
pela oportunidade de realizar este trabalho.
Agradeço aos colegas da Audaces por despertar interesse em temas relevantes ao
desenvolvimento de software.
Agradeço à minha namorada, Tatiana Farias, pelo apoio incondicional durante a
realização deste trabalho.
Agradeço aos meus amigos pela compreensão, pois em diversas vezes receberam
um “não” como resposta, devido a minha indisponibilidade.
Não existe, neste mundo todo, uma superioridade real que
possa ser separada de uma vida correta.
RESUMO
Com o passar do tempo, a arquitetura dos sistemas evoluiu para um design mais
modular. Um dos fatores que influenciou essa evolução foi a necessidade de
aumentar o reuso de código. Essa melhoria permite que sistemas não sejam escritos
do zero a cada nova implementação. Uma forma de conseguir essa modularização e
conseqüentemente aumentar o reuso de código é através do uso de plugins no
desenvolvimento dos sistemas. Este trabalho aborda a utilização de plugins como
uma alternativa para melhorar a qualidade da arquitetura de software. Com esse
objetivo, apresentamos o conceito de plugins como um padrão de projeto e
estudamos algumas aplicações comerciais, a fim de complementar a bibliografia
acadêmica. No decorrer deste trabalho, modelamos uma pequena biblioteca escrita
em C++, para facilitar a utilização de plugins. Por fim, um sistema existente é
adaptado para usar o conceito estudado.
ABSTRACT
Over time, systems architecture has evolved into a more modular design. One of the
factors that influenced this evolution was the need to increase the reuse of code. This
improvement avoids the problem of re-writing the system from scratch with each new
implementation. One way to achieve this modularization and therefore increase the
reuse of code is through the use of plugins. This paper discusses the use of plugins
as an alternative to improve the quality of software architecture. As a way to achieve
that, we present the concept of plugins as a design pattern. We also present the
study of some commercial applications to complement the academic literature.
During the course of the work, we model a small library, written in C++, in order to
make the use of plugins easier. Finally, an existing system is adapted to use the
concepts studied.
LISTA DE ABREVIAÇÕES E SIGLAS
API – Application Programming Interface (ou Interface de Programação de
Aplicativos)
AV – Acrobat Viewer
AS – Acrobat Support
BHO – Browser Helper Object (Objeto de Ajuda de Navegação)
COM – Component Object Model (Modelo de Objeto Componente)
Cos – Cos Object System
DLL – Dynamic-link library (Biblioteca de ligação dinâmica)
GUI – Graphical User Interface (Interface Gráfica do Usuário)
IDE
–
Integrated
Development
Environment
(Ambiente
Integrado
de
Desenvolvimento)
PD – Portable Document (Documento Portável)
PDF – Portable Document Format (Formato de Documento Portável)
SDK – Software Development Kit (Kit de Desenvolvimento de Software)
XML – eXtensible Markup Language (Linguagem de Marcação Extensível)
LISTA DE FIGURAS
Figura 1. Diferença entre a primeira e segunda geração de plugins. ... 15
Figura 2. Diagram UML do padrão Plugin. ... 15
Figura 3. Camadas de uma aplicação que possibilita plugins. ... 17
Figura 4. OSGi Service Registry. ... 19
Figura 5. Camadas do OSGi. ... 20
Figura 6. Hierarquia do Acrobat Core API. ... 25
Figura 7. A biblioteca para carregamento dinâmico ... 29
Figura 8. Exemplo de declaração da função CreateInstance. ... 30
Figura 9. Layout do OpenPaint. ... 32
Figura 10. Método OnMouse antes do refactoring. ... 33
Figura 11. Estrutura do padrão Command. ... 34
Figura 12. Representação resumida dos comandos. ... 36
Figura 13. Funcionalidade sem interação com o mouse. ... 37
Figura 14. OnMouse utilizando o padrão Command. ... 37
Figura 15. Extensão concreta. ... 40
Figura 16. Carregamento dos plugins. ... 41
Figura 17. Diagrama de seqüência demonstrando um clique no menu. ... 42
Figura 18. Método da classe ToolTriangle que desenha um triângulo na interface. . 43
Figura 19. Função de criação do plugin triângulo. ... 44
LISTA DE QUADROS
SUMÁRIO
1
INTRODUÇÃO ... 12
1.1 ESCOPO ... 12 1.2 OBJETIVO GERAL ... 13 1.3 OBJETIVOS ESPECÍFICOS ... 132
REVISÃO BIBLIOGRÁFICA ... 14
2.1 PLUGIN ... 14 2.2 OSGI ... 172.3 A TECNOLOGIA DE PLUGINS EM APLICAÇÕES COMERCIAIS ... 20
2.3.1 Eclipse ... 21
2.3.2 Mozilla Firefox ... 22
2.3.3 Internet Explorer ... 23
2.3.4 Adobe Reader ... 24
2.3.5 Aspectos dos plugins nos produtos ... 26
3
A APLICAÇÃO DO CONCEITO DE PLUGINS ... 28
3.1 A MODELAGEM DA BIBLIOTECA ... 28
3.1.1 A biblioteca em C++ ... 30
3.2 A ADAPTAÇÃO DE UM SISTEMA EXISTENTE... 31
3.2.1 O OpenPaint ... 31
3.2.2 Aspectos da arquitetura atual do OpenPaint... 32
3.2.3 Padrões de projetos ... 34
3.2.4 O conceito de plugins no OpenPaint ... 37
3.3 ESTENDENDO A APLICAÇÃO ... 42
3.3.1 Criando um plugin para a aplicação ... 42
4
CONCLUSÃO ... 45
REFERÊNCIAS ... 46
APÊNDICES ... 48
APÊNDICE A: DIAGRAMA DE CLASSE ILUSTRANDO AS INTERFACES UTILIZADAS NO REFACTORING (REFATORAÇÃO) DAS FUNCIONALIDADES ... 48
APÊNDICE B: DIAGRAMA DE CLASSES DEMONSTRANDO AS INTERFACES UTILIZADAS NA ARQUITETURA DE PLUGINS ... 49
APÊNDICE C: DIAGRAMA DE SEQÜÊNCIA DEMONSTRANDO O CARREGAMENTO DOS PLUGINS DURANTE O INÍCIO DA APLICAÇÃO ... 50
APÊNDICE D: CÓDIGO DO PROJETO TOOLTRIANGLE ... 51
APÊNDICE E: CÓDIGO DA APLICAÇÃO ... 54
APÊNDICE F: CÓDIGO DA BIBLIOTECA PLUGINCORE ... 86
APÊNDICE G: CÓDIGO DA BIBLIOTECA OPENPAINTAPI ... 92
APÊNDICE H: CÓDIGO DOS PLUGINS ... 95
1 INTRODUÇÃO
A utilização dos computadores cresceu nos últimos anos. Pesquisas informam que
no ano 2000 havia 10 milhões de computadores em uso no Brasil. Em 2009 esse
número cresceu para 60 milhões e estima-se que deva chegar em 100 milhões até o
ano 2012. Com o aumentado da utilização da informática, se ampliou também a
necessidade de aplicações das mais diversas áreas por parte dos usuários.
Devido ao baixo tempo de desenvolvimento requerido por parte dos clientes, iniciar a
implementação de um novo sistema do zero ou reescrever grande parte de um
software a cada nova necessidade torna quase que inviável o desenvolvimento de
software. Buscando aperfeiçoar o processo de desenvolvimento e aumentar a
qualidade e reutilização de código, a arquitetura dos sistemas evoluiu para um
design mais modularizado.
Outro ponto crítico na etapa de desenvolvimento está na manutenção de código.
Com o crescimento de um projeto, um código pouco modularizado e com alto
acoplamento tende a ser instável, já que a mudança de algum comportamento ou
correção de algum bug pode gerar efeito colateral em outra parte do sistema,
teoricamente não relacionada.
Além disso, a especificidade de uma funcionalidade necessária ao cliente pode
requerer que uma equipe especializada desenvolva parte do software.
Visando resolver esses problemas, o conceito de plugin ganhou força, possibilitando
que sistemas complexos possam ser compostos por componentes e equipes de
desenvolvimento formadas por terceiros possam implementar funcionalidades para a
aplicação.
1.1 Escopo
O escopo deste projeto delimita-se ao estudo da tecnologia de plugins, tendo como
exemplo aplicações reais. Uma aplicação será adaptada para utilizar plugins, com o
objetivo de demonstrar este conceito e comprovar seus benefícios.
1.2 Objetivo Geral
Apresentar a tecnologia de plugins, demonstrando suas vantagens e desvantagens
no desenvolvimento de software.
1.3 Objetivos Específicos
Os objetivos específicos são:
1. Estudar o conceito de plugins, utilizando publicações acadêmicas que
abordem o assunto;
2. Analisar algumas aplicações comerciais com suporte a plugins;
2 REVISÃO BIBLIOGRÁFICA
Este capítulo tem como objetivo realizar um estudo sobre a tecnologia de plugins.
Nele fazemos uma revisão da bibliografia acadêmica, apresentamos uma breve
introdução sobre OSGi (The Dynamic Module System for Java – Sistema Modular
Dinâmico para Java), quem vem se mostrando a principal iniciativa para criação de
aplicações com alto nível de modularização, e analisamos algumas aplicações
comerciais que utilizam esta tecnologia.
2.1 Plugin
Os plugins tem se popularizado nos últimos anos. Eles contribuíram para o sucesso
de aplicações conhecidas, como a IDE de desenvolvimento Eclipse e o navegador
de Internet Mozilla Firefox.
Esta tecnologia permite que sistemas sejam estendidos em tempo de execução
através da adição de uma nova funcionalidade. Por isso, ela é indicada para
sistemas que necessitam ser reconfigurados em tempo de execução e aplicações de
uso geral que estão em desenvolvimento.
Além disto, a utilização de plugins possibilita o desenvolvimento colaborativo de
aplicações. A aplicação principal pode definir pontos de extensão (extension points)
para adição de uma nova funcionalidade, permitindo que a equipe responsável pelo
desenvolvimento do projeto principal se concentre no núcleo (core) do produto e
outras equipes, formadas por terceiros ou não, trabalhem no desenvolvimento de
funcionalidades ou partes específicas do projeto. Adicionalmente, o fato de
diferentes equipes poderem desenvolver plugins alternativos para uma mesma
aplicação possibilita a competição entre esses fornecedores, o que tende a
aumentar a qualidade e a disponibilidade de plugins para uma determinada tarefa.
Segundo Dietrich et. al. (2007), os plugins podem ser classificados em duas
gerações. A principal diferença entre as gerações é que na primeira, apenas a
aplicação principal possui a habilidade de fornecer pontos de extensão. Já na
segunda geração, as extensões também possuem essa habilidade.
Figura 1. Diferença entre a primeira e segunda geração de plugins.
Fonte: Adaptação de Dietrich et al. (2007).
Mayer et al. (2002) apresentam o conceito de plugins através de um padrão de
projeto (design pattern), no formato dos designs patterns apresentados no livro
Design Patterns: Elements of Reusable Object-Oriented Software (Padrões de
Projetos: Soluções reutilizáveis de software orientado a objetos). Este padrão
permite que uma aplicação seja estendida em tempo de execução através de
módulos ou classes carregados dinamicamente, desconhecidos durante o tempo de
compilação. Ainda segundo Mayer et al. (2002), a estrutura do padrão seria formada
basicamente pelo plugin loader, pela interface do plugin e pelo plugin concreto,
como demonstrado na figura 2.
Figura 2. Diagram UML do padrão Plugin.
O plugin loader é a entidade responsável por procurar as extensões em tempo de
execução e dar ao cliente o acesso aos artefatos carregados. Já a interface PlugIn é
responsável por permitir a comunicação com todos os plugins concretos de um
mesmo tipo e determinar os métodos que o fornecedor do plugin deverá
implementar para que o mesmo funcione no sistema. Já a classe ConcretePlugin
implementa a interface PlugIn, fornecendo uma funcionalidade especial ao sistema.
Como motivação para o padrão, os autores citam o exemplo de uma aplicação
construída para exibir uma variedade de formatos gráficos, onde o desenvolvedor
não é capaz de escrever um decodificador (decoder) para um formato que será
definido após o desenvolvimento da aplicação. Este problema pode ser resolvido
permitindo que terceiros implementem plugins que recebam um stream codificado e
retornam um stream com a informação decodificada.
Dentre as vantagens da utilização de plugins na construção de uma aplicação, ficam
evidentes os seguintes benefícios:
1) Estender a aplicação durante a execução;
2) Modularizar sistemas volumosos a fim de reduzir a complexidade;
3) Desenvolver componentes do sistema sem ter que recompilar a aplicação
inteira;
4) Permitir que terceiros desenvolvam plugins;
5) Diminuir o tempo de startup e requisitos de hardware, como memória;
6) Permitir a reconfiguração de servidores sem necessidade de que os mesmos
sejam reiniciados.
Mas a utilização de plugins não apresenta apenas vantagens, segundo Marquart
(2005) algumas limitações podem ser encontradas no modelo, como:
1) Devem-se conhecer os tipos de extensões possíveis para a aplicação, pois as
interfaces devem ser definidas com antecedência;
2) A partir da hora que as interfaces são publicadas, o potencial de evolução da
aplicação fica limitado às classes e serviços que oferece;
3) Apenas a interface do plugin pode não ser suficiente para manter a aparência
(look-and-feel) da aplicação. Com isso, guias de estilo podem ser
necessários.
Se dividida em camadas, uma aplicação que tenha a capacidade de receber plugins
terá pelo menos as camadas das bibliotecas, do plugin loader (carregador de
plugin), das interfaces do plugin, da aplicação principal e dos plugins concreto,
conforme demonstrado na figura 3.
Figura 3. Camadas de uma aplicação que possibilita plugins.
Fonte: Mayer et al. (2002)
Nessa arquitetura, a aplicação principal conhece as camadas das interfaces de
plugins, do plugin loader e das bibliotecas. Já a implementação do plugin conhece
apenas a biblioteca e as interfaces dos plugins. Pode haver várias interfaces de
plugins, mas uma extensão concreta normalmente implementa apenas uma
interface. Dentro do código do plugin, as classes da biblioteca são usadas para
realizar tarefas comuns às extensões. Todas as funcionalidades adicionais são
fornecidas através de plugins e estes são gerenciados e carregados no sistema, em
tempo de execução, através do plugin loader. Todas as partes do sistema que não
podem ser modeladas como plugins devem pertencer à aplicação principal.
2.2 OSGi
Desde os anos setenta, a modularização do software é considerada como uma
propriedade chave para aumentar a flexibilidade e o reuso de projetos de software.
Geralmente, esses módulos podem ser considerados como unidades de trabalho
implementadas e compiladas independentemente da aplicação principal.
No caso de sistemas desenvolvidos em Java, esses módulos podem ser
constituídos de classes agrupadas na forma de pacotes e distribuídos em arquivos
JAR. Porém, essa abordagem pode apresentar dois problemas:
• As regras de visibilidade aplicadas às classes, atributos e métodos, como
público, protegido ou privado, não se aplicam ao nível de pacote e arquivo
JAR. Por exemplo, não é possível restringir o acesso a uma classe pública
definida em um pacote disponível. Isso faz com que outros módulos do
sistema tenham visibilidade desta classe, permitindo seu acesso e,
conseqüentemente, podendo aumentar o acoplamento entre módulos ou
camadas do software;
• Pela característica inerentemente estática da linguagem Java, existe a
necessidade da interrupção e reinício dos sistemas quando algum módulo é
atualizado.
Com o objetivo de resolver esses problemas, a OSGi Alliance propôs um modelo de
desenvolvimento e um framework que provê uma arquitetura dinâmica e orientada a
serviços para a plataforma Java. O núcleo da especificação é um framework que
define um modelo de gerenciamento de ciclo de vida, registro de serviços, ambiente
de execução e seus módulos.
De acordo com os princípios do OSGi, os sistemas devem ser estruturados através
de módulos, chamados de bundles, que disponibilizam serviços para a aplicação.
Através do gerenciamento do ciclo de vida dos bundles pelo framework, passou a
ser possível adicionar, remover e substituir bundles em tempo de execução.
Atualmente, a especificação do OSGi encontra-se na sua quarta revisão e possui
vários projetos que a implementam. Como exemplos, podemos citar:
• Implementações da quarta revisão: Knopflerfish, Apache Felix e Equinox;
• Implementação da terceira revisão: Concierge.
Basicamente, os bundles são arquivos JAR contendo arquivos “.class”, recursos
como ícones e imagens e um arquivo de manifesto que declara informações
estáticas a respeito do bundle, como os pacotes importados e exportados. Eles
devem fornecer serviços para outros bundles. Esses serviços são classes Java que
são registrados utilizando uma ou mais tipos de interface.
Para possibilitar que os serviços registrados sejam encontrados por outros bundles,
o OSGi define o Service Registry (registro de serviço) que fornece o meio necessário
para os bundles efetuarem a publicação e a recuperação de serviços. Como
demonstrado pela figura 4, a partir da hora que um serviço é publicado pelo bundle
A e um bundle B está procurando-o, o registro está apto a vincular estes dois
bundles.
Figura 4. OSGi Service Registry.
Fonte: Tavares e Valente (2008)
2.2.1 Arquitetura
Qualquer estrutura que implementa o padrão proposto pela OSGi Alliance fornece
um ambiente para a modularização da aplicação em pacotes menores.
Conceitualmente, na especificação do núcleo do OSGi, o framework é dividido nas
camadas Service, Life Cycle, Module, Execution Environment e Security, como
mostrado na figura 5.
Figura 5. Camadas do OSGi.
Fonte: OSGi Service Platform Core Specification.
Cada camada possui uma função bem definida:
• Service: a camada de serviços conecta bundles de uma forma dinâmica,
oferecendo um modelo baseado em publicar-procurar-vincular para objetos
Java. Possui o Service Registry, que fornece uma API para gerenciar
serviços. Ex: ServiceRegistration, ServiceTracker e ServiceReference;
• Life Cycle: fornece uma API para gerenciar o ciclo de vida dos bundles, que
podem estar nos estados instalar, iniciar, parar, atualizar e desinstalar;
• Module: é a camada que define o encapsulamento e declaração de
dependências. Define como um bundle pode importar e exportar código;
• Execution Environment: define quais os métodos e classes estão disponíveis
em uma plataforma específica. Não existe uma lista fixa de ambientes de
execução, uma vez que está sujeita a criação de novas versões e mudanças
nas edições do Java, realizado pela Java Community Process;
• Security: é a camada que lida com aspectos de segurança, limitando as
funcionalidades dos bundles a capacidades pré-definidas.
2.3 A Tecnologia de Plugins em Aplicações Comerciais
Nesta seção demonstraremos alguns aspectos da utilização de plugins por
aplicações conhecidas e largamente utilizadas no dia-a-dia. Procuraremos abordar
sistemas de diferentes áreas, como plataformas de desenvolvimento de software,
navegadores de Internet e visualizadores de PDF.
Para este estudo, priorizamos aplicativos que possuem algum tipo de documentação
disponível ou publicações em artigos e livros. Levando em consideração esta
premissa, escolhemos a IDE de desenvolvimento Eclipse, os navegadores de
Internet Mozilla Firefox e Internet Explorer e a ferramenta para visualização de
arquivos PDF Adobe Reader.
Ao final, faremos algumas considerações sobre as aplicações estudadas com
relação ao uso de plugins.
2.3.1 Eclipse
O Eclipse não é um simples programa monolítico. Ele é formado por um pequeno
kernel (núcleo) cercado por centenas de plugins. Este pequeno kernel é uma
implementação da especificação OSGi R4 e recebeu o nome de Equinox.
Devido ao seu design modularizado, o Eclipse possibilita criar extensões que não
foram previstas pelos desenvolvedores originais da aplicação. O conjunto mínimo de
plugins para formar uma aplicação cliente é chamado de Eclipse Rich Client Platform
(RCP).
Todo o comportamento dos plugins é definido no código e suas dependências e
serviços são declarados nos arquivos MANIFEST.MF e plugin.xml.
No início da aplicação, o plugin loader lê os arquivos MANIFEST.MF e plugin.xml de
todos os plugins, formando a estrutura definida nesses arquivos. Isso consome
memória, mas permite que os plugins sejam encontrados mais rapidamente quando
requeridos e reduz a quantidade de memória consumida pelo sistema se comparado
com o carregamento da aplicação e do código de todas as extensões de uma única
vez.
Um típico plugin para o Eclipse deve incluir os arquivos Java (com extensão .class),
imagens e ícones utilizados pelo plugin, o arquivo MANIFEST.MF descrevendo
aspectos do plugin como identificador, versão e dependências, e o arquivo
plugin.xml descrevendo a extensão e seus pontos de extensão.
Um plugin pode ser adicionado ao Eclipse de três formas:
2 Criando um sítio de update do Eclipse (Eclipse Update Site), permitindo que o
Eclipse baixe e gerencie o plugin a partir deste site;
3 Instalando o plugin em outro diretório e criando um arquivo de link para que o
Eclipse encontre as extensões. Para isso, um arquivo no formato “.link” deve
ser colocado na pasta links do Eclipse. Se o subdiretório links não existir, ele
deverá ser criado.
2.3.2 Mozilla Firefox
Os plugins para o Firefox, também conhecidos como extensões ou add-ons, podem
acessar os recursos do navegador e melhorar suas funcionalidades.
O Mozilla oferece uma técnica fácil e flexível de criar plugins. Toda extensão do
Firefox é definida em um arquivo XPI (Cross-Platform Install), que é compatível com
o formato Zip. Neste arquivo encontramos o código da extensão, assim como o
arquivo “install.rdf”, que possui as informações de instalação. As principais
informações contidas no arquivo “install.rdf” são: o nome da extensão, a versão
compatível do navegador e um número único de identificação. O responsável por
gerenciar as extensões do navegador é chamado de Extension Manager (EM –
Gerente de Extensão).
Todas as extensões para o Firefox são escritas em Javascript. Quando uma
extensão é executada, ela é interpretada pela engine (motor) Mozilla Javascript que
é embutida dentro do navegador. Por serem escritos em Javascript, os plugins do
Firefox são de código aberto por definição.
Ao criar extensões, o desenvolvedor deve tratar os eventos criados pelo navegador.
Esses eventos são normalmente criados em resposta a uma entrada do usuário ou
ao final do carregamento de uma página Web. Por exemplo, o desenvolvedor
poderá interceptar as teclas pressionadas pelo usuário tratando os eventos de clique
no teclado (key-press events). Também é possível mudar o conteúdo de uma página
alterando sua representação interna (DOM – Document Object Model).
Se uma extensão possuir interface com o usuário, ela deverá utilizar a linguagem
XML User Interface Language (XUL – Linguagem XML de Interface de Usuário). O
XUL é um dialeto XML (Extensible Markup Language – Linguagem de Marcação
Extensível) interpretado pelo motor de renderização Gecko, que apresenta a GUI
dentro do navegador.
2.3.3 Internet Explorer
Os plugins para o navegador Internet Explorer (IE) são integrados com a aplicação
através do desenvolvimento de um objeto BHO (Browser Helper Object). Esses
objetos possuem acesso ao mecanismo de evento do IE e possibilitam a criação de
elementos da interface com o usuário.
Os objetos BHOs são objetos binários que seguem o padrão COM (Component
Object Model – Modelo de Objeto Componente). Este padrão foi desenvolvido pela
Microsoft para apoio, entre outras coisas, ao mercado de softwares baseados em
componentes. Todo objeto COM implementa um conjunto de interfaces, sendo cada
interface um contrato bem definido que descreve a sua funcionalidade. O padrão
COM garante que as tabelas virtuais das interfaces continuam as mesmas
independentemente
de
compilador,
permitindo
a
esses
objetos
serem
implementados e utilizados por qualquer linguagem que suporte chamadas de
funções através de uma tabela de ponteiros de função.
O BHO é um simples objeto COM que implementa a interface IObjectWithSite. Os
elementos da interface com o usuário são similares aos objetos BHO, sendo que
implementam mais algumas interfaces e incluem um componente gráfico.
Ao
iniciar,
o
Internet
Explorer
consulta
a
chave
de
registro
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser
Helper
Objects e carrega todos os objetos que possuem identificador salvos lá. Logo após,
o IE fornece para cada BHO um ponteiro para a interface IUnkown. Através deste
ponteiro o objeto BHO pode consultar o IE sobre interfaces mais específicas, como
IWebBrowser2, IDispatch ou IConnectionPointContainer, tornando possível acessar
eventos e funções do navegador.
Como os objetos BHO são executados como código nativo dentro do processo do
browser, ele poderá acessar aos recursos do sistema operacional que o navegador
tem acesso, como sockets, arquivos e processos da rede.
2.3.4 Adobe Reader
Para auxiliar na criação de plugins para o Adobe Reader, a Adobe disponibiliza um
kit de desenvolvimento de software (SDK – Software Development Kit) chamado
Acrobat SDK. O Acrobat SDK possui duas bibliotecas, a Acrobat core API e a PDF
Library API. A Acrobat core API contém um conjunto de interfaces que permite ao
desenvolvedor interagir com o Adobe Reader. Já a PDF Library API possibilita que
os desenvolvedores interajam e manipulem arquivos PDF.
Os plugins adicionam uma nova funcionalidade e são equivalentes às bibliotecas
lincadas dinamicamente (DLL) na plataforma Windows, com a diferença de que a
extensão da biblioteca não é “.dll”, mas sim “.api”. No sistema operacional Mac OS a
extensão do nome do plugin é acroplugin.
Os plugins desenvolvidos são colocados na pasta plug-ins no diretório do Acrobat e
após a inclusão de uma nova extensão, o aplicativo deve ser reiniciado para que o
plugin seja reconhecido.
2.3.4.1
Acrobat core API
A Acrobat core API consiste em métodos que operam sobre os objetos contidos
dentro do documento PDF. Ela é implementada como uma biblioteca ANSI C e é
suportada no Windows (32 bits), Mac OS, Linux, alguma plataformas UNIX, como o
Solaris, HP-UX e AIX.
Figura 6. Hierarquia do Acrobat Core API.
Fonte: Adobe Acrobat SDK 8.1
A camada Acrobat Viewer (AV) possibilita ao plugin controlar e modificar a interface
com o usuário. Através desta camada, utilizando os métodos AV, é possível
adicionar menus, botões, abrir e fechar arquivos e exibir caixas de diálogo.
A camada Portable Document (PD) fornece acesso aos componentes do documento
PDF, como as páginas e anotações. Nesta camada existem dois grupos de métodos
que controlam diferentes aspectos do documento. Os métodos PDFEdit lidam com a
representação física do documento PDF e permitem aos plugins ler, escrever, editar
e manipular recursos de uma página, como fontes e imagens. Os métodos PDSEdit
lidam com a estrutura lógica do documento, permitindo ao plugin acessar um arquivo
PDF através de uma estrutura de árvore, facilitando a navegação, procura e
extração de dados de um documento PDF.
A camada Acrobat Support (AS) fornece uma variedade de métodos utilitários, como
rotinas de alocação de memória. Além disso, permite ao plugin substituir rotinas de
baixo nível do sistema de arquivos utilizadas pelo Acrobat, como ler, escrever,
reabrir, remover, renomear, entre outras. Isso permite que o Acrobat seja utilizado
em outros sistemas de arquivos, como no caso de sistemas on-line.
A camada Cos Object System (Cos) fornece acesso aos blocos de construção
utilizados na criação de documentos PDF. Ela permite manipular dados de um
arquivo PDF num baixo nível, como um dicionário ou um stream (fluxo) de dados.
Além dos métodos apresentados anteriormente, a Acrobat core API fornece um
grupo de métodos para lidar com questões que são único nas plataformas Windows,
Linux e Mac OS.
2.3.5 Aspectos dos plugins nos produtos
Com o estudo realizado sobre as aplicações comerciais, encontramos alguns
aspectos importantes que complementam o conceito de plugins e ajudam a tornar
um software extensível.
Notamos que um software "plugável" pode ter dois tipos de arquitetura. No primeiro
tipo, todas as partes do software são providas através de plugins e um framework de
aplicação administra as extensões e suas interações. No segundo, a aplicação já
possui suas funcionalidades pré-definidas e publica alguns pontos de extensão que
possibilitam criar plugins para o software.
Para que a aplicação seja capaz de encontrar os plugins em tempo de execução, é
necessário definir a forma como essas extensões serão descobertas. Aplicações
como Adobe Reader e Firefox disponibilizam uma pasta onde todas as extensões
para a aplicação devem ser armazenadas. Já o Eclipse, além da opção de
armazenar os plugins em uma pasta pré-definida, também permite que eles sejam
instalados em outra pasta, necessitando que a localização seja informada em um
arquivo em formato específico (.link). O Internet Explorer, por sua vez, utiliza o
registro do Windows para localizar os plugins instalados.
Quanto ao formato do arquivo do plugin, não existe uma padronização. Esse formato
é influenciado diretamente pelas características de cada linguagem de programação
e pelo fabricante do software. No caso de plugins que são interpretados, os scripts
podem ser agrupados em um arquivo semelhante a um arquivo compactado e
utilizados de acordo com a necessidade da aplicação. Quanto aos plugins
compilados, os formatos DLL, COM e JAR são os mais utilizados, sendo este último
especifico para a linguagem Java. Apesar das diferenças de formato de arquivo,
todas as extensões possuem basicamente o mesmo conteúdo. São formadas pelas
classes ou funções (compiladas ou para interpretação) que estendem as interfaces
publicadas, recursos como figuras e ícones e, em alguns casos, arquivos que
descrevam características dos plugins.
Por último, vimos que é comum as aplicações disponibilizarem bibliotecas ou SDKs
(Software Development Kit – Kit de Desenvolvimento de Software), além das
interfaces para extensão, para facilitar o desenvolvimento dos plugins, melhorando a
integração entre extensão e software estendido.
3 A APLICAÇÃO DO CONCEITO DE PLUGINS
Este capítulo tem como objetivo demonstrar a adaptação de uma aplicação legada
para a utilização de plugins. Para tal, modelamos uma biblioteca que facilita o
carregamento dinâmico de classes, refatoramos uma aplicação para uma arquitetura
intermediária e aplicamos o conceito de plugins. Ao final, apresentamos uma forma
de criar novas extensões para a aplicação.
3.1 A Modelagem da Biblioteca
Com base nos estudos realizados no capítulo anterior, modelamos uma biblioteca
que possibilita estender softwares em tempo de execução. Ela segue o padrão
proposto por Mayer et. al. (2002), onde é prevista a utilização de interfaces para os
plugins, um carregador de plugins e extensões concretas.
Antes de iniciar a modelagem, determinamos algumas premissas que nortearam o
desenvolvimento. De acordo com essas premissas, a biblioteca deve:
1) Disponibilizar uma interface comum aos plugins, pois toda comunicação entre
aplicação e extensão será realizada através desta interface;
2) Prover um mecanismo de carregamento dinâmico de plugins, que carregará
as extensões a partir de um determinado diretório;
3) Possibilitar que as extensões sejam carregadas apenas quando solicitadas.
Com esses objetivos levantados, desenvolvemos a arquitetura apresentada na figura
7.
Figura 7. A biblioteca para carregamento dinâmico
Essa
biblioteca
é
formada
pela
interface
IPlugin
e
pelas
classes
PluginInstance
,
PluginLoader
e
FileScan
.
A interface
IPlugin
é a base da hierarquia dos plugins e declara apenas o método
abstrato
GetPluginName
. Ela se torna o ponto de comunicação entre a biblioteca
do plugin concreto e a estrutura responsável por carregar as extensões. No domínio
da biblioteca modelada, nenhuma outra interface de plugin é conhecida. A
especialização da interface
IPlugin
deve ser concebida pela aplicação ou
framework sobre o qual a aplicação é implementada. O método
GetPluginName
deve ser implementado pelo plugin concreto, onde informará o nome da extensão.
Essa informação poderá ser utilizada para apresentar ao usuário o nome da
extensão carregada.
A classe
PluginInstance
é a classe responsável por encapsular o caminho da
biblioteca do plugin instalada e uma referência para a instância do plugin. Os
métodos
Init
e
Stop
permitem alocar e desalocar a instância de um plugin,
fazendo com que a extensão não tenha que estar em memória durante todo o tempo
de vida da aplicação.
Junto com
PluginInstance
, a classe
PluginLoader
é responsável por executar
o carregamento dinâmico dos plugins. Ao disponibilizar os métodos
GetPlugin
e
GetPlugins
, a classe possibilita ao cliente passar o caminho de um único plugin ou
o caminho do diretório onde estão todas as extensões. Para encontrar os caminhos
dos plugins em um diretório, o
PluginLoader
utiliza a classe utilitária
FileScan
.
3.1.1 A biblioteca em C++
Com o projeto da biblioteca conceitualmente modelado, desenvolvemos sua versão
para C++. Os seguintes aspectos motivaram a escolha da linguagem:
1) O sistema proposto para ser adaptado à tecnologia de plugins é desenvolvido
em C++;
2) O conhecimento da linguagem pelo autor.
Essa biblioteca foi desenvolvida na forma de uma biblioteca estática (.lib) e deve ser
utilizada incluindo os arquivos de cabeçalhos das classes (.h) dentro do código da
aplicação e lincando a biblioteca ao executável após a compilação do sistema.
Na linguagem C++, as extensões concretas devem ser compiladas no formato de
DLL (dynamica-link library ou biblioteca de ligação dinâmica). Por este motivo, toda
extensão deve exportar a função
CreateInstance
, possibilitando a aplicação ler a
DLL e criar os plugins. Esta função deve retornar um ponteiro com um plugin
alocado. A figura 8 ilustra como que a função
CreateInstance
é declarada.
Para o carregamento das DLLs, as funções da API do Windows
LoadLibrary
,
GetProcAddress
e
FreeLibrary
foram utilizadas. A plataforma UNIX também
possui funções equivalentes às do Windows, o que possibilita que essa biblioteca
seja portada para outros sistemas operacionais além do Windows. Apresentamos na
tabela 1 as equivalências entre as funções para manipulação de bibliotecas
dinâmicas no Windows e no Unix.
Quadro 1 - Equivalências entre API do Windows e do UNIX.
API do Windows
API do UNIX
Cabeçalho das funções
windows.h
dlfcn.h
Função para carregar
LoadLibrary
LoadLibraryEx
dlopen
Função para extrair
conteúdo
GetProcAddress
dlsym
Função para
descarregar
FreeLibrary
dlclose
Para auxiliar no desenvolvimento, um projeto de teste foi formado. Nele, foram
criados testes unitários que garantem os objetivos descritos no início deste capítulo.
Além deste projeto, duas DLLs que implementam a classe
IPlugin
foram criadas
para tornar possível a realização dos testes sem depender da aplicação principal.
3.2 A Adaptação de um Sistema Existente
Este capítulo tem como objetivo descrever a adaptação de uma aplicação real,
desenvolvida em C++, para utilizar o conceito de plugins estudado nos capítulos
anteriores.
Na primeira parte deste capítulo, apresentaremos o sistema que será adaptado,
assim como aspectos da sua arquitetura. Logo após, abordaremos o padrão de
projeto que auxiliou na refatoração (refactoring) da aplicação para uma arquitetura
intermediária, de modo a facilitar a adaptação para a arquitetura proposta. Por fim,
demonstraremos a aplicação da tecnologia de plugins ao sistema, descrevendo as
interfaces criadas, os plugins concretos, o carregamento e a ativação dos plugins.
3.2.1 O OpenPaint
O sistema escolhido para aplicar o conceito de plugins se chama OpenPaint. Ele é
um editor de imagens open source com funcionalidades semelhantes às do Paint,
editor de imagens distribuído com as versões do sistema operacional Microsoft
Windows. A figura 9 apresenta o layout do OpenPaint.
Figura 9. Layout do OpenPaint.
Fonte: openpaint.org
Entre seus recursos, o OpenPaint apresenta:
• Layout familiar;
• Suporte a múltiplas abas;
• Suporte a um grande número de tipos de imagens;
• Ferramentas como lápis, pincel, borracha, ampliação, seleção, entre outros;
• Opções para escalonar, redimensionar, girar e espelhar as imagens;
• Filtros de imagens.
O OpenPaint é um sistema implementado em C++, utilizando o toolkit gráfico open
source chamado wxWidgets. Sua arquitetura não é divida em camadas e suas
classes são grandes. Na próxima seção abordaremos aspectos da arquitetura do
OpenPaint.
3.2.2 Aspectos da arquitetura atual do OpenPaint
O OpenPaint não é um sistema dividido em camadas. Todas as funcionalidades da
aplicação são implementadas dentro da classe
OpenPaintMDIChildFrame
, que é
o frame responsável pelo desenho. Suas funcionalidades podem ser classificadas
em dois tipos:
• Funcionalidades que executam apenas uma operação e são finalizadas.
Como exemplo, podemos citar a aplicação de filtro em uma imagem;
• Funcionalidades que reagem aos eventos do mouse, executando
transformações na área de desenho. Como exemplo, temos as funções de
pintar e apagar.
Para cada funcionalidade do primeiro tipo, a classe
OpenPaintMDIChildFrame
fornece um método público, que é executado quando o cliente clica em um menu da
interface gráfica. Já para as funcionalidades que reagem aos eventos do mouse, a
classe implementa o método OnMouse que, através das instruções de controle
condicional
if/else
e
switch
, executa a função requisitada. Abaixo,
apresentamos um fragmento do método
OnMouse
.
Figura 10. Método OnMouse antes do refactoring.
Com essa estrutura, a classe
OpenPaintMDIChildFrame
precisa ser alterada
a
cada inclusão de nova funcionalidade, aumentando ainda mais o seu tamanho e a
complexidade do método
OnMouse
.
Outro aspecto negativo da estrutura do OpenPaint é o fato da aplicação ser
implementada dentro da interface gráfica. Com a atual abordagem, qualquer
alteração feita no sistema pode impactar em outras áreas da aplicação, além de
inviabilizar a inclusão de testes unitários de maneira significante.
3.2.3 Padrões de projetos
Por causa da falta de modularidade do OpenPaint, antes de começar a aplicar o
conceito de plugins, precisamos isolar as funcionalidades que se encontram
implementadas no frame de desenho em estruturas genéricas. Para isso, aplicamos
o padrão Command (comando), criando uma hierarquia de classes que reflita as
necessidades da aplicação.
3.2.3.1
O padrão Command
De acordo com Gamma et. al. (1995), o padrão Command tem como objetivo
“encapsular uma solicitação como um objeto, desta forma permitindo parametrizar
clientes com diferentes solicitações, enfileirar ou fazer registros (log) de solicitações
e suportar operações que podem ser enfileiradas”. Desta forma, todas as
solicitações compartilham uma interface em comum, permitindo que todas sejam
invocadas da mesma maneira. A estrutura do padrão é apresentada na figura 11.
Figura 11. Estrutura do padrão Command.
Fonte: Gamma et. al., 1995
Na estrutura definida por Gamma et. al. (1995), os participantes do padrão possuem
as seguintes responsabilidades:
• Command: classe abstrata ou interface que declara uma operação que será
implementada pelas suas classes filhas;
• ConcreteCommand: é o objeto concreto de Command que implementa o
método Execute. Pode vincular o Receiver a uma ação;
• Client: cria os objetos ConcreteCommand. Num sistema real, pode ser a
própria aplicação;
• Invoker: solicita ao Command a execução da solicitação;
• Receiver: sabe como executar as operações associadas a uma solicitação.
Gama e. al. (2005) ainda discutem o quanto de inteligência um comando deve ter,
sendo que a implementação do padrão pode variar numa gama de possibilidades.
Em sua forma mais simples, o padrão Command pode servir apenas como vínculo
entre uma ação e um Receiver. Em outro extremo, o padrão implementa toda a
lógica, sem delegar nenhuma tarefa ao Receiver.
A aplicação do padrão Command desacopla o objeto que chama uma função do
objeto responsável em realizá-la, além de facilitar a adição de novos comandos à
aplicação.
3.2.3.2
A aplicação do padrão
Um dos problemas para aplicar o conceito de plugins no OpenPaint é a falta de
abstrações que encapsulem as funcionalidades do sistema. Como foi observado em
seções anteriores, a classe
OpenPaintMDIChildFrame
é responsável pela
implementação das funcionalidades e a execução é realizada no método OnMouse.
Toda execução é orientada através de uma grande lógica condicional if/else
aninhada a um switch.
Em Refactoring to Patterns (Refatorando para Padrões), Kerievsky (2008) sugere
que a lógica condicional utilizada para executar ações seja substituída pelo padrão
Command, quando:
• Existe pouca flexibilidade em tempo de execução, onde os clientes dependem
de um mecanismo de envio condicional e não podem configurá-lo
dinamicamente;
• O corpo dos mecanismos de lógica condicional se torna grande.
Desta forma, refatoramos as funcionalidades para classes que estendam as
interfaces IMenuCommand e IToolCommand. As interfaces dos comandos estão no
Apêndice A e uma representação resumida é demonstrada na figura 12.
Figura 12. Representação resumida dos comandos.
As interfaces dos comandos foram divididas em duas, IToolCommand e
IMenuCommand, devido ao fato da aplicação já tratar de forma diferente as
funcionalidades que necessitam interação com o mouse das que não necessitam.
A interface IMenuCommand é implementada nos casos das funcionalidades que
executam uma ação sobre a área de desenho, sem necessitar de interação do
mouse. Assim, ela é ativada e utilizada apenas no escopo do método que trata os
eventos de menu. O trecho de código abaixo ilustra a sua utilização.
Figura 13. Funcionalidade sem interação com o mouse.
Já nos casos dos comandos que necessitam de interação com o mouse, foi utilizada
a estratégia de manter o comando ativado pelo usuário em uma variável de instância
chamada
comandoAtual
. Ou seja, quando o usuário clicar em um comando que
necessite interação com o mouse, como no caso de pintar, o comando escolhido
será atribuído como comando atual da aplicação e, a partir desse momento, o
método OnMouse delegará ao comando o tratamento das solicitações do usuário,
como ilustrado no trecho de código abaixo.
Figura 14. OnMouse utilizando o padrão Command.
3.2.4 O conceito de plugins no OpenPaint
Com a extração das funcionalidades da aplicação
em comandos, o processo de
adaptação do sistema para aceitar plugins foi facilitado. Porém, nesta versão do
OpenPaint, possibilitaremos apenas a criação de plugins da primeira geração, ou
seja, apenas a aplicação disponibilizará pontos de extensão. Isto se deve ao fato do
atual design do sistema ser pouco orientado a objetos, não possuindo real divisão de
suas camadas.
Para identificar quais são os possíveis candidatos a plugin em uma aplicação,
Marquart (2005) sugere verificar os pontos em aberto na especificação da
aplicação. Frequentemente, um tipo de extensão pode estar implicita em frases da
especificação que possuem a palavra “etc”. Subclasses de uma abstração chave
também são candidatas, quando se tem interesse em extender o sistema
adicionando outras subclasses. Desta forma, no OpenPaint todas as funcionalidades
disponíveis na interface gráfica para o cliente são candidatas a se tornarem
extensões.
Seguindo a arquitetura proposta por Mayer et al. (2002), detalharemos nas próximas
subseções a construção das interfaces dos plugins, dos plugins concretos e do
PluginLoader.
3.2.4.1
Interfaces dos plugins
De acordo com o conceito de plugins, as interfaces são as responsáveis por
possibilitar a comunicação entre a aplicação e suas extensões.
No caso do OpenPaint, apenas as funções disponíveis no menu e na barra de
ferramentas do sistema se tornaram plugins. Assim, as interfaces dos comandos
descritas em seções anteriores foram aproveitadas, com pequenas alterações.
Como descrevemos nos capítulos anteriores, a aplicação deve especializar a
interface
IPlugin
, criando novas interfaces de extensão. Para viabilizar a
comunicação entre a aplicação e os plugins, criamos as seguintes interfaces:
• IToolPlugin: estende a interface IPlugin, permitindo o tratamento dos eventos
do mouse MouseDown (início do clique do mouse), MouseMove (movimento
do mouse sobre o frame) e MouseUp (fim do clique do mouse) sobre a área
de desenho;
• IMenuPlugin: estende a interface IPlugin, possibilitando executar ações no
frame de desenho, sem que haja interação com o mouse;
• IPaintPainel: essa interface encapsula as principais operações realizadas em
cima do frame de desenho. Toda alteração na área de desenho é realizada
através desta interface;
• ISelectionAttributes: essa interface é acessada através da interface
IPaintPainel e possui informações a respeito de seleção realizada na área de
desenho.
Apresentamos no Apêndice B o diagrama de classes descrevendo as interfaces
apresentadas acima.
Para melhorar a organização do projeto da aplicação, as interfaces foram extraídas
para um novo projeto chamado OpenPaintAPI, onde os desenvolvedores da
aplicação e dos plugins compartilham a mesma biblioteca. Em aplicações melhores
estruturadas, essas interfaces seriam candidatas a fazer parte de um framework da
aplicação.
3.2.4.2
Os plugins concretos
Os plugins concretos são implementações das interfaces publicadas pelo OpenPaint
e necessitam ser carregados de forma dinâmica. Em C++, o modo de realizar esse
carregamento dinâmico é através da utilização de DLLs. A DLL (Dynamic-link Library
ou biblioteca de ligação dinâmica) é uma implementação feita pela Microsoft que
contem código executável, dados e recursos.
Com a lógica das funcionalidades encapsuladas em comandos, bastou criar um
projeto de DLL para cada funcionalidade, corrigindo as diferenças existentes entre
as interfaces dos comandos e dos plugins. Os requisitos mínimos dos projetos são:
• Arquivo que define o ponto de entrada da DLL. Geralmente é nomeado como
dllmain.cpp;
• Arquivo com a implementação do plugin;
• Arquivo com a função de criação do plugin. É esta função que é exportada
junto com a DLL e que é utilizada pela aplicação para carregar o plugin.
Abaixo, mostramos o código gerado pela implementação de uma extensão da
interface IMenuPlugin.
Figura 15. Extensão concreta.
Para a aplicação, a única função conhecida é a função
CreateInstance,
sendo
que todas as implementações de plugins devem exportar esta função.
3.2.4.3
O carregamento dos plugins
As DLLs dos plugins devem ser colocadas na pasta plugins, dentro do diretório da
aplicação. Durante o início do sistema, todas as DLLs são lidas, com a finalidade de
criar uma estrutura interna para montar a interface gráfica.
Para realizar a leitura das extensões, a aplicação deve utilizar a classe
PluginLoader
, informando a pasta onde os plugins estão armazenados e a
extensão do arquivo da biblioteca dinâmica. Como retorno, a aplicação recebe um
vetor de plugins que são utilizados para construir a interface gráfica. A figura 16
mostra como que os plugins são carregados.
Figura 16. Carregamento dos plugins.
Ainda durante o carregamento dos plugins, cada extensão é armazenada dentro de
um mapa, através da chamada do método
setPlugins
. Cada extensão recebe um
identificador único, que será utilizado para ativação do plugin, após o mesmo ser
escolhido na interface gráfica pelo usuário. O Apêndice C apresenta o diagrama de
seqüência com as interações realizadas durante o carregamento dos plugins, assim
que a aplicação é iniciada.
3.2.4.4
A ativação de um plugin
Como vimos na seção anterior, cada plugin recebe um identificador único ao ser
carregado. Esse identificador é utilizado para criar a correspondência dos menus e
das ferramentas de desenho na interface gráfica, sendo que cada componente
gráfico recebe o identificador do plugin que o corresponde.
Na figura 17, percebemos que quando o usuário clica em um componente da
interface gráfica, um evento é disparado delegando a classe
OpenPainCtrl
o seu
tratamento. Na classe
OpenPaintCtrl
, o plugin é localizado através de seu
identificador único e seu algoritmo executado.
Figura 17. Diagrama de seqüência demonstrando um clique no menu.
No caso de ferramentas de desenho, a diferença está no fato de que o plugin não é
executado e sim atribuído como plugin atual da aplicação. Desta forma, todos os
eventos de
MouseDown
,
MouseMove
e
MouseUp
que acontecem na janela de
desenho são delegados ao plugin.
3.3 Estendendo a Aplicação
Após a refatoração e a adaptação da aplicação, o OpenPaint se tornou um sistema
possível de extensão através da criação de novos plugins. Para tanto, as novas
extensões devem implementar as interfaces
IToolPlugin
ou
IMenuPlugin
e
compilá-las no formato de uma DLL, exportando a função de criação
CreateInstance
.
A seguir, demonstraremos a criação de uma ferramenta de desenho para aplicação.
3.3.1 Criando um plugin para a aplicação
Para iniciar, devemos levantar o que a ferramenta proposta deve realizar. Como
pré-requisitos, definimos que a ferramenta necessita:
• Possibilitar ao usuário desenhar um triângulo vazado na janela de desenho
com um único clique;
• Utilizar a cor definida pelo usuário na paleta de cores para desenhar o
contorno do triângulo.
Com os objetivos da ferramenta definidos, o próximo passo é criar um projeto
console no Visual C++, informando o tipo de aplicação como DLL. Automaticamente,
o Visual C++ cria os arquivos necessários para o projeto. No caso do nosso plugin, o
nome dado ao projeto foi
ToolTriangle
.
Após a criação do projeto, foi necessário configurá-lo. Para isso, precisamos
informar ao projeto as pastas com os arquivos de cabeçalho (extensão “.h”) das
interfaces e das bibliotecas utilizadas para auxiliar na criação de plugins, como o
PluginCore
e a
OpenPaintAPI
. Além dessas, também foi necessário incluir a
biblioteca
wxWidgets
, que é o toolkit gráfico utilizado pela aplicação, tanto para
criação da interface gráfica com o usuário quanto para as estruturas de dados, como
exemplo do mapa de bits.
Na figura abaixo, demonstramos o algoritmo utilizado para desenhar um triângulo na
área de desenho. Nele, definimos os três pontos do triângulo a partir do seu centro,
e utilizamos a classe
wxMemoryDC
para desenhar o triângulo no bitmap atual. Ao
final, informamos ao painel de pintura a imagem atualizada.
Figura 18. Método da classe ToolTriangle que desenha um triângulo na interface.
Para que a extensão seja encontrada pela aplicação, é necessário declarar e
exportar a função que cria o plugin. Na figura 19 apresentamos está função.
Figura 19. Função de criação do plugin triângulo.