ritual
ritual copied to clipboard
Subclassing API
Let's have a C++ class that can be subclassed:
class Example {
public:
Example(int x);
virtual ~Example();
virtual int vfunc2(double y);
protected:
void func1();
};
First, we create a C++ wrapper:
class ExampleSubclass : public Example {
ExampleSubclass(int x);
~ExampleSubclass();
void func1();
int vfunc2(double y);
void set_vfunc2_func(int (*func)(void*, double), void* data);
void set_destructor_func(void (*func)(void*), void* data);
private:
int (*m_func2_func)(void*, double);
void* m_func2_data;
void (*m_destructor_func)(void*);
void* m_destructor_data;
};
ExampleSubclass::ExampleSubclass(int x) : Example(x) {
m_func2_func = 0;
m_func2_data = 0;
m_destructor_func = 0;
m_destructor_data = 0;
}
ExampleSubclass::~ExampleSubclass() {
if (m_destructor_func) {
m_destructor_func(m_destructor_data);
}
}
void ExampleSubclass::func1() {
return Example::func1();
}
void ExampleSubclass::set_vfunc2_func(int (*func)(void*, double), void* data) {
m_func2_func = func;
m_func2_data = data;
}
void ExampleSubclass::set_destructor_func(void (*func)(void*), void* data) {
m_destructor_func = func;
m_destructor_data = data;
}
int ExampleSubclass::vfunc2(double y) {
if (m_func2_func) {
return m_func2_func(m_func2_data, y);
} else {
return Example::vfunc2(y);
// or abort() if the method was pure virtual
}
}
The wrapper exposes all protected functions of the base class and reimplements all its virtual functions. It allows to add callbacks for each virtual method. If a callback is not set, it calls base class implementation, as if the method was not reimplemented. It also allows to add a destructor callback for cleanup purposes.
Rust API changes the order of operations. First, the user needs to assign virtual functions. Then the object can be created:
let x = ExampleSubclassBuilder::new();
x.bind_func2(|arg| arg as i32);
let object: CppBox<ExampleSubclass> = x.new(constructor_arg);
If not all pure virtual functions were bound, ExampleSubclassBuilder::new
function will panic.
ExampleSubclassBuilder::new
creates a Rust struct that owns all lambdas and ensures that they are not deleted until the object itself is deleted. The struct is kept in memory by calling mem::forget
. The destructor callback is used to delete this struct when it's not needed anymore. If the base class's destructor is virtual, you can pass the ExampleSubclass
object's ownership to the C++ library. When it delets the object, the Rust struct cleanup will still be done. If the base class's destructor is not virtual, the only correct way to release the resources is to let CppBox
delete the object.
ExampleSubclass
Rust type is just a wrapper for ExampleSubclass
C++ class, similar to other class wrappers. It exposes all its public methods, thus providing access to protected methods of the base class. Callback setters are not exposed. ExampleSubclass
will provide means to downcast it to Example
base type, just as any other derived class.
Example of initialization of a Rust struct with a subclass field:
impl MyExampleSubclass {
pub fn new(arg: i32) -> MyExampleSubclass {
let mut obj = MyExampleSubclass {
example: CppBox::null(),
other_data: 42,
};
let subclass = ExampleSubclassBuilder::new();
x.bind_func2(|arg| {
// can capture `self` or its field in some form here
arg as i32
});
obj.example = x.new(arg);
obj
}
}
Could it possible use https://github.com/rust-lang-nursery/rust-bindgen way?
Can you describe what is the way you mean? I found the doc page that says that they don't support cross language inheritance at all.
They support inheritance (its the first stated) and even somehow work with vtable as I've seen in their code.
Meanwhile I have created a QObject wrapper which holds the Rust closure for custom event filtering. Maybe it can be useful. https://github.com/snuk182/qt_core_custom_events
That crate is exactly what I needed... but is outdated and broken. Is there any way to create event filter QObject
s using just the base ritual Qt crates?
Update: I was using the crate wrong and the event filters do work. Still a bit of a hassle, but at least theres a solution.
That crate is exactly what I needed... but is outdated and broken. Is there any way to create event filter
QObject
s using just the base ritual Qt crates?Update: I was using the crate wrong and the event filters do work. Still a bit of a hassle, but at least theres a solution.
Hi Melvyn. Do you mind sharing a snippet of how you used it? Having a hard time making it work
Sure, it's pretty messy (first attempts) but works:
unsafe fn add_event_filters(self: &Rc<Self>) {
fn file_list_event_filter(obj: &mut QObject, event: &mut QEvent) -> bool {
// Function body has to be unsafe rather than function, because the filter requires an FnMut
// Which only safe function pointers are
unsafe {
if event.type_() == q_event::Type::DragEnter {
println!("Trace: received DragEnter event.");
// Transmute is safe because we check the event type
let devent: &mut QDragEnterEvent = transmute(event);
let mime_data = devent.mime_data().text().to_std_string();
// There is a method QMimeData.urls() but dealing with QLists is no fun
let urls: Vec<&str> = mime_data.lines().collect();
// Check if there are any files, excluding paths ending with / (dirs)
if devent.mime_data().has_urls()
&& urls
.iter()
.any(|url| !url.is_empty() && !url.ends_with('/'))
{
println!("Trace: event has valid data, accepting.");
devent.set_drop_action(DropAction::LinkAction);
// If we don't accept the DragEnter event, the DropEvent won't trigger
devent.accept();
return true;
}
} else if event.type_() == q_event::Type::Drop {
println!("Trace: received Drop event.");
// Transmute is safe because we check the event type
let devent: &mut QDropEvent = transmute(event);
let obj_type = CStr::from_ptr(obj.meta_object().class_name());
if obj_type.to_bytes() != b"QListWidget" {
println!(
"Error: received even on wrong QObject ({:?} instead of QWidget)",
obj_type
);
return false;
}
// Transmute is safe because we check the widget type
let list_widget: &mut QListWidget = transmute(obj);
let mime_data = devent.mime_data().text().to_std_string();
let urls: Vec<&str> = mime_data.lines().collect();
for file in urls.iter().filter(|f| !f.ends_with('/')) {
list_widget.add_item_q_string(qs(file.replacen("file://", "", 1)).as_ref());
}
devent.set_drop_action(DropAction::LinkAction);
devent.accept();
return true;
}
return false;
}
}
self.ui.lib_list.install_event_filter(
CustomEventFilter::new(file_list_event_filter).into_raw_ptr(),
);
}
Sure, it's pretty messy (first attempts) but works:
unsafe fn add_event_filters(self: &Rc<Self>) { fn file_list_event_filter(obj: &mut QObject, event: &mut QEvent) -> bool { // Function body has to be unsafe rather than function, because the filter requires an FnMut // Which only safe function pointers are unsafe { if event.type_() == q_event::Type::DragEnter { println!("Trace: received DragEnter event."); // Transmute is safe because we check the event type let devent: &mut QDragEnterEvent = transmute(event); let mime_data = devent.mime_data().text().to_std_string(); // There is a method QMimeData.urls() but dealing with QLists is no fun let urls: Vec<&str> = mime_data.lines().collect(); // Check if there are any files, excluding paths ending with / (dirs) if devent.mime_data().has_urls() && urls .iter() .any(|url| !url.is_empty() && !url.ends_with('/')) { println!("Trace: event has valid data, accepting."); devent.set_drop_action(DropAction::LinkAction); // If we don't accept the DragEnter event, the DropEvent won't trigger devent.accept(); return true; } } else if event.type_() == q_event::Type::Drop { println!("Trace: received Drop event."); // Transmute is safe because we check the event type let devent: &mut QDropEvent = transmute(event); let obj_type = CStr::from_ptr(obj.meta_object().class_name()); if obj_type.to_bytes() != b"QListWidget" { println!( "Error: received even on wrong QObject ({:?} instead of QWidget)", obj_type ); return false; } // Transmute is safe because we check the widget type let list_widget: &mut QListWidget = transmute(obj); let mime_data = devent.mime_data().text().to_std_string(); let urls: Vec<&str> = mime_data.lines().collect(); for file in urls.iter().filter(|f| !f.ends_with('/')) { list_widget.add_item_q_string(qs(file.replacen("file://", "", 1)).as_ref()); } devent.set_drop_action(DropAction::LinkAction); devent.accept(); return true; } return false; } } self.ui.lib_list.install_event_filter( CustomEventFilter::new(Self::file_list_event_filter).into_raw_ptr(), ); }
Thanks Melvyn. Had been experimenting with Paint events, still maneuvering around to make it work. 👨🏽💻