Criando uma estrutura para plugins em aplicações KDE

Nesse artigo, que se trata de uma tradução do tutorial escrito por Jesper K. Pedersen para o site developer.kde.org, será abordado com criar plugin para aplicações KDE.

Plugins fornecem a capacidade para que as aplicações carreguem funcionalidades extras em tempo de execução. Além de ser uma coisa legal de dizer que suia aplicação possui, também vem com certos valores de verdade:

  • Permite personalizar as aplicação de formas que o autor nunca imaginou, ou não desejava embutir na aplicação.
  • Permite espalhar responsabilidade e honrar. Muitas pessoas trabalham em programas de código aberto graças a honra de ser o autor de uma parte do código. Com plugins, uma pessoa pode desenvolver um, e chamar de seu.
  • Permite compartilhar código entre aplicações não relacionadas, se elas compartilharem somente uma estrutura de plugin comum. Um exemplo seria visualizadores de imagem. Muitos visualizadores diferentes existem, cada um com sua característica única. Ter um sistema de plugin comum, torna possivel desenvolver plugins que trabalham com cada visualizador em particular.
  • Finalmente, permite fazer que parte de uma aplicação esteja disponivel de alguma biblioteca de terceiros não estiver disponivel no sistema.

Esse documento irá seguir todos os passos requeridos para construir a estrutura de plugins para uma aplicação do KDE. Pode ser lido mesmo que você não for escrever uma estrutura de plugin para uma aplicação, mas apenas quer escrever um plugin.

Existem dois tipos de plugins:

  • O primeiro tipo implementa uma intreface especificada pela aplicação alvo. Isso é similar a implementar uma classe virtual pura, com a exceção que a classe está em uma biblioteca dinâmica, Esse tipo de plugin é frequentemente referênciado como um módulo carregável. Exemplos disso são os carregadores de formato de imagem, marcadores de sintaxe, e importação/exportação de dados de formatos diversos. A vantagem desses plugins é que partes de terceiros podem adicionar novos formatos para a aplicação.
  • O segundo tipo de plugins traz novas funcionalidades e ações para as barras de menu e ferramentas. Exemplos desse tipo incluem geradores de HTML a partir de uma gerenciador de imagem (que antes não possuia nenhum recurso de exportação para HTML), adição de verificação ortográfica a um editor, ou validação de HTML a um navegador. A vantagem desse tipo de plugin é que essas funcionalidades extras pode ser adicionadas por terceiros com as imlicações descritas na seção acima.

O foco principal desse documento é no segundo tipo, mas qualquer comentário exposto levará em conta a diferenças entre os dois tipos.

A aplicação exemplo

Junto com esse documento seguiremos por uma aplicação exemplo com uma estrutura para plugins. A aplicação pode ser baixada aqui, e no decorrer desse documento, ela será referenciada como exemplo em execução.

Os arquivos  serão separados em três diretórios:

Os passos

Os passos necessários para construir a estrutura para plugins para uma aplicação asão basicamente esses:

  • Desenvolva uma classe base para que os plugins implementem.
  • Desenvolva uma interface para que os plugins possam usar para acessar a aplicação.
  • Implemente a interface  a partir da aplicação alvo.
  • Carregue os plugins na aplicação alvo.
  • Implemente os plugins usando a interface para se comunicar com a aplicação.

A ordem dos passos são descritas nesse documento de uma forma um pouco diferente da ordem acima, simplesmente para tornar o assunto mais compreensivel.

Nós começaremos olhando como todas as coisas acima são feitas do ponto de vista do C++, e então olharemos quais arquivos de suporte são necessários (ex.: Makefile.am e arquivos .desktop).

Classe base para os plugins

No KDE, os seguintes passos são seguidos quando um plugin é carregado: A aplicação acha a biblioteca dinâmica que é o plugin, a biblioteca é carregada, uma função C especial é pesquisada, e quando encontrada é executada. O retorno dessa função C precisa ser algo que tenha valor para o programa que carrega o plugin. No KDE, precisa retornar o ponteiro para a classe base definida pela chamada da função.

