简体   繁体   中英

How do I close this Gtk::MessageDialog before it's parent window is destructed?

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 eg 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.

PS 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM