¿Qué sucede si el compilador no es compatible con las interfaces de desplazamiento cero de VMT?

Para que sirve


A menudo es necesario escribir complementos para programas. Pero debido a la incompatibilidad binaria de las clases, estos complementos deberán estar escritos en el mismo idioma que el programa principal. En C ++, se acostumbra colocar la tabla de funciones virtuales primero en la clase. Si usa ciertas reglas (no use la herencia múltiple de interfaces) y usa clases abstractas, puede lograr la capacidad de ejecutar complementos compilados en diferentes compiladores de C ++.

En este artículo, mostraré cómo usar un complemento escrito usando Free Pascal Compiler en un programa C ++ (solo una idea general, no un complemento real).

¿Qué es VMT?


La tabla de método virtual (VMT) es una tabla de coordinación o vtable es un mecanismo utilizado en lenguajes de programación para admitir la coincidencia dinámica (o enlace tardío).

Los estándares C ++ no definen claramente cómo se debe implementar la coordinación dinámica, pero los compiladores a menudo usan algunas variaciones del mismo modelo base.

Por lo general, el compilador crea una vtable separada para cada clase. Después de crear un objeto, se agrega un puntero a esta vtable, llamado puntero de tabla virtual o vpointer (también llamado a veces vptr o vfptr), como un miembro oculto del objeto (y a menudo como el primer miembro). El compilador también genera código "oculto" en el constructor de cada clase para inicializar vpointer'ov sus objetos con las direcciones de la tabla vtable correspondiente.
(Párrafos tomados de Wikipedia).

Implementación


Primero necesitamos crear un contenedor alrededor del código en 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 (); 

Donde IPlugin es la interfaz del complemento. Y thisPascal es un puntero a una versión binaria de la clase de implementación de interfaz en pascal.

Y el código del contenedor en sí: 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 puede ver, el código llama a funciones de la biblioteca en pascal y les pasa un puntero a la implementación del complemento en pascal que se guardó previamente al crear la clase. Se llama a getNewPlugin para crear una instancia de la clase de complemento en el programa principal.

Ahora hablemos sobre la implementación del complemento en 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. 

Este archivo implementa casi la misma interfaz en pascal y envuelve las funciones del complemento para exportar funciones a la biblioteca. Tenga en cuenta que todas las funciones de implementación de la interfaz contienen el puntero a la clase como primer parámetro. Este parámetro se pasa implícitamente para los métodos de clase como el primer parámetro y es necesario para acceder a los métodos y campos de la clase. La función getNewPlugin se usa para obtener un puntero en una clase de C ++. El código Pascal está conectado como una biblioteca.

PD: Olvidé mencionar que el código en pascal debería / debería estar preferiblemente envuelto en try / catch ya que no se deberían lanzar excepciones en este método de complemento. El complemento debe manejar sus excepciones y devolver los resultados de forma inmediata o como una función separada en forma de tipos simples.

PS2: agregó un comentario sobre la función gratuita y cambió su código. No agregaré ningún cambio aquí para mantener el cumplimiento de los comentarios. Y agregó un comentario sobre el uso de la función getNewPlugin y la eliminación del objeto en aplicaciones de terceros. Aunque es una persona que conoce las interfaces, esto ya estará claro.

Fuentes de muestra

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


All Articles