Em outras palavras, o resultado do carregamento do plugin é um ponteiro para uma classe, que a aplicação conhece. Portanto, nossa primeira tarefa quando estivermos desenvolvendo a estrutura para plugins é criar uma classe base virtual que os plugins devem implementar.

Em nossa aplicação exemplo, essa classe é interfaces/plugindemo/plugin.h. Essa classe é, como todos os arquivos do sub-diretório modelo, parte do namespace PluginDemo. Isso não é estritamente necessário mas evita choques de nomes, e torna mais óbvio de onde as coisas vem. Do mesmo mode que o arquivo está no sub-diretório plugindemo, o que significa que a inclusão do arquivo é feita por uma diretiva #include <plugindemo/plugin.h>, isso também evita choques de nomenclatura.

O importante a observar nessa classe é que ela herda de KXMLGUIClient. Esse fato atinge a aplicação alvo que pode misturar as ações de sua barra de menu e ferramentas com as do plugin. Nós discutiremos como fazer isso mais adiante.

Os dois outros metódos definidos na classe, são chamados editorInterface() e selectionInterface(). Para o plugin ser útil, precisa ser capaz de acessar partes internas da aplicação que ele se conecta. Esses dois metódos retornam a interface para isso. A interface é implementada, como veremos mais adiante, pela aplicação alvo. Por agora, somente aceite que os plugins podem acessar a aplicação alvo por meio dessas duas classes que podem ser encontradas em interfaces/plugindemo/editorinterfaace.h e interfaces/plugindemo/selectioninterface.h.

Quando esse plugin for um módulo carregavel, você deve ter as seguintes diferenças:

  • Nós não teremos uma classe que herda de KXMLGUIClient.
  • Uma ou mais funções para fazer o que o plugin tem que fazer deve ser adicionada a interface. Ex.: uma função decode() para um decodificador de formato de imagens.
  • Talvez não seja preciso dos metódos de interface (ex.: editorInterface() ) caso as funções específicas dos plugin (ex.: decode() ) sejam iniciadas pela alicação alvo, e tragam todas as informações relevantes para o plugin como argumentos dessas funções.

Desenvolvendo plugins

Vamos pular para os plugins, e ver como eles são implementados. Um plugin exemplo pode ser encontrado em plugindemo-capitalize/capitalizeplugin.h e plugindemo-capitalize/capitalizeplugin.cpp. Esse plugin usa a interface selection para mudar para maiuscula a primeira letra de cada palavra em uma seleção de texto.

A primeira coisa a observar é que a classe herda de PluginDemo::Plugin, e isso é requisito para ser um plugin. Do ponto de vista do C++ ela é uma classe C++ normal, que naturalmente pode implementar slots, ter variáveis de instância, se conectar a signals, etc. A única coisa diferente sobre essa classe é como ela é construída. Ela é construída pela aplicação alvo pelo carregamento dinâmico da biblioteca em que é definida, e por um conjunto de indireções, criando uma instância dela.

Entendendo o mecanismo de carregamento

Para entender como todo esse processo funciona, dê uma olhada na implementação (plugindemo-capitalize/capitalizeplugin.cpp). O ponto crucoal é encontrado no topo do arquivo, onde você verá essas linhas de código:

  K_EXPORT_COMPONENT_FACTORY( plugindemo_capitalize,
      KGenericFactory<CapitalizePlugin>( "plugindemo_capitalize" ) )

