¿Llamar a C++ (no C) desde Common Lisp?

7 minutos de lectura

unnamed file 105
Arrendajo

Me pregunto si hay alguna forma de llamar al código C ++ desde Common Lisp (preferiblemente de forma portátil, y si no, preferiblemente en SBCL, y si no, entonces Clozure, CLisp o ECL).

El C ++ se llamaría dentro de los bucles para el cálculo numérico, por lo que sería bueno si las llamadas fueran rápidas.

CFFI parece no apoyar esto:

“El concepto se puede generalizar a otros lenguajes; en el momento de escribir este artículo, solo el soporte C de CFFI está bastante completo, pero se está trabajando en el soporte C++”.

(capítulo 4 del manual)

El manual de SBCL tampoco menciona C++; en realidad dice

Este capítulo describe la interfaz de SBCL para programas y bibliotecas C (y, dado que las interfaces C son una especie de
lingua franca del mundo Unix, a otros programas y bibliotecas en general.)

El código C++ usa OO y sobrecarga de operadores, por lo que realmente necesita compilarse con g++.

Y hasta donde yo sé, puedo tener una función main() de C++ y escribir contenedores para funciones de C, pero no al revés, ¿es eso cierto?

De todos modos… ¿Hay alguna manera de hacer esto?

¡Gracias!

Después de la compilación, la mayoría de las funciones de C++ en realidad se reducen a llamadas regulares de funciones de C. Debido a la sobrecarga de funciones y otras características, los compiladores de C++ usan nombre destrozado para distinguir entre funciones con nombres similares. Dada una utilidad de volcado de objetos y suficiente conocimiento sobre su compilador C++, puede llamar al código C++ directamente desde el mundo exterior.

Sin embargo, dicho esto, puede que le resulte más fácil escribir una capa compatible con C entre Lisp y su código C++. Lo harías usando extern "C" como esto:

extern "C" Foo *new_Foo(int x)
{
    return new Foo(x);
}

Esto hace que el new_Foo() siga la convención de llamadas de C para que pueda llamarla desde fuentes externas.

  • La segunda opción sería buena, pero el código C++ (que no es mío) utiliza la sobrecarga de operadores. Supongo que no puedo declarar un operador “C” externo +… La primera opción depende del compilador que se utilice, ¡pero es una buena sugerencia!

    – Jay

    5 de septiembre de 2009 a las 5:33

  • Todavía puede llamar a operadores sobrecargados desde el exterior, solo tiene que ser un poco creativo con su API. Por ejemplo: extern "C" void add_Foo(Foo *result, const Foo *foo1, const Foo *foo2) { *result = *foo1 + *foo2; }

    – Greg Hewgill

    5 de septiembre de 2009 a las 5:54

  • No todas las funciones de C++ “se reducen a C normal”. Las funciones miembro suelen utilizar una convención de llamada diferente además de la manipulación de nombres. (Creo en MSVC en x86, el this el parámetro se pasa en un registro, mientras que todos los parámetros en una función C se pasan en la pila)

    – jalf

    5 de septiembre de 2009 a las 12:59

  • Correcto, pero aún puedo escribir un contenedor con funciones similares a C y hacer lo que expliqué en mi propia respuesta …

    – Jay

    5 sep 2009 a las 15:50

La principal diferencia al llamar a funciones de C++ en lugar de funciones de C, aparte de la manipulación de nombres, son las características ‘ocultas’ como este punteros que se pasan implícitamente a las funciones miembro. La capa de tiempo de ejecución de C no sabe nada acerca de estas conversiones de tipo implícitas y otras funciones divertidas de C++, por lo que si tiene la intención de llamar a C++ a través de una interfaz de C, es posible que tenga que falsificar estas funciones si es necesario.

Suponiendo que puede mantener al menos un vacío * en el objeto que pretende llamar y los datos que requiere, puede degradar la siguiente llamada de C++

matrix->multiply(avector);

a una llamada C si crea una función contenedora C:

