从Common Lisp调用C ++(而不是C)?

时间:2009-09-05 05:16:47

标签: c++ c common-lisp

我想知道是否有某种方法可以从Common Lisp调用C ++代码(最好是可移植的,如果没有,最好是在SBCL中,如果没有,那么,那么Clozure,CLisp或ECL)。

C ++将在内部循环中调用以进行数值计算,因此如果调用速度很快就会很好。

CFFI似乎不支持这个:

  

“这个概念可以推广到   其他语言;在那个时间   写作,只有CFFI的C支持   相当完整,但C ++支持   正在努力。“

(手册第4章)

SBCL的手册也没有提到C ++;它实际上说

  

本章介绍SBCL   C程序和库的接口   (并且,因为C接口是一种   Unix世界的通用语言,以及其他程序和库   一般。)

C ++代码使用OO和运算符重载,所以它确实需要用g ++编译。

据我所知,我可以使用C ++ main()函数并为C函数编写包装器,但不是相反 - 这是真的吗?

无论如何......有没有办法做到这一点?

谢谢!

5 个答案:

答案 0 :(得分:17)

编译之后,大多数C ++函数实际上归结为常规C函数调用。由于函数重载和其他功能,C ++编译器使用name mangling来区分类似命名的函数。给定一个对象转储实用程序和关于C ++编译器的足够知识,您可以直接从外部世界调用C ++代码。

尽管如此,您可能会发现在Lisp和C ++代码之间编写C兼容层更容易。您可以使用extern "C"这样做:

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

这使得new_Foo()函数遵循C调用约定,以便您可以从外部源调用它。

答案 1 :(得分:13)

除了名称修改之外,调用C ++函数而不是C函数的主要区别在于隐藏传递给成员函数的 this 指针等“隐藏”功能。 C运行时层对这些,隐式类型转换和其他有趣的C ++特性一无所知,因此如果您打算通过C接口调用C ++,则可能必须在必要时伪造这些功能。

假设您至少可以为要调用的对象及其所需的数据保留void *,则可以降级以下C ++调用

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接口。只要你可以与不透明指针进行交互,你就可以使用上面的翻译垫片了。

不可否认,对于像这样的问题,这不一定是最优雅的解决方案,但我过去在像你这样的情况下使用它。

另一种选择是直接通过将C ++调用视为带有附加参数的C调用并自己提供所有必需信息来直接进行C ++调用,但这确实可以让您快速进入特定于编译器的代码领域。基本上,您仍然会持有C ++对象的不透明指针,但您必须计算出要调用的函数的错位名称。一旦你有了这个函数名,就必须提供this指针(在C ++中是隐式的,在上面的例子中是隐式的)和正确的参数然后调用函数。它可以完成,但如上所述,让您深入了解编译器领域甚至编译器版本特定的行为。

答案 2 :(得分:6)

哦,等等!

似乎我可以使用trick

我在C ++中编写了一个包装器,声明包装函数extern“C”:

#include "lib.h"

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

可以从C和C ++调用的头文件lib.h是:

#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中,我在加载libstdc ++后调用包装器。

无论如何,谢谢你的答案!

答案 3 :(得分:3)

根据您的C ++ ABI,您的包装器(上面的lib_operate)可能需要以某种方式处理可能发生的任何C ++异常。如果您的ABI执行表驱动器异常处理,则未处理的异常将使(Lisp)进程崩溃。如果它改为进行动态注册,您甚至可能不会注意到任何问题。无论哪种方式,都很糟糕。

或者,如果你已经获得了包装代码的无抛出保证,你可以忽略这一切。

答案 4 :(得分:1)

您可以使用cl-cxx,就像为python编写pybind11一样。

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)

这将为您完成所有转换,例如外部“ C”……。