K_EXPORT_COMPONENT_FACTORY é uma macro definida pelo KDE. VocÊ pode apenas pular para a lista pontilhada abaixo para ver como escreve-la, mas para realmente entender o que acontece é útil olhar para sua definição, que é:

  #define K_EXPORT_COMPONENT_FACTORY( libname, factory ) \
    extern "C" { void *init_##libname() { return new factory; } }

Isso revela que a macro exande a definição de uma função C, e a função retorna uma instância da classe definida pelo segundo argumento da macro.

O segundo argumento é um pouco mágico:

  KGenericFactory<CapitalizePlugin>( "plugindemo_capitalize" )

KGenericFactory é um modelo, que pega uma classe para o instanciamento do modelo. O KGenericFactory tem um metódo necessário para tudo isso se encaixar junto, chamado metódo create(). Esse metódo quando chamado de um sub-sistema KDE, cria uma instância da classe que foi fornecida durante o instanciamento do modelo.

Se tudo isso parece muito obscuro para você, ou extremamente complexo, não se incomode, não é importante para o desenvolvimento de plugins, mas meramente incluido aqui para que quiser conhecer toda a glória de detalhes. Tudo que precisa lembrar são os seguintes passos:

  • Insira o K_EXPORT_COMPONENT_FACTORY no arquivo cpp definindo a classe do plugin.
  • Adicione uma sentença #include <kgenericfactory.h>
  • No primeiro argumento fa macro, você precisa especificar a biblioteca na qual essa classe reside (sem o prefixo lib, e sem a extensão .la)
  • No segundo argumento, você precisa especificar, entre colchetes, o nome da classe onde você definiu o plugin.
  • Finalmente, dentro dos colchetes você precisa especificar uma string única, que lhe fornecerá uma criação do objeto LInstance. KInstance é responsavel por especificar onde os arquivos de tradução e configuração, etre outras coisas, estão localizados.

Juntando uma interface com o usuário com a aplicação alvo

Olhando o corpo do construtor você verá essas duas linhas:

    setInstance(KGenericFactory<CapitalizePlugin>::instance());
    setXMLFile("plugindemo_capitalizeui.rc");

O que ela fazem basicamente é carregar a insterface do plugin, e junta-las com a aplicação alvo. Isso significa que o plugin pode adicionar itens à barra de menu ou de ferramentas da aplicação alvo.

O nome especificado como argumento de setXMLFile() é um arquivo de recursos, que precisa ser instalado pelo Makefile.am para o plugin. Os detalhes sobre esse arquivo Makefile.am serão explicados adiante.

O arquivo de recursos especifica uma ação a ser colocada na barra de menu Edit. Essas ações precisam estar configuradas no construtor. Por isso uma instância de KApplication é criada.

Comunicação com a aplicação alvo

A comunicação do plugin com a aplicação alvo acontece através das interfaces disponíveis na super classe (interfaces/plugindemo/plugin.h). Um exemplo disso pode ser visto no metódo slotCapitalize(), que é invocado quando o usuário seleciona nossa ação na barra de menu. Nesse metódo, nós pedimos a interface de seleção pelo texto da seleção, colocamos as palavras do texto em maiuscula, e devolvemos o texto.

Carregando plugins

Agora é a hora de dar uma olhada na aplicação alvo disponível em application/mainwindow.cpp, e revelar quão fácil é carregar um plugin.

A primeira coisa a observar sobre a aplicação alvo é que sua janela principal herda de KMainWindow. Isso é necessário para poder juntar as barras de menu e ferramentas.

O carregamento dos plugins é feito pela função loarPlugins(). A primeira linha dessa função é:

    KTrader::OfferList offers = KTrader::self()->query("PluginDemo/Plugin");

O que ela faz, basicamente, é pedir ao sistema KDE para retornar uma lista de plugins que correspondam ao tipo PluginDemo/Plugin. Cada plugin precisa vir com um arquivo desktop que especifica o tipo do plugin. Nós iremos discutir o arquivo desktop em detalhes mais tarde.

No próximo passo, nós percorremos a lista de plugins, e carregamos cada um deles por vez. Isso é feito através do código:

    for(iter = offers.begin(); iter != offers.end(); ++iter) {
        KService::Ptr service = *iter;
        int errCode = 0;
        PluginDemo::Plugin* plugin =
            KParts::ComponentFactory::createInstanceFromService
            ( service, _pluginInterface, 0, QStringList(), &errCode);

Todas a coisas pesadas envolvendo a abertura do arquivo e chamada da função init fica a cargo da função createInstanceFromService.

Muitas coisas podem acontecer quando um plugin é carregado, como uma coisa que não seja um plugin para sua ap-licação, o usuário ter errado alguma coisa no arquivo desktop, etc. Por isso é muito importante checar que nos foi retornado um ponteiro da chamada à função createInstanceFromService(). Nós devemos obviamente avisar ao usuário se nós conseguimos carregar ou não o plugin, mas para simplificar o exemplo nós pularemos esa parte aqui.

Você pode notar que o segundo parâmetro de createInstanceFromService() é _pluginInterface, que é um ponteiro pai, mas nesse framework precisa ser especial de maneiras descritas na próxima seção.

Uma vez que o plugin foi carregado, é hora de adicionar um KXML factory, então assim a interface do plugin será juntada à da aplicação alvo. Isso é feito pela chamada de metódo:

  guiFactory()->addClient(plugin);

Comunicação entre os plugins e as aplicações alvo

Até agora nós consideramos que a comunicação entre o plugin e a aplicação alvo se passa pelos metódos de interface que são retornados pela classe PLugin de interfaces/plugindemo/plugin.h.

Essas funções, porém, precisam obter seus dados de fora da aplicação alvo de uma maneira ou outra, o que será discutido agora.

Olhando a implementação de selectionInterface() nos mostrará como isso acontece:

PluginDemo::SelectionInterface* PluginDemo::Plugin::selectionInterface()
{
    return static_cast<SelectionInterface*>
           ( parent()->child( 0, "PluginDemo::SelectionInterface" ) );
}

O plugin simplesmente pedi à classe pai pela filha que herda da classe selectionInterface. Em outras palavras, a classe pai precisa configurar uma instância de uma classe que herda de SelectionInterface. Como essa configuração é feita, pode ser visto no arquivo application/mainwindow.cpp:

    _pluginInterface = new QObject( this, "_pluginInterface" );
    new MySelectionInterface( _editor, _pluginInterface, "selection interface" );

No código acima, nós criamos um objeto _pluginInterface, e fornecemos a ele um ponteiro para a classe MySelectionInterface. O ponteiro _pluginInterface é mais tarde fornecido como a classe pai quando construímos o plugin (visto acima):

   KParts::ComponentFactory::createInstanceFromService
     ( service, _pluginInterface, 0, QStringList(), &errCode);

A implementação atual da interface SelectionInterface está em application/myselectionintercae.h e application/myselectioninterface.cpp.

No código acima você pode observar que nenhuma interface foi implementada para a classe EditorInterface. Isso ilustar como um tipo de aplicação alvo pode implementar apenas algumas das interfaces. Imagine que os plugins são compartilhados entre um grande número de aplicações. Nem todas as aplicações podem ser capazes de oferecer a interface completa. Os plugins que precisarem de uma interface não implementada naturalmente não funcionarão quando essa interface não esta implementada. Por isso, quando um plugin precisa de acesso a uma interface, ele deve primeiro checar se essa interface está disponível, e se não disabilita-la.

Arquivos auxiliares necessários para fazer tudo funcionar

Agora você conhece todas as peças de código que juntas formam a estrutura para plugins de sua aplicação. Isso não é suficiente, todavia; o framework do KDE precisa saber que o código que você compilou como um plugin é realmente um plugin, e você precisa dizer ao KDE sobre o tipo de seu novo plugin, que foi usado pelo KTrader acima. Isso será mostrado nessa seção.

O diretório da interface

O Makefile.am de interfaces/plugindemo precisa de uma adição simples comparado ao Makefile.am normal de uma biblioteca, que se trata da seguinte linha:

  kde_servicetypes_DATA = plugindemoplugin.desktop

O que essa linha diz é que o arquivo plugindemoplugin.desktop precisa ser instalado no sistema com um tipo de serviço.

Lembre que quando você carrega um plugin, nós não especificamos um diretório para que o KDE localize-o, nem especificamos um arquivo a ser usado. Tudo que nós especificamos é que queremos carregar um plugin do tipo de serviço PluginDemo/Plugin. O arquivo interfaces/plugindemo/plugindemoplugin.desktop descreve o tipo do serviço:

[Desktop Entry]
Type=ServiceType
X-KDE-ServiceType=PluginDemo/Plugin
Comment=A Demo Plugin

O detalhe importante aqui é a linha X-KDE-ServiceType=PluginDemo/Plugin, onde você reconhecerá o tipo que nós pedimos quando carregamos o plugin.

Esse arquivo pode também conter alguns atributos, que podem ser requisitados quando carregamos o plugin; porém isso está fora do escopo desse artigo.

O diretório do plugin

 

O Makefile.am do diretório plugindemo-capitalize também precisa de algumas linhas especiais.

kde_module_LTLIBRARIES = plugindemo_capitalize.la

Para o plugin ser instalado no lugar correto, precisamos especificar o caminho usando kde_module_LTLIBRARIES.

plugindemo_capitalize_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries)

Os plugins são diferentes das bibliotecas no sentido que eles não contém informações sobre a versão, apenas como você não especifica que quer carregar a versão x.y do konqueror, mas apenas que quer carregar o konqueror. Da mesma maneira, você não especifica que quer carregar uma versão especifica do plugin. Por essa razão, você precisa adicionar o parâmetro -module à -LDFLAGS.

pluginsdir = $(kde_datadir)/plugindemo_capitalize
plugins_DATA = plugindemo_capitalizeui.rc

Como vimos acima, o plugin tem um arquivo de recursos para a interface com o usuário que acompanha o plugin; esse arquivo naturalmente precisa ser instalado no sistema. Que é o que as linhas acima asseguram.

Observe que o nome do arquivo que você especificar como argumento de setXMLFile() precisa corresponder ao nome na linha plugins_DATA, e o nome que você especificar como argumento de KGenericFactory como  parte da macro K_EXPORT_COMPONENT_FACTORY, precisa corresponder ao nome da seção plugins_DATA.

kde_services_DATA = plugindemo_capitalize.desktop

 

Finalmente, o plugin precisa de um arquivo desktop, que nós especificamos na linha acima. O arquivo desktop se parece como isso:

[Desktop Entry]
Name=Capitalize
Comment=Capitalize word plugin for PluginDemo
ServiceTypes=PluginDemo/Plugin
Type=Service
X-KDE-Library=plugindemo_capitalize

Existem três coisas importantes a observar nesse arquivo. Primeiro, a linha ServiceTypes precisa corresponder ao tipo de serviço especificado no arquivo desktop do diretório da interface. Segundo, o K-KDE-Library precisa especificar o nome da biblioteca que onde o plugin é compilado, sem o sufixo .la. Finalmente, não é coincidência que o nome do arquivo tenha o prefixo plugindemo_. Coisas ruins podem acontecer se duas aplicações instalarem um arquivo desktop com o mesmo nome, assim você deve sempre usar um namespace como mostrado acima.

Conclusão

Você acabou de ver como construir um sistema com plugins para sua aplicação KDE, discutimos as vantagens de usar plugins, mas não discutimos como desenhar as suas interfaces de classes. Essa parte é bem parecido com o desenho de uma interface de uma biblioteca. Você deve ser cuidadoso com o que disponibiliza através do plugin. Tornar pouca coisa disponível faz com que o plugin seja inútil. Tornar muita coisa disponível faz com que seja impossivel fazer qualquer alteração sem quebrar a compatibilidade com a aplicação alvo.

Creditos

Esse artigo foi traduzido do site http://developer.kde.org/documentation/tutorials/developing-a-plugin-structure/index.html