E se o compilador não suportar interfaces de deslocamento zero do VMT

Para que serve?


Geralmente é necessário escrever plugins para programas. Porém, devido à incompatibilidade binária das classes, esses plugins precisarão ser escritos no mesmo idioma do programa principal. Em C ++, é habitual colocar a tabela de funções virtuais primeiro na classe. Se você usar determinadas regras (não usar herança múltipla de interfaces) e usar classes abstratas, poderá obter a capacidade de executar plug-ins compilados em diferentes compiladores C ++.

Neste artigo, mostrarei como usar um plug-in escrito usando o Free Pascal Compiler em um programa C ++ (apenas uma idéia geral, não um plug-in real).

O que é VMT?


A tabela de método virtual (VMT) é uma tabela de coordenação ou vtable é um mecanismo usado em linguagens de programação para oferecer suporte à correspondência dinâmica (ou ligação tardia).

Os padrões C ++ não definem claramente como a coordenação dinâmica deve ser implementada, mas os compiladores geralmente usam algumas variações do mesmo modelo base.

Normalmente, o compilador cria uma tabela separada para cada classe. Após criar um objeto, um ponteiro para esta tabela, chamado ponteiro de tabela virtual ou vpointer (também chamado de vptr ou vfptr), é adicionado como um membro oculto do objeto (e geralmente como o primeiro membro). O compilador também gera código "oculto" no construtor de cada classe para inicializar vpointer'ov seus objetos com os endereços da tabela v correspondente.
(Parágrafos retirados da Wikipedia.)

Implementação.


Primeiro, precisamos criar um wrapper em torno do código em pascal.

plugin.hpp
#pragma once #include "ApiEntry.hpp" class IPlugin { public: virtual void APIENTRY free () = 0; virtual void APIENTRY print () = 0; }; class Plugin : public IPlugin { public: virtual void APIENTRY free (); virtual void APIENTRY print (); Plugin (); virtual ~Plugin (); private: void* thisPascal; }; extern "C" IPlugin* APIENTRY getNewPlugin (); 

Onde IPlugin é a interface do plug-in. E thisPascal é um ponteiro para uma versão binária da classe de implementação de interface no pascal.

E o próprio código do wrapper: plugin.cpp
 #include "plugin.hpp" #include "pascalunit.hpp" #include <iostream> void APIENTRY Plugin::free () { IPlugin_release (thisPascal); delete this; } void APIENTRY Plugin::print () { IPlugin_print (thisPascal); } Plugin::Plugin () { std::cout << "Plugin::Plugin" << std::endl; thisPascal = IPlugin_getNewPlugin (); } Plugin::~Plugin () { std::cout << "Plugin::~Plugin" << std::endl; } extern "C" IPlugin* APIENTRY getNewPlugin () { Plugin* plugin = new Plugin (); return plugin; } 

Como você pode ver, o código chama funções da biblioteca em pascal e passa a elas um ponteiro para a implementação do plug-in em pascal que foi salvo anteriormente ao criar a classe. O getNewPlugin é chamado para instanciar a classe de plug-in no programa principal.

Agora vamos falar sobre a implementação do plugin no Pascal.

 library pascalunit; {$MODE OBJFPC} uses ctypes; type IPlugin = interface procedure _release (); cdecl; procedure print (); cdecl; end; TPlugin = class (TInterfacedObject, IPlugin) public procedure _release (); cdecl; procedure print (); cdecl; constructor Create (); destructor Free (); end; PPlugin = ^TPlugin; procedure TPlugin._release (); cdecl; begin Free; end; procedure TPlugin.print (); cdecl; begin writeln ('Hello World'); end; procedure _release (this: PPlugin); cdecl; begin this^._release (); end; procedure print (this: PPlugin); cdecl; begin this^.print (); end; constructor TPlugin.Create (); begin inherited; writeln ('TPlugin.Create'); end; destructor TPlugin.Free (); begin writeln ('TPlugin.Free'); end; function getNewPlugin (): PPlugin; cdecl; var plugin: PPlugin; begin New (plugin); plugin^ := TPlugin.Create (); result := plugin; end; exports getNewPlugin name 'IPlugin_getNewPlugin', print name 'IPlugin_print', _release name 'IPlugin_release'; begin end. 

Esse arquivo implementa quase a mesma interface no pascal e envolve as funções de plug-in para exportar funções para a biblioteca. Observe que todas as funções de implementação da interface contêm o ponteiro para a classe como o primeiro parâmetro. Este parâmetro é passado implicitamente para métodos de classe como o primeiro parâmetro e é necessário para acessar os métodos e campos da classe. A função getNewPlugin é usada para obter um ponteiro em uma classe C ++. O código Pascal está conectado como uma biblioteca.

PS: Eu esqueci de mencionar que o código em pascal deve / deve preferencialmente ser envolvido em try / catch, pois as exceções não devem ser lançadas neste método de plug-in. O plug-in deve lidar com suas exceções e retornar os resultados imediatamente ou como uma função separada na forma de tipos simples.

PS2: adicionou um comentário sobre a função livre e alterou seu código. Não adicionarei alterações aqui para manter a conformidade com os comentários. E ele adicionou um comentário sobre o uso da função getNewPlugin e a exclusão do objeto em aplicativos de terceiros. Embora uma pessoa que conheça as interfaces, isso já esteja claro.

Fontes de amostra

Source: https://habr.com/ru/post/pt409565/


All Articles