Proxy2Cpp

Using the tool

The Proxy2cpp tool is invoked as a subcommand of the Polyglot program. It must be provided with at least a proxy file and an output path:

$> polyglot proxy2cpp <proxy-file> -o<output-path> {<switch>}
  • --help

    Display Help.

  • -q

    be quiet.

  • -v

    verbose output.

  • -o|--output=<outputPath>

    path to the directory in which the C++ interface should be generated. Directories along the path are created if they do not exist yet.

  • --header-file=<file>

    Prepend the content of the given file to all sources generated by Polyglot.

Modules

Modules in the JSON Proxy are translated to namespaces and are each in a separate file. Nested modules become nested namespaces.

Ada declarations

Generated C++ headers

-- example.ads
package Example is
   procedure Foo;
end Example;
// example.h
namespace example {

void Foo();

} // namespace example
-- example-child.ads
package Example.Child is
   procedure Bar;
end Example.Child;
// example-child.h
namespace example {
namespace child {

void Bar();

} // namespace child
} // namespace example

Functions

Function placement

By default, functions will be placed in their parent namespace. Functions with roles become member functions to the type of the role.

proxy.json

(proxy2cpp →) C++ header

{
  "kind": "function",
  "name": {"names": ["n1", "f"]}
},
{
  "kind": "class",
  "name": {"names": ["n1", "c"]}
},
{
  "kind": "function",
  "name": {"names": ["n1", "fm"]},
  "role": {
    "kind": "method",
    "type": {"kind": "typename", "name": {"names": ["n1", "c"]}}
  }
}
namespace n1 {

void f();

class C {
  void fm();
};

} // namespace n1

Operators

Functions from a proxy that have a name matching a placeholder name will be translated to an operator overload.

Ada declaration

(ada2proxy →) proxy.json

(proxy2cpp →) C++ header

type T is private

function "+"(I: Long_Integer; V: T) return Integer;
{
  "kind": "function",
  "name": {"names": ["operator_plus"]}
}
int operator+(long i, T v);

Class types

Final types

Classes marked final in the proxy should not be inherited. They lack the support for dynamic dispatch in the bound library.

Currently, nothing prevents inheriting from such types. However, in the future, we will add a mechanism to prevent inheritance, and as such incorrect behaviours when that would happen.

Function members

As mentioned previously, functions with a role will be function members of the class. The first parameter has to be of the same type as the role’s. The latter becomes the implicit this value. If the first argument type in the proxy is constant, then the the function member will be marked as const.

Inheritance

Types with a shadow constructor can be inherited. It is mandatory to use the shadow constructor in order to enable overriding non static function members from bound types.

The shadow constructor can be distinguished by its last argument: when constructing the shadow object of a type T, its constructor will expect a T*. This argument expects the this value.

package Example is

   type Foo is tagged null record;

end Example;
// example.h

class Foo {
   // Normal constructor: creates an `Example.Foo` ada object.
   Foo();

   // Shadow constructor
   Foo(Foo *);
};

// main.cpp

class Bar : public example::Foo {
    // valid: creates a shadow object
    Bar() : example::Foo(this) {}

    // Invalid: the input library will not create a shadow object,
    // dispatching will not work
    Bar() : example::Foo() {}
}

Note

This is necessary due to C++ initializing the vtable for a given type only at the time that is it being constructed. When executing example::Foo‘s constructor, the vtable of Bar is not yet initialized, meaning that it cannot be aware that the object being constructed actually inherits from example::Foo.

Overriding virtual functions

Virtual functions from bound types can be overriden. This allows inside the bound libraries to dynamically dispatch back to overrides.

Ada declaration

C++ code using and overriding the generated bindings

