[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [MirageOS-devel] ctypes and c++



On 10/05/2014, Richard Mortier <Richard.Mortier@xxxxxxxxxxxxxxxx> wrote:
> On 9 May 2014, at 17:42, Jeremy Yallop <jeremy.yallop@xxxxxxxxxxxx> wrote:
>> On 9 May 2014 17:05, Steven Luland <psxsl6@xxxxxxxxxxxxxxxx> wrote:
>>> Iâm looking to use ctypes to make the openzwave library compatible with
>>> OCaml. Would ctypes work on a c++ library such as this?
>>
>> For the moment, ctypes needs a C-compatible interface.  It's possible
>> to bind C++ libraries that expose 'extern "C"' declarations, but not
>> possible to bind arbitrary C++ code.
>
> apologies, it's been a while since i did any C++ (thankfully)-- does this
> need more support than just name mangling? (which could be done manually in
> a pinch couldn't it)?

Indeed, if you're willing to do name mangling manually it should be
possible to get something working.  Here's a simple example: a C++
library with a single trivial class whose instances can be printed
out.  To make it a tiny bit more realistic I've made the output stream
an argument of the print function, so we need to look up symbols in
the standard library as well as the library we're looking to bind.

   $ cat point.cc
   #include <ostream>

   class Point {
     int x, y;
   public:
     Point(int, int);
     ~Point();

    void print(std::ostream&);
   };

   Point::Point(int x_, int y_) : x(x_), y(y_) { }
   Point::~Point() { }
   void Point::print(std::ostream& os) { os << "<" << x << "," << y << ">\n"; }
   $ g++ -shared -ansi -pedantic -W -Wall -fPIC point.cc -o libpoint.so

Before we get started, let's look up the symbols we need.  We'll want
cout from the standard library:

   $ objdump -T /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep cout
   0000000000301280 g    DO .bss        0000000000000110  GLIBCXX_3.4 _ZSt5wcout
   0000000000301700 g    DO .bss        0000000000000110  GLIBCXX_3.4 _ZSt4cout

The second symbol is the one we want; the first is for wide character
output.  We'll also need the constructor and destructor of Point, and
the print member function:

   $ objdump -T libpoint.so  | grep Point
   0000000000000986 g    DF .text       0000000000000023  Base        
_ZN5PointC2Eii
   00000000000009aa g    DF .text       0000000000000024  Base        
_ZN5PointD2Ev
   00000000000009aa g    DF .text       0000000000000024  Base        
_ZN5PointD1Ev
   0000000000000986 g    DF .text       0000000000000023  Base        
_ZN5PointC1Eii
   00000000000009ce g    DF .text       0000000000000070  Base
_ZN5Point5printERSo

I think the C1 and D1 constructor and destructor are the ones we want.
 The C2 and D2 versions are used for object creation and deletion from
subclasses.

We're ready to bind libpoint from ctypes.  Let's start by opening the
Ctypes module and loading the shared objects for libpoint and the
standard library:

   open Ctypes
   let libpoint = Dl.(dlopen ~filename:"./libpoint.so" ~flags:[RTLD_NOW])
   let libstdcpp = Dl.(dlopen ~filename:"libstdc++.so.6" ~flags:[RTLD_NOW])

We'll define an opaque type to represent std::ostream, then bind
std::cout using the symbol we looked up earlier:

   let ostream = structure "ostream"
   let cout = Foreign.foreign_value ~from:libstdcpp "_ZSt4cout" ostream

Let's describe the layout of the Point class.  (This isn't strictly
necessary, since we're not going to access the fields directly.  We
could make do with just knowing the size.)

   type point
   let point = structure "point"
   let x = field point "x" int
   let y = field point "y" int
   let () = seal point

Time to bind the constructor, destructor, and print function.  They
all accept the 'this' pointer as an additional first argument.

   let construct_Point = Foreign.foreign ~from:libpoint "_ZN5PointC1Eii"
     (ptr point @-> int @-> int @-> returning void)

   let destroy_Point = Foreign.foreign ~from:libpoint "_ZN5PointD1Ev"
     (ptr point @-> returning point)

   let print_Point = Foreign.foreign ~from:libpoint "_ZN5Point5printERSo"
     (ptr point @-> ptr ostream @-> returning void)

Before we can call the constructor we need to allocate memory for the
object.  The new_Point function, defined next, allocates memory for a
Point, attaches a finaliser which calls the destructor when the memory
is garbage collected, and calls the constructor to initialize the
object:

    let new_Point ~x ~y =
      let finalise p = ignore (destroy_Point (addr p)) in
      let p = make ~finalise point in
      begin
        construct_Point (addr p) x y;
        p
      end

Finally, we can use our new bindings.  Let's allocate a Point and call
its print() method with std::cout:

    let pt = new_Point ~x:10 ~y:20
    let () = print_Point (addr pt) cout

It works on my machine, at least!  Here's the output:

    <10,20>

Of course, this is a fairly trivial example.  Things become more
interesting (or in some cases impossible with current ctypes) if the
C++ library you want to bind uses the many exciting features of C++:
various forms of inheritance, templates, and so on.

>> Recent developments (namely
>> support for generating stubs) make binding to C++ feasible, so it's
>> possible that there'll be support for C++ at some point.

(What I mean by this is that with stub generation the C++ compiler
will take care of name mangling, since ctypes is generating source
rather than resolving symbols in binary objects.  There are various
missing pieces making that route unviable for the moment, though.)

> cool-- i guess this isn't terribly high priority though? or is it something
> that's actually planned?

For now it's not so much planned as noted as a nice-to-have
(https://github.com/ocamllabs/ocaml-ctypes/issues/32).

Jeremy.

_______________________________________________
MirageOS-devel mailing list
MirageOS-devel@xxxxxxxxxxxxxxxxxxxx
http://lists.xenproject.org/cgi-bin/mailman/listinfo/mirageos-devel

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.