Robbie Harwood: Ffi To C++ Considered Difficult

As was perhaps inevitable, I’ve combined two of my current interests. So
here’s a Rust program that uses the current version (0.1.2/0.1.3) of the
QT5 bindings to make a simple interface:

extern crate cpp_utils;
extern crate qt_core;
extern crate qt_widgets;

use cpp_utils::{AsBox, StaticCast};

use qt_core::connections::Signal;
use qt_core::slots::SlotNoArgs;
use qt_core::string::String;

use qt_widgets::application::Application;
use qt_widgets::push_button::PushButton;
use qt_widgets::qvb_ox_layout::QvbOxLayout as VBoxLayout;
use qt_widgets::widget::Widget;

fn glom<T>(ptr: *mut T) -> &'static mut T {
    unsafe { ptr.as_mut() }.expect("null pointer in glom")
}

fn main() {
    Application::create_and_exit(|_| {
        let mut widget = Widget::new(AsBox);
        let mut layout = VBoxLayout::new((widget.as_mut_ptr(), AsBox));
        let mut button = PushButton::new((&String::from("Quit"), AsBox));

        button.set_enabled(true);

        layout.add_widget(button.static_cast_mut() as *mut _);

        widget.resize((250, 150));
        widget.set_window_title(&String::from("QEMU"));
        widget.show();

        let b_ref = button.into_raw();

        let on_click = SlotNoArgs::new(|| {
            glom(b_ref).set_enabled(false) };
            Application::quit()
        });

        glom(b_ref).signals().clicked().connect(&on_click);

        Application::exec()
    });
}

So there’s a lot to unpack here. I should probably start by saying that
Rust’s C Foreign-Function Interface (FFI) is “fine” – that is, it works, and it’s
fairly straightforward to use, but it’s not particularly brilliant or even
novel. Next, QT is not a C library; rather, it is many C++ libraries.
Which is to say that the Rust FFI is not designed for this. Actually, now
might be a good time to break out the C++ version of this for comparison:

#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>

class VerticalBox : public QWidget {
public:
    VerticalBox(QApplication *app, QWidget *parent = 0);

private slots:
    void OnClick();

private:
    QPushButton *quit;
    QApplication *app;
};

VerticalBox::VerticalBox(QApplication *app, QWidget *parent): QWidget(parent) {
    QVBoxLayout *vbox = new QVBoxLayout(this);

    QPushButton *quit = new QPushButton("quit");
    quit->setEnabled(true);
    vbox->addWidget(quit);

    connect(quit, &QPushButton::clicked, this, &VerticalBox::OnClick);

    setLayout(vbox);

    this->quit = quit;
    this->app = app;
}

void VerticalBox::OnClick() {
    quit->setEnabled(false);
    app->exit();
}

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    VerticalBox window(&app);
    window.resize(250, 150);
    window.setWindowTitle("QEMU");
    window.show();

    return app.exec();
}

Now that I’ve thoroughly upset the syntax highlighting of both my blog and my
editor, let me call out a few things. First, this is a semi-contrived
example: a real application would probably not care about disabling the quit
button before quitting the application itself. Second, the boilerplate for
both users of QT is about the same: there’s an entire other file for QT
dependency management in C++, but the Rust code has a longer prelude. (For
more on building QT in C++, see
this post in
the unlikely event that you stumble across this post and want minutiae from
someone who doesn’t know the project history).

As for the build process itself, the C++ is substantially faster but much more
fiddly. In a C++ context, I’m making use of my distro’s header files and
shared objects, so it only takes a few seconds. In the Rust context, though,
there isn’t a strong notion of shared objects yet: I have to download each
crate and build it from source before it’s (statically) linked into my
binary. I understand there is work being done on this, but because these
crates only have to be built once, the hour (!!!) overhead on the first build
here isn’t a huge issue (since it never happens again). It also “just
worked™” – I didn’t have to do anything special beyond the normal rust process.

Part of the reason the Rust build is slower than it might otherwise be is that
it dynamically generates the bindings. The rust-qt folk have a tool for
automatically generating bindings to C++ libraries, which is nice because QT
has an enormous number of functions. The drawback is that there are several
warts over the calling code. For instance, QT has its own type for Strings,
so there’s a lot of &String::from; Rust’s trait system can be used to fix
this, but it must be done by hand. Ditto the awkwardness of taking a tuple as
an argument.

There are also a couple of (what I imagine must be) bugs in its naming; the
one I’m highlighting is the transition from the C++ type QVBoxLayout to the
Rust type qvb_ox_layout::QvbOxLayout. Which leads nicely into the point I
want to highlight, which is how poorly the inheritance is getting handled
here. In the C++ code, my VerticalBox object inherits from the QWidget
parent class, and so I can define my own, custom, widgets easily; in the Rust,
though, there’s no such convenience, and my custom widgets have to hold their
own widget object inside them.

This leads directly to the two uses of unsafe in this code (which happen to
both be safe, thankfully) that I’ve highlighted in the glom function.
Within the on_click callback, we’d really like to do, as we do in the C++
case, manipulate part of our state (button). However, if we do so, then
button is moved to within the closure, and I can’t wire it to connect after.
And I can’t reverse the order that these things are declared in, because then
the callback won’t be initialized at time of reistration! (As for the “slots”
abstraction itself, I will not defend the way QT implements this in C++, so
it’s not at all surprising that it’s ugly in Rust as well.)

For a 0.1-series release, it’s impressive that this works at all. Going
forward, a more powerful FFI is something Rust really needs to work on, even
if it requires some knowledge of how C++ works. I’m hopeful that this might
actually be a priority for Mozilla because the *Monkey JavaScript JITs have
only a C++ interface; however, right now I believe they’re using a (different)
binding generator tool for Servo. I think it’s sufficiently clear that a
better story for handling inheritance is needed, at the very least.

Attribution

The code in this post is adapted from both
the Rust-QT example
and Jan Bodnar’s QT5 tutorial. I promise that
this is just code for me learning the tools and that I am not
going dark.


Source From: fedoraplanet.org.
Original article title: Robbie Harwood: Ffi To C++ Considered Difficult.
This full article can be read at: Robbie Harwood: Ffi To C++ Considered Difficult.

Advertisement
directory-software-online-bussiness-script


Random Article You May Like

Leave a Reply

Your email address will not be published. Required fields are marked *

*
*