from - Llamando a C++ desde D
python wrapper c++ (4)
He revisado los documentos explicando cómo llamar a C ++ desde D que se explica aquí: http://dlang.org/cpp_interface.html Sin embargo, hay un par de cosas que no están del todo claras para mí.
Tomando el ejemplo proporcionado en el sitio web de D:
#include <iostream>
using namespace std;
class D {
public:
virtual int bar(int i, int j, int k)
{
cout << "i = " << i << endl;
cout << "j = " << j << endl;
cout << "k = " << k << endl;
return 8;
}
};
D *getD() {
D *d = new D();
return d;
}
La clase C ++ se puede llamar desde D como se muestra a continuación:
extern (C++) {
interface D {
int bar(int i, int j, int k);
}
D getD();
}
void main() {
D d = getD();
d.bar(9,10,11);
}
Lo que no me queda claro es cómo se elimina el objeto C ++. ¿El recolector de basura D llama al borrado en el objeto C ++ o necesitamos proporcionar una función de "eliminación" que borre el objeto y lo llame manualmente desde D? Me parece que si agrego un destructor a la clase C ++, nunca se llama. También noté que la clase C ++ debe declarar las funciones miembro exactamente en el mismo orden en que están declaradas en la interfaz D (por ejemplo, si agrego un destructor antes del método bar (), el objeto C ++ no puede llamarse desde D, pero si el destructor se declara después del método de barra (), todo funciona bien).
También si la interfaz D se define como:
extern(C++){
interface D{
int bar();
int foo();
}
}
Y la clase correspondiente de C ++ está dada por:
class D{
public:
virtual int bar(){};
virtual int foo(){};
};
¿Cómo puede garantizar que los métodos virtuales de C ++ vtbl se crearán en el mismo orden que los métodos declarados en la interfaz D. Para mí no hay garantía para esto. En otras palabras, ¿cómo podemos estar seguros de que D :: bar () estará en la primera posición en el vtbl? ¿No es esto dependiente de la aplicación / compilador?
Debe agregar un método en su clase D que llame al operador de eliminación de c ++. O bien, puede utilizar un método de destrucción global.
Además, no olvide que cualquier interfaz a otro idioma debe ser declarada como "C" externa para evitar el mal funcionamiento de la función del compilador.
#include <iostream>
using namespace std;
class D {
public:
virtual int bar(int i, int j, int k)
{
cout << "i = " << i << endl;
cout << "j = " << j << endl;
cout << "k = " << k << endl;
return 8;
}
// option 1
virtual void destroy()
{
delete this;
}
};
extern "C"
D *getD() {
D *d = new D();
return d;
}
// option 2
extern "C"
void killD(void* d) {
delete d;
return;
}
Luego, en su código d, debe crear una cláusula de alcance que llame al método de destrucción.
La forma específica en que se implementa esto es que el objeto D simplemente tiene una vtable compatible con C ++. Por lo tanto, solo las funciones virtuales funcionan, y como la tabla está diseñada por índice, deben aparecer en el mismo orden.
D no tiene idea acerca de los constructores, destructores o cualquier otro método especializado de C ++, pero si son virtuales, pueden deshacerse de la vtable.
Escribí un pequeño programa rápido llamado dtoh pendiente de revisión ahora mismo que puede ayudar a generar automáticamente los encabezados de C ++ a partir de la fuente D, para mantener esto simple. Aún no está terminado, pero podría ser útil de todos modos: https://github.com/adamdruppe/tools/blob/7d077b26d991dd5705e834900f66bea737a233b2/dtoh.d
Primero dmd dtoh.d
, dmd dtoh.d
, luego haga el JSON desde su archivo D: dmd -X yourfile.d
, luego ejecute dtoh yourfile.json
y debería escupir un archivo accesible. Pero, como dije, aún no está terminado y aún está pendiente de revisión para el diseño general, por lo que podría ser horrible. Sin embargo, siempre puedes hacer lo que estás haciendo ahora y hacerlo tú mismo.
De todos modos, el objeto que se ve en D es como una Clase * en C ++. Siempre lo pasas a través del puntero, por lo que nunca se hace una construcción o copia.
D y C ++ tampoco entienden los sistemas de asignación de memoria de cada uno. La regla que sigo es cualquier cosa que su biblioteca cree, su biblioteca debería poder destruir. Así que si su programa de C ++ lo hizo nuevo, asegúrese de que también se elimine en C ++. Cualquier objeto que crees en D para pasar a C ++ también debería ser destruido por D ... y es posible que desees hacerlo manualmente. Si su función de C ++ mantiene una referencia al objeto D, pero no hay una en D, ¡podría ser recolectada la basura! Así que querrás asegurarte de que siempre haya una referencia viva en D durante la vida útil del objeto, o crearlo tú mismo con funciones como malloc y gratis.
No me gusta que la persona que llama use incluso genérico free (), ya que la versión no necesariamente coincidirá. Yo digo que siempre provea un método de su biblioteca para liberar cosas. Incluso si su implementación es solo gratuita (ptr) ;, dar su propia función hará que sea explícito que debe usarse y le brinda protección contra tales desajustes.
No esperaría que el recolector de basura de D supiera cómo liberar un objeto C ++. Eso implicaría (al menos) que el tiempo de ejecución D:
- hacer suposiciones sobre el tiempo de ejecución de C ++, es decir, cómo eliminar un objeto de C ++
- que el objeto ya no es necesario por otro código C ++
Estoy seguro de que tendrás que proporcionar otra función de C ++ que llame al objeto que se le pasa. De hecho, muchas bibliotecas de C ++ (incluso cuando también se usan desde C ++) tienen este mismo patrón en los casos en que se llama al constructor desde dentro de la biblioteca. Incluso en C directa, normalmente es una mala idea asignar memoria en una dll / exe y liberarla en otra. Esto puede fallar si los dos binarios no comparten la misma biblioteca de tiempo de ejecución.
Ya que su pregunta tiene el título "Llamando a C ++ desde D", asumiré que está tratando de http://dlang.org/cpp_interface.html .
¿El recolector de basura D llama al borrado en el objeto C ++ o necesitamos proporcionar una función de "eliminación" que borre el objeto y lo llame manualmente desde D?
Por el " objeto C ++ " asumo que te refieres a un objeto asignado usando el new
operador. D no tiene idea de los objetos C ++ creados con el nuevo operador de C ++. Por lo tanto, siempre que necesite eliminar un objeto asignado por C ++, debe proporcionar su propio código para liberar la memoria.
El soporte de C ++ en D es muy limitado, por una buena razón. - El soporte completo de C ++ significaría que un compilador de C ++ completo (con preprocesador de C ++) debería incluirse en el compilador de D. Haría la implementación del compilador D mucho más difícil.
También noté que la clase C ++ debe declarar las funciones miembro exactamente en el mismo orden en que están declaradas en la interfaz D (por ejemplo, si agrego un destructor antes del método bar (), el objeto C ++ no puede llamarse desde D, pero si el destructor se declara después del método de barra (), todo funciona bien).
En este caso particular, creo que primero escribe la clase C ++, teniendo en cuenta que se usará en un proyecto D, y luego escribe la interfaz D. La interfaz D debe coincidir estrechamente con los métodos en la clase C ++ porque el compilador D generará una tabla virtual compatible con C ++.
El soporte de C ++ mejorará, pero D probablemente no tendrá soporte completo de C ++. Se ha trabajado para admitir los espacios de nombres de C ++ (una mejora solicitada por la comunidad D).
Dado que D es totalmente compatible con C, la mejor idea es "aplanar" el código complejo de C ++ a C de manera similar a como se hace en el artículo " Mezcla de C y C ++ ". Hace mucho tiempo usé un enfoque similar para llamar a los métodos C ++ de Delphi.