簡體   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