extern "C"
void matrix_multiply(void *cpp_matrix, void *cpp_vector) {
  reinterpret_cast<matrix_type *>(cpp_matrix)->multiply(reinterpret_cast<vector_type *>(cpp_vector);
}

Obviamente, la función matrix_multiply se ubicaría en el código fuente de C++ y se compilaría como tal, pero expone una interfaz C al mundo exterior. Siempre que pueda interactuar con los punteros opacos, está de acuerdo con las correcciones de traducción anteriores.

Es cierto que esta no es necesariamente la solución más elegante para un problema como este, pero la he usado en el pasado en situaciones como la tuya.

La otra opción sería hacer las llamadas de C++ directamente tratándolas como llamadas de C con parámetros adicionales y proporcionando toda la información requerida, pero eso lo lleva al ámbito del código específico del compilador muy rápidamente. Básicamente, todavía estaría sosteniendo los punteros opacos a los objetos de C++, pero tendría que averiguar el nombre alterado de la función a la que desea llamar. Una vez que tenga el nombre de la función, deberá proporcionar el puntero this (que está implícito en C++ y semiimplícito en el ejemplo anterior) y los parámetros correctos y luego llamar a la función. Se puede hacer, pero como se mencionó, lo coloca profundamente en el ámbito del compilador e incluso en el comportamiento específico de la versión del compilador.

¡Oh espera!

Parece que hay un truco ¡Puedo usar!

Escribo un envoltorio en C++, declarando funciones de envoltorio extern “C”:

#include "lib.h"

extern "C" int lib_operate (int i, double *x) {
...
}

El archivo de encabezado lib.h, al que se puede llamar tanto desde C como desde C++, es:

#if __cplusplus
extern "C" {
#endif

int lib_operate (int i, double *x);

#if __cplusplus
}
#endif

Luego compila con:

g++ -c lib.cpp
gcc -c prog.c
gcc lib.o prog.o -lstdc++ -o prog

¡Parece funcionar para un ejemplo de juguete! 🙂

Entonces, en Common Lisp llamaría al envoltorio después de cargar libstdc++.

De todos modos, gracias por sus respuestas!

unnamed file 106
I.Omar

Actualización 2021:CL-CXX-JIT que maneja la mayor parte del trabajo en el lado ceceo.

Ejemplo:

(ql:quickload :cxx-jit)
(in-package cxx-jit)
(from '("<cmath>") 'import '("static_cast<double(*)(double)>(std::sin)" . "cpp-sin"))
(cpp-sin 0d0)
(from nil 'import "struct C{ auto hi(){return \"Hello, World\\n\";} auto bye(){return \"Bye\";} };" '("&C::bye" . "bye") '("&C::hi" . "hi") '("[](){static C x; return x;}" . "cc"))
(cc)
(hi *)
(bye **)

Puedes usar cl-cxx que es como escribir pybind11 para python.

ejemplo en c++ ‘std >= c++14’, compilado como biblioteca compartida:

#include <string>

#include "clcxx/clcxx.hpp"

class xx {
 public:
  xx(int xx, int yy) : y(yy), x(xx) {}
  std::string greet() { return "Hello, World"; }

  int y;
  int x;
};

std::string greet() { return "Hello, World"; }
int Int(int x) { return x + 100; }
float Float(float y) { return y + 100.34; }
auto gr(std::complex<float> x) { return x; }
std::string hi(char* s) { return std::string("hi, " + std::string(s)); }

void ref_class(xx& x) { x.y = 1000000; }

CLCXX_PACKAGE TEST(clcxx::Package& pack) {
  pack.defun("hi", &hi);
  pack.defun("test-int", &Int);
  pack.defun("greet", &greet);
  pack.defun("test-float", &Float);
  pack.defun("test-complex", &gr);
  pack.defun("ref-class", &ref_class);
  pack.defclass<xx, false>("xx")
      .member("y", &xx::y)
      .defmethod("greet-from-class", &xx::greet)
      .constructor<int, int>();
}

uso en lisp:

(cffi:use-foreign-library my-lib)
(cxx:init)
(cxx:add-package "TEST" "TEST")
(test:greet)
(setf my-class (test:creat-xx2 10 20))
(test:y.get myclass)

Eso se encargaría de todas las conversiones, “C” externa, … por usted.

Dependiendo de su ABI de C++, su contenedor (lib_operate arriba) podría necesitar manejar de alguna manera cualquier excepción de C ++ que pueda ocurrir. Si su ABI maneja excepciones de unidades de tabla, las excepciones no controladas simplemente bloquearán el proceso (Lisp). Si, en cambio, realiza un registro dinámico, es posible que ni siquiera note que algo salió mal. De cualquier manera, es malo.

O bien, si tiene la garantía de no generar resultados para el código envuelto, puede ignorar todo esto.

  • Estoy de acuerdo. ¿Pero que se puede hacer al respecto?

    – Michael Zorro

    11 de enero de 2017 a las 0:24

  • Estoy de acuerdo. ¿Pero que se puede hacer al respecto?

    – Michael Zorro

    11 de enero de 2017 a las 0:24

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad