Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: C++ bindings #187

Closed
avsm opened this issue Oct 5, 2014 · 2 comments
Closed

docs: C++ bindings #187

avsm opened this issue Oct 5, 2014 · 2 comments

Comments

@avsm
Copy link
Contributor

avsm commented Oct 5, 2014

@yallop sent this mail to mirageos-devel, and it's useful enough to put somewhere in the documentation or tutorial. Filing it here for now.

I find it easier to think about this kind of thing if I separate the code that binds the C++ from the code that gives it a more idiomatic OCaml interface. You then end up with four "layers" with clearly-separated responsibilities:

  1. the C++ library itself (object-oriented C++)
  2. the 'extern "C"' interface (function-oriented C++)
  3. the ctypes bindings (function-oriented OCaml)
  4. the OCaml interface (object-oriented OCaml)

Here's an example to show what I mean. First, a simple C++ library with a thin 'extern "C"' interface that takes "this" arguments and forwards calls to member functions:

  $ cat shapes.cc
  #include <cmath>

  struct Shape {
    virtual double area() = 0;
    virtual ~Shape() = 0;
  };

  Shape::~Shape() { }

  struct Square : public Shape {
    Square(double s) : side(s) { }
    double area() { return side * side; }
    ~Square() { }

  private:
    double side;
  };

  struct Circle : public Shape {
    Circle(double r) : radius(r) { }
    double area() { return M_PI * radius * radius; }
    ~Circle() { }

  private:
   double radius;
  };

  extern "C" {
    Square *create_Square(double side) { return new Square(side); }
    void destroy_Square(Square *s) { delete s; }
    double Square_area(Square* s) { return s->area(); }

    Circle *create_Circle(double radius) { return new Circle(radius); }
    void destroy_Circle(Circle *c) { delete c; }
    double Circle_area(Circle* c) { return c->area(); }
  }
  $ g++ -shared -fPIC -ansi -pedantic -W -Wall shapes.cc -o libshapes.so

You could also have a header file declaring the extern functions, but let's leave that out for the sake of simplicity, since ctypes.foreign doesn't use it.

The next layer uses ctypes to bind the extern "C" functions. This is a fairly straightforward matter of translating the C declaration syntax into the corresponding calls to functions in the ctypes interface.

  $ cat shape_bindings.ml
  open Ctypes
  open Foreign

  type square
  type circle
  let square : square structure typ = structure "Square"
  let circle : circle structure typ = structure "Circle"

  let create_Square =
    foreign "create_Square" (double @-> returning (ptr square))
  let destroy_Square =
    foreign "destroy_Square" (ptr square @-> returning void)
  let square_area =
    foreign "Square_area" (ptr square @-> returning double)

  let create_Circle =
    foreign "create_Circle" (double @-> returning (ptr circle))
  let destroy_Circle =
    foreign "destroy_Circle" (ptr circle @-> returning void)
  let circle_area =
    foreign "Circle_area" (ptr circle @-> returning double);

Finally, we can define OCaml classes that forward method calls to the various bound functions. There are various choices, such as how do deal with destructors, that probably need to be made on a per-binding basis. For this example, I've defined an initializer in each class that registers the destructor of each object with the garbage collector so that the C++ object is destroyed when the corresponding OCaml object becomes unreachable. I've also made the 'this' member, which is implicit in C++, into an explicit instance variable in the OCaml classes.

  $ cat shapes.ml
  class virtual shape =
  object
    method virtual area : float
  end

  class circle ~radius =
  object
    inherit shape
    val this = Shape_bindings.create_Circle radius
    method area = Shape_bindings.circle_area this
    initializer Gc.finalise Shape_bindings.destroy_Circle this
  end

  class square ~side =
  object
    inherit shape
    val this = Shape_bindings.create_Square side
    method area = Shape_bindings.square_area this
    initializer Gc.finalise Shape_bindings.destroy_Square this
  end
  $ ocamlfind ocamlc -linkpkg -custom -package ctypes.foreign \
       shape_bindings.ml shapes.ml -cclib -L. -cclib -lshapes

To see it all working we can load the library, instantiate the objects, and call methods:

  $ ocamlfind ocamlmktop -package ctypes.foreign shapes.cma -o shapes.top
  $ ./shapes.top  -short-paths
          OCaml version 4.01.0

  # open Shapes;;
  # let c = new circle ~radius:5.0;;
  val c : circle = <obj>
  # let s = new square ~side:10.0;;
  val s : square = <obj>
  # c#area;;
  - : float = 78.5398163397448315
  # s#area;;
  - : float = 100.
@abhi18av
Copy link

Hello World :)

Is there any activity towards being able to call C++ using OCaml and ctypes library?

@yallop
Copy link
Owner

yallop commented Nov 30, 2018

Some parts of C++ are supported. Using ctypes stub generation it's possible to call overloaded functions, function templates, etc.: anything that can be called using C call syntax will work, and in a type-safe way.

Some parts of C++ are not supported. At the moment there's no support for calling member functions or overloaded operators, and that's not likely to change in the short term.

Repository owner locked and limited conversation to collaborators Apr 29, 2023
@yallop yallop converted this issue into discussion #743 Apr 29, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests

3 participants