Вызов С++ (не C) из Common Lisp?
Мне интересно, есть ли способ вызвать код С++ из Common Lisp (желательно портативно, а если нет, желательно в SBCL, а если нет, то Clozure, CLisp или ECL).
С++ будет вызываться внутри циклов для численного вычисления, поэтому было бы неплохо, если бы звонки были быстрыми.
CFFI, похоже, не поддерживает это:
"Эта концепция может быть обобщена на Другие языки; в момент письменной форме, только поддержка CFFI C довольно полная, но поддержка С++ над которым работали".
(глава 4 руководства)
В руководстве SBCL также не упоминается С++; это на самом деле говорит
В этой главе описываются функции SBCL интерфейс к программам и библиотекам C (и, поскольку C-интерфейсы являются своего рода lingua franca мира Unix, другим программам и библиотекам в вообще.)
В С++-коде используется OO и перегрузка операторов, поэтому его действительно нужно скомпилировать с помощью g++.
И насколько я знаю, у меня может быть функция С++ main() и писать оболочки для функций C, но не наоборот - это правда?
В любом случае... Есть ли способ сделать это?
Спасибо!
Ответы
Ответ 1
О, подождите!
Кажется, что существует трюк, который я могу использовать!
Я пишу обертку в С++, объявляя функции-оболочки extern "C":
#include "lib.h"
extern "C" int lib_operate (int i, double *x) {
...
}
Заголовочный файл lib.h, который может быть вызван как из C, так и из С++, это:
#if __cplusplus
extern "C" {
#endif
int lib_operate (int i, double *x);
#if __cplusplus
}
#endif
Затем скомпилируйте с помощью:
g++ -c lib.cpp
gcc -c prog.c
gcc lib.o prog.o -lstdc++ -o prog
Кажется, это работает на примере игрушек!: -)
Итак, в Common Lisp я бы назвал оболочку после загрузки libstdС++.
В любом случае, спасибо за ваши ответы!
Ответ 2
После компиляции большинство функций С++ фактически сводятся к регулярным вызовам функций C. Из-за перегрузки функций и других функций компиляторы С++ используют name mangling, чтобы различать похожие функции. Учитывая полезность дампа объекта и достаточные знания о вашем компиляторе на С++, вы можете вызвать код С++ непосредственно из внешнего мира.
Сказав это, вам может быть проще написать C-совместимый уровень между Lisp и вашим кодом на С++. Вы сделали бы это с помощью extern "C"
следующим образом:
extern "C" Foo *new_Foo(int x)
{
return new Foo(x);
}
Это означает, что функция new_Foo()
выполняет соглашение о вызове C, чтобы вы могли вызывать ее из внешних источников.
Ответ 3
Основным отличием при вызове функций С++ вместо функций C, помимо имени, являются "скрытые" функции, такие как this указатели, которые неявно передаются в функции-члены. Среда выполнения C ничего не знает об этих, неявных преобразованиях типов и других интересных функциях С++, поэтому, если вы намереваетесь вызывать С++ через интерфейс C, вам, возможно, придется подделать эти функции.
Предполагая, что вы можете удерживать хотя бы void * для объекта, который собираетесь называть, и требуемых данных, вы можете деградировать следующий вызов С++
matrix->multiply(avector);
для вызова C, если вы создаете функцию обертки 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);
}
Очевидно, что функция matrix_multiply будет находиться в исходном коде на С++ и скомпилирована как таковая, но она открывает интерфейс C во внешний мир. Пока вы можете взаимодействовать с непрозрачными указателями, вы в порядке с прокладками перевода выше.
По общему признанию, это не обязательно самое элегантное решение для такой проблемы, но я использовал его в прошлом в таких ситуациях, как ваш.
Другим вариантом было бы сделать вызовы С++ напрямую, рассматривая их как вызовы C с дополнительными параметрами и предоставляя всю необходимую информацию самостоятельно, но это очень быстро переводит вас в область кода, специфичного для компилятора. В принципе, вы все равно будете удерживать непрозрачные указатели на объекты С++, но вам придется выработать искаженное имя функции, которую вы хотите вызвать. После того, как у вас есть это имя функции, вам нужно будет указать этот указатель (который неявный в С++ и полу-имплицитный в приведенном выше примере) и правильные параметры, а затем вызвать функцию. Это может быть сделано, но, как уже упоминалось, вы глубоко вникаете в сферу компилятора и даже специфическое поведение для компилятора.
Ответ 4
В зависимости от вашего С++ ABI вашей оболочке (lib_operate
выше) может потребоваться каким-то образом обработать любые исключения С++, которые могут возникнуть. Если ваш ABI выполняет обработку исключений табличного диска, необработанные исключения просто вызовут процесс (Lisp). Если вместо этого происходит динамическая регистрация, вы даже не заметите, что что-то пошло не так. В любом случае, это плохо.
Или, если у вас есть гарантия отсутствия броска для обернутого кода, вы можете игнорировать все это.
Ответ 5
Вы можете использовать cl-cxx, как писать pybind11 для python.
пример в c++ 'std> = c++ 14', скомпилированный как разделяемая библиотека:
#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>();
}
использование в 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)
Это позаботится обо всех конверсиях, extern "C",... для вас.