繁体   English   中英

如何在它的父窗口被破坏之前关闭这个 Gtk::MessageDialog?

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

我目前正在尝试创建一个简单的 Gtkmm 程序,该程序具有一个生成对话框的按钮。 但是,我目前遇到问题,因为 AppWindow 类的析构函数导致关闭对话框的段错误。 我在调用close之前检查unique_ptr是否为nullptr ,但即使进行了检查,如果对话框在主窗口之前已经关闭,它也会崩溃。 我在这里采取正确的方法吗? 在调用析构函数之前是否释放了unique_ptr

主程序

#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);
}

应用程序窗口.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();
};

应用程序窗口.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();
}

评论中的建议是正确的,您应该将析构函数留空,而不是在那里调用dialog->close() 要了解原因,请注意此函数是 GTK C API 函数gtk_window_close的包装器,为Gtk::Window class定义如下( Gtk::MessageDialog通过Gtk::Dialog继承):

// in gtk/gtkmm/window.cc
void Window::close()
{
  gtk_window_close(gobj());
}

这里gobj()返回指向 C API 的 GTK 句柄的内部gobject_指针(在ObjectBase类中定义),当前类封装了该句柄。 这个指针在gtk_window_close这种方式使用:

// 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;
}

正如 VS 调试器在运行您的示例时所说的那样,发生段错误是因为_gtk_widget_get_realized中的widget为 0。 它在对话框关闭时被清除,因为默认情况下关闭(通过close方法或关闭按钮)一个窗口意味着销毁它,例如通过调用gtk_window_destroy中的gtk_window_close可以看到。 使用 GTK 的 C API gtk_window_destroy函数通过一个相当复杂的回调机制进行销毁,达到了与内存管理相关的适当注册的 C++ 函数。 数据断点有助于准确定位gobject_设置为 0 的位置 - Object::destroy_notify_ (它本身是从 GTK 的引用计数代码中间接调用的,因为对话框的引用计数降至 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
    }
  }
}

因此,在关闭对话框后, MessageDialog的内部句柄被清除为 0(顺便说一句,在此期间未调用 MessageDialog 析构函数,我也在调试器中进行了检查),并且由于您调用了一个假定它不是的close函数,你会遇到内存访问问题。 析构函数在gobject_已被清除的情况下正确工作,因此在关闭后在on_button_clicked中重新创建MessageDialog不会出现问题(如果存在, std::unique_ptr为先前的对象调用析构函数),以及在gobject_仍然指向的情况下到有效的对象,所以即使在重新创建之前没有关闭对话框,你也不应该有问题。 出于同样的原因,在AppWindow析构函数中调用MessageDialog析构函数是可以的,无论对话框是否在窗口之前关闭。

PS 请注意,此处的 gtkmm .cc文件来自 vcpkg buildtrees,在构建过程中使用 gtkmm 的自定义预处理gmmproc工具从.ccg源文件生成,因此您不会在存储库中找到它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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