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>}
--helpDisplay Help.
-qbe quiet.
-vverbose 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