You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GTK3封装GetOpenFileName时gtk_init崩溃,求修复或GTK4实现方案

修复GTK3文件选择对话框崩溃问题

解决GTK3崩溃的几个关键修复点:

  1. 延迟初始化GTK
    全局静态对象s_gtkInit会在程序启动时自动调用gtk_init,这个时机可能早于其他依赖库初始化,或在非主线程执行,直接导致崩溃。改成第一次调用对话框时再初始化:
// 移除全局静态GtkInit类,替换为线程安全的延迟初始化
static void ensureGtkInit() {
    static std::once_flag initFlag;
    std::call_once(initFlag, []() {
        int argc = 0;
        gtk_init(&argc, nullptr);
        SLOG_STMI() << "init gtk done";
    });
}

// 在nativeFileDialog开头调用
ensureGtkInit();
  1. 修正父窗口关联逻辑
    native2GtkWindow创建临时GtkWindow并绑定外部HWND的方式存在资源泄漏和窗口管理冲突,改用更安全的实现:
static GtkWindow* native2GtkWindow(HWND winid) {
    if (!winid) return nullptr;
    GdkDisplay* display = gdk_display_get_default();
    GdkWindow* gdk_wnd = gdk_x11_window_foreign_new_for_display(display, winid);
    if (!gdk_wnd) return nullptr;
    // 尝试获取已有顶层窗口,不存在则返回NULL
    GtkWidget* widget = gtk_widget_get_toplevel(GTK_WIDGET(gdk_wnd));
    return GTK_IS_WINDOW(widget) ? GTK_WINDOW(widget) : nullptr;
}
  1. 确保GTK操作在主线程执行
    GTK要求所有UI操作必须在主线程完成,如果程序在非主线程调用对话框,需通过消息队列切换到主线程执行。

  2. 内存安全检查

  • 修复substr越界问题:
static char* substr(const char* src, int m, int n) {
    if (!src || m < 0) return nullptr;
    int srcLen = strlen(src);
    n = std::min(n, srcLen);
    if (m >= n) return nullptr;
    int len = n - m;
    char* dest = (char*)malloc(sizeof(char) * (len + 1));
    if (!dest) return nullptr;
    strncpy(dest, src + m, len);
    dest[len] = '\0';
    return dest;
}
  • 检查文件名获取的空指针:
const char* filename = gtk_file_chooser_get_filename(chooser);
if (filename) {
    **filenames = strdup(filename);
} else {
    **filenames = nullptr;
    ret = false;
}

GTK4实现类似GetOpenFileName的文件选择对话框

GTK4废弃了GtkFileChooserDialog,改用GtkFileDialog,API更简洁,以下是完整实现:

#include "gtkdlghelper.h"
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include "../log.h"