package Example is
   type Foo is tagged private;

   procedure Hello (F : Foo) is
   begin
      Put_Line ("Hello from Ada");
   end Hello;

   procedure Call_Hello (F : Foo'Class) is
   begin
      F.Hello; -- Dispatching call
   end Call_Hello;
end Example;
// example.h

namespace example {

class Foo {
public:
    Foo();

    virtual void hello() const;
};

void call_hello(const Foo&);

} // namespace example

// main.cpp

class Bar : example::Foo {
    Bar() : example::Foo(this) {}

    void hello() const override {
        std::cout << "Hello from C++\n";
    }
};

int main() {
    example::Foo foo;
    Bar bar;
    example::call_hello(foo); // Hello from Ada;
    example::call_hello(bar); // Hello from C++;
}

Uncopyable classes (copy elision (C++17))

C++ only supports guaranteed copy elision since C++17. However, there may be times where input languages support returning uncopyable values through similar concepts to copy elision. In order to avoid errors with invalid code generation, when these values are returned before with standards prior to C++17, they will be returned though a pointer. These values, since cloned before by the proxy before returning, will be owned by the user.

Polyglot pointers

Pointer types are represented using a ref-counted smart pointer (polyglot_ptr) in order to avoid manual memory management. The smart pointer holds information on the ownership of the underlying pointer.

Creating a pointer from an existing object sets the owner to STATIC. It implies that the memory should not be freed when the pointer goes out of scope, and the owner cannot be changed.

When passing a pointer to a function, the function expects a minimum level of ownership. This is to make sure that after calling a function that may escape the pointer, the latter should not be freed inadvertently which could leave a dangling pointer.

package body Example is

   type Rec is record
      I: Integer;
   end record;

   type Rec_A is access all Rec;

   Acc : Rec_A := null;

   procedure Set (New_Acc: Rec_A) is
   begin
      Acc := New_Acc;
   end Add;

   procedure Increment is
   begin
      Acc.all := Acc.all + 1;
   end Add;

end Example;
// example.h

namespace example {

   class Rec {
      Rec(int);

      int &get_i();
      void set_i(int i);
   };

   void set(polyglot::polyglot_ptr<Rec> new_acc);
   void inc();

} // namespace example

// main.cpp

int main() {
    {
        polyglot::polyglot_ptr<example::Rec> ptr(new Rec(4));
        // `Example.Set` only accepts the default minimum ownership: LIBRARY
        ptr.set_owner(polyglot::memory_owner::LIBRARY);
        example::set(ptr);
        example::inc();
        // The managed C++ object of `ptr` is freed, but its underlying Ada
        // data is not because it is owned by the library.
    }
    // We can keep calling `Example.Inc`
    example::inc();
    example::inc();
}

Note

For a concrete example of using pointers, checkout the “ex2” example

References and view types

Returning non-scalar references is not possible, so instead, view types are generated. Similarly to the std::vector<bool>::reference specialization, their goal is to provide a value that can be used as a reference of their underlying type. As a comparison, a pointer with a static ownership would result in a similar result, but the outcome would not reflect the behavior of the interface as well.

Destruction of such view objects do not cause the underlying data to be freed.

References to Scalar type are still translated to usual C++ references.

package Example

   type P is private;

   type Rec is record
       Comp: P;
   end record;

   procedure Foo (Val: in out P);

end Example;
// example.h

namespace example {
   class P {};

   class Rec {
       P::view get_comp();
       void set_comp(const P&);
   };

   void foo(P &);
} // namespace example

// main.cpp

int main() {
    Rec r;
    P::view ref = r.get_comp();
    example::foo(ref); // View types can automatically convert to their reference counterpart
    example::foo(r.get_comp());

    P &ref_invalid = r.get_comp(); // Invalid: the reference outlives the view object.
    example::foo(ref_invalid);
    // Undefined behaviour: the view object is no longer valid. The value of the reference is unknown.
}

Exceptions

Exceptions are bound as classes that inherit the std::exception type. Polyglot guarantees the consistency of C++ exception types at run time.

Ada declaration

C++ code using the generated bindings

package Example is

   Exc : exception;

   procedure Raise_Exc is
   begin
      raise Exc with "message";
   end Raise_Exc;

end Example;
// example.h

namespace example {

class Exc : public polyglot::ada::exceptions::AdaException {
public:
    Exc();
    Exc(const polyglot::ada::strings::polyglot_string &);
};

void raise_exc();

} // namespace example

// main.cpp

#include "example.h"

int main() {
    try {
        example::raise_exc();
    } catch (const example::Exc& ex) {
        std::cout << ex.what() << '\n';
    }
}

Note

For a concrete example of using bound exceptions, checkout the “ex3” example