I'm currently trying to create a simple Gtkmm program that has a button which spawns a dialog box. I'm currently having issues, however, as the destructor for the AppWindow class causes a segfault closing the dialog box. I check if the unique_ptr
is nullptr
before calling close, but even with that check it will crash if the dialog has already been closed before the main window has. Am I taking the correct approach here? Is the unique_ptr
getting freed before the destructor is called?
main.c
#include "AppWindow.hpp"
#include <cstdio>
#include <cstdlib>
#include <gtkmm/application.h>
int main(int argc, char **argv) {
std::shared_ptr<Gtk::Application> app = Gtk::Application::create("org.dylanweber.test");
return app->make_window_and_run<AppWindow>(argc, argv);
}
AppWindow.hpp
#include <gtkmm/button.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/window.h>
#include <iostream>
#pragma once
class AppWindow : public Gtk::Window {
public:
AppWindow();
virtual ~AppWindow();
private:
Gtk::Button m_button;
std::unique_ptr<Gtk::MessageDialog> dialog;
void on_button_clicked();
};
AppWindow.cpp
#include "AppWindow.hpp"
AppWindow::AppWindow() : m_button("Hello, world!") {
this->m_button.set_margin(10);
this->m_button.signal_clicked().connect(sigc::mem_fun(*this, &AppWindow::on_button_clicked));
this->set_child(this->m_button);
}
AppWindow::~AppWindow() {
if (this->dialog != nullptr) {
this->dialog->close(); // seg fault here
}
}
void AppWindow::on_button_clicked() {
this->dialog = std::make_unique<Gtk::MessageDialog>(
"Button clicked", false, Gtk::MessageType::QUESTION, Gtk::ButtonsType::OK);
this->dialog->set_transient_for(*this);
this->dialog->set_secondary_text("Hello");
this->dialog->set_default_response(Gtk::ResponseType::OK);
this->dialog->show();
}
Suggestion in the comments is right, you should leave your destructor empty and not call dialog->close()
there. To see why, note that this function is a wrapper for GTK C API function gtk_window_close
, defined like this for Gtk::Window class
(from which Gtk::MessageDialog
inherits through Gtk::Dialog
):
// in gtk/gtkmm/window.cc
void Window::close()
{
gtk_window_close(gobj());
}
Here gobj()
returns internal gobject_
pointer (defined in ObjectBase
class) pointing to C API's GTK handle, which current class incapsulates. This pointer is used inside gtk_window_close
this way:
// in gtk/gtkwindow.c
void
gtk_window_close (GtkWindow *window)
{
GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
if (!_gtk_widget_get_realized (GTK_WIDGET (window)))
return;
if (priv->in_emit_close_request)
return;
g_object_ref (window);
if (!gtk_window_emit_close_request (window))
gtk_window_destroy (window);
g_object_unref (window);
}
// in gtk/gtkwidgetprivate.h
static inline gboolean
_gtk_widget_get_realized (GtkWidget *widget)
{
return widget->priv->realized;
}
As VS debugger told when running your example, segfault happens because widget
is 0 in _gtk_widget_get_realized
. It was cleared when dialog was closed, because by default closing (through close
method or close button) a window means destroying it, as you can see e.g. by a call to gtk_window_destroy
inside gtk_window_close
. This destroy using GTK's C API gtk_window_destroy
function through a pretty complex callback mechanism reaches appropriately registered C++ functions relating to memory management. Data breakpoint helped pinpoint exactly where gobject_
was set to 0 - Object::destroy_notify_
(which itself was indirectly called from reference counting code of GTK because dialog's reference count dropped to 0):
// gtk/gtkmm/object.cc
void Object::destroy_notify_()
{
//Overriden.
//GTKMM_LIFECYCLE
#ifdef GLIBMM_DEBUG_REFCOUNTING
g_warning("Gtk::Object::destroy_notify_: this=%p, gobject_=%p\n", (void*)(Glib::ObjectBase*)this, (void*)gobject_);
if(gobject_)
g_warning(" gtypename=%s\n", G_OBJECT_TYPE_NAME(gobject_));
#endif
//Actually this function is called when the GObject is finalized, not when it's
//disposed. Clear the pointer to the GObject, because otherwise it would
//become a dangling pointer, pointing to a non-existent object.
gobject_ = nullptr;
if(!cpp_destruction_in_progress_) //This function might have been called as a side-effect of destroy_() when it called g_object_run_dispose().
{
if (!referenced_) //If it's manage()ed.
{
#ifdef GLIBMM_DEBUG_REFCOUNTING
g_warning("Gtk::Object::destroy_notify_: before delete this.\n");
#endif
delete this; //Free the C++ instance.
}
else //It's not managed, but the C gobject_ just died before the C++ instance..
{
#ifdef GLIBMM_DEBUG_REFCOUNTING
g_warning("Gtk::Object::destroy_notify_: setting gobject_ to 0\n");
#endif
}
}
}
So, after closing a dialog, MessageDialog
's internal handle is cleared to 0 (BTW, MessageDialog destructor is not called during this, which I also checked in a debugger), and since you call a close
function which assumes it isn't, you get memory access problems. Destructor correctly works in case gobject_
was already cleared, so you don't get problems with recreating MessageDialog
in on_button_clicked
after it was closed (where std::unique_ptr
calls destructor for previous object if it's present), as well as in case gobject_
still points to valid object, so you shouldn't have problems with it even if dialog wasn't closed before recreation. For the same reasons, MessageDialog
destructor called in AppWindow
destructor is OK regardless of whether the dialog was closed before the window.
P.S. Note that the gtkmm .cc
files here are from vcpkg buildtrees, generated using gtkmm's custom preprocessing gmmproc
tool during build process from .ccg
source files, so you won't find them in repositories.