#define kLogTag "gtk"
namespace Gtk {

enum class Mode {
    OPEN,
    SAVE,
    FOLDER
};

static void ensureGtkInit() {
    static std::once_flag initFlag;
    std::call_once(initFlag, []() {
        int argc = 0;
        gtk_init(&argc, nullptr);
        SLOG_STMI() << "init gtk4 done";
    });
}

static GdkSurface* native2GdkSurface(HWND winid) {
    if (!winid) return nullptr;
    GdkDisplay* display = gdk_display_get_default();
    return gdk_x11_surface_foreign_new_for_display(display, winid);
}

bool openGtkFileChooser(std::list<std::string>& files,
                        HWND parent,
                        Mode mode,
                        const std::string& title,
                        const std::string& file,
                        const std::string& path,
                        const std::string& filter,
                        std::string* sel_filter,
                        bool sel_multiple) {
    ensureGtkInit();

    GtkFileDialog* dialog = gtk_file_dialog_new();
    gtk_file_dialog_set_title(dialog, title.c_str());

    // 设置初始路径或文件名
    if (!path.empty()) {
        GFile* gPath = g_file_new_for_path(path.c_str());
        gtk_file_dialog_set_initial_folder(dialog, gPath);
        g_object_unref(gPath);
    }
    if (!file.empty()) {
        gtk_file_dialog_set_initial_name(dialog, file.c_str());
    }

    // 设置文件过滤器(适配Windows风格格式)
    if (!filter.empty() && mode != Mode::FOLDER) {
        GtkFileFilter* gtkFilter = gtk_file_filter_new();
        std::stringstream ss(filter);
        std::string token;
        while (std::getline(ss, token, ';;')) {
            size_t openParen = token.find('(');
            size_t closeParen = token.find(')');
            if (openParen != std::string::npos && closeParen != std::string::npos) {
                std::string name = token.substr(0, openParen);
                std::string patterns = token.substr(openParen + 1, closeParen - openParen - 1);
                gtk_file_filter_set_name(gtkFilter, name.c_str());
                std::stringstream patternSs(patterns);
                std::string pattern;
                while (std::getline(patternSs, pattern, ' ')) {
                    gtk_file_filter_add_pattern(gtkFilter, pattern.c_str());
                }
            }
        }
        gtk_file_dialog_set_filter(dialog, gtkFilter);
        g_object_unref(gtkFilter);
    }

    // 根据模式执行对话框
    GError* error = nullptr;
    gboolean ret = FALSE;
    if (mode == Mode::OPEN) {
        if (sel_multiple) {
            GListStore* store = gtk_file_dialog_open_multiple(dialog, 
                                                              parent ? GTK_WIDGET(native2GdkSurface(parent)) : nullptr,
                                                              nullptr, &error);
            if (store) {
                ret = TRUE;
                guint nItems = g_list_model_get_n_items(G_LIST_MODEL(store));
                for (guint i = 0; i < nItems; i++) {
                    GFile* file = G_FILE(g_list_model_get_item(G_LIST_MODEL(store), i));
                    char* path = g_file_get_path(file);
                    files.push_back(path);
                    g_free(path);
                    g_object_unref(file);
                }
                g_object_unref(store);
            }
        } else {
            GFile* selectedFile = gtk_file_dialog_open(dialog,
                                                       parent ? GTK_WIDGET(native2GdkSurface(parent)) : nullptr,
                                                       nullptr, &error);
            if (selectedFile) {
                ret = TRUE;
                char* path = g_file_get_path(selectedFile);
                files.push_back(path);
                g_free(path);
                g_object_unref(selectedFile);
            }
        }
    } else if (mode == Mode::SAVE) {
        GFile* selectedFile = gtk_file_dialog_save(dialog,
                                                   parent ? GTK_WIDGET(native2GdkSurface(parent)) : nullptr,
                                                   nullptr, &error);
        if (selectedFile) {
            ret = TRUE;
            char* path = g_file_get_path(selectedFile);
            files.push_back(path);
            g_free(path);
            g_object_unref(selectedFile);
        }
    } else if (mode == Mode::FOLDER) {
        GtkFileDialogFlags flags = GTK_FILE_DIALOG_SELECT_FOLDER;
        GFile* selectedFolder = gtk_file_dialog_open(dialog,
                                                     parent ? GTK_WIDGET(native2GdkSurface(parent)) : nullptr,
                                                     nullptr, &error);
        if (selectedFolder) {
            ret = TRUE;
            char* path = g_file_get_path(selectedFolder);
            files.push_back(path);
            g_free(path);
            g_object_unref(selectedFolder);
        }
    }

    if (error) {
        SLOG_STMI() << "File dialog error: " << error->message;
        g_error_free(error);
    }

    g_object_unref(dialog);
    return ret == TRUE;
}

static inline BYTE tobcolor(double v) {
    return (BYTE)(v * 255);
}

bool gtk_choose_color(HWND parent, COLORREF* out) {
    ensureGtkInit();

    GtkColorDialog* dialog = gtk_color_dialog_new();
    gtk_color_dialog_set_title(dialog, "Choose Color");

    GdkRGBA initialColor = {0};
    // 如需初始化颜色,可从out读取值赋值给initialColor

    GError* error = nullptr;
    GdkRGBA selectedColor;
    gboolean ret = gtk_color_dialog_choose_rgba(dialog,
                                                parent ? GTK_WIDGET(native2GdkSurface(parent)) : nullptr,
                                                &initialColor,
                                                nullptr, &error, &selectedColor);
    if (ret) {
        *out = RGBA(tobcolor(selectedColor.red), 
                    tobcolor(selectedColor.green), 
                    tobcolor(selectedColor.blue), 
                    tobcolor(selectedColor.alpha));
    }

    if (error) {
        SLOG_STMI() << "Color dialog error: " << error->message;
        g_error_free(error);
    }

    g_object_unref(dialog);
    return ret == TRUE;
}

} // end of ns Gtk

说明

  • GTK4的GtkFileDialog采用同步调用,和GetOpenFileName行为一致
  • 过滤器解析适配Windows风格格式(用;;分隔不同过滤器组)
  • 父窗口关联通过gdk_x11_surface_foreign_new_for_display获取对应GdkSurface传递给对话框
  • 所有GTK操作通过ensureGtkInit确保只初始化一次,且在第一次调用时执行

内容的提问来源于stack exchange,提问作者黄建雄

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 18:19:48