【问题标题】:GTK+, C-based software, corruption with ImagesGTK+,基于 C 的软件,图像损坏
【发布时间】:2020-06-25 19:02:46
【问题描述】:

我正在为家用 CNC 铣床的数字读出器/控制器编写一些代码,目标是带有 TFT/LCD 的 Pi,我选择使用我知道的 C 和 GTK +3,我知道的比较少(在黑暗中摸索,突然想到)。

控制器的一个页面(通过来自主应用程序/GTK 计时器事件的更新调用安排,具体取决于活动页面)应该呈现 3 个(或其他)按钮,并带有按钮状态的可视指示符。按钮图像从 .png 文件加载。

在创建时,我加载选择/取消选择的图像并存储在每个按钮的结构中。刷新时,我会更改选定/取消选定图像的状态,并使用适当的图像更新按钮图像。或者这就是理论。在初始化时,我默认取消选择图像。我可以很容易地更改为选中,但随后调用取消选中的图像会导致 GTK 错误消息。

调用函数 play_init() 来创建页面内容(前缀“play...” - 这将是一个模板“页面”,以测试不同的 GTK 方法 - 这是为了解决在不同版本中实现的相同问题在不破坏主要代码库的情况下)。

每个“页面”都会创建一个容器,main() 将其附加到 GTK 笔记本上,因此此启动代码无需担心窗口设置 - 其他地方。

在下面的代码部分中,我修改了一些代码并添加了调试代码以默认为取消选择,然后更改为已选择,返回为取消选择等等,所有这些都带有“我在这里”调试语句。这是为了避免指针超出范围等任何问题,以尝试了解我哪里出错了。我知道这与设置按钮图像有关,但无法弄清楚我在这里错过了什么。确实,图像选择应该(是)在包含的事件框事件处理程序中进行管理......但是在这里转储调试代码只是有助于将事情保持在一起。

所有调试文本(1、2、3、4、5 等)只是为了让我可以找到代码开始失败的点。我最近添加了 gtk_button_set_image(.., NULL) 以尝试在设置新图像之前“卸载”图像。纳达,没有变化。

/****************************************************
 * Local type definitions
 ****************************************************/
enum buttons {START, PAUSE, STOP};
typedef enum buttons button_t;

struct play_eb_buttons
{
   GtkWidget *button;      // Button holding the button image
   GtkWidget *im_selc;     // "Selected" image
   GtkWidget *im_deselc;   // "De-selected" image
   GtkWidget *eb;          // Event box containing button
   GtkWidget *parent;      // Points to the parent of the event box
   bool       selc;        // New selection state
   bool       last_selc;   // Previous selection state
};
typedef struct play_eb_buttons play_eb_button_t;

/****************************************************
 * Code Implementation                ** INTERNALS **
 ****************************************************/
void playSetSelcState(play_eb_button_t *button, bool selc_state)
{
   button->selc = selc_state;
   if ((button->selc == true ) && (button->last_selc == false ))
   {
      printf("Setting image (selc) 0x%llX\n", button->im_selc);
      gtk_button_set_image(GTK_BUTTON(button->button), button->im_selc);
   }
   else if ((button->selc == false ) && (button->last_selc == true))
   {
      printf("Setting image (deselc) 0x%llX\n", button->im_deselc);
      gtk_button_set_image(GTK_BUTTON(button->button), button->im_deselc);
   }
   button->last_selc = button->selc;

}

gint playgreen_press_callback( GtkWidget *widget, GdkEvent  *event, gpointer   callback_data )
{
   printf("Green callback invoked\n");
   playSetSelcState(&play_eb_button[START], true);
   playSetSelcState(&play_eb_button[PAUSE], false);
   playSetSelcState(&play_eb_button[STOP],  false);
   return TRUE;
}
gint playyellow_press_callback( GtkWidget *widget, GdkEvent  *event, gpointer   callback_data )
{
   printf("Yellow callback invoked\n");
   playSetSelcState(&play_eb_button[START], false);
   playSetSelcState(&play_eb_button[PAUSE], true);
   playSetSelcState(&play_eb_button[STOP],  false);
   return TRUE;
}
gint playred_press_callback( GtkWidget *widget, GdkEvent  *event, gpointer   callback_data )
{
   printf("Red callback invoked\n");
   playSetSelcState(&play_eb_button[START], false);
   playSetSelcState(&play_eb_button[PAUSE], false);
   playSetSelcState(&play_eb_button[STOP],  true);
   return TRUE;

}

void playCreateButton( GtkWidget *parent, char *selc, char *deselc, char *name, int col, int row, GCallback func, play_eb_button_t *button )
{
   GtkWidget *label;

   button->button    = gtk_button_new();
   button->eb        = gtk_event_box_new();
   button->parent    = parent;
   button->selc      = false;
   button->last_selc = false;
   button->im_selc   = gtk_image_new_from_file(selc);
   button->im_deselc = gtk_image_new_from_file(deselc);
   gtk_widget_set_vexpand(button->im_selc, TRUE);
   gtk_widget_set_vexpand(button->im_deselc, TRUE);
   printf("1\n");
   gtk_button_set_image(GTK_BUTTON(button->button), NULL);
   gtk_button_set_image(GTK_BUTTON(button->button), button->im_deselc);
   printf("2\n");
   gtk_button_set_image(GTK_BUTTON(button->button), NULL);
   gtk_button_set_image(GTK_BUTTON(button->button), button->im_selc);
   printf("3\n");
   gtk_button_set_image(GTK_BUTTON(button->button), NULL);
   gtk_button_set_image(GTK_BUTTON(button->button), button->im_deselc);
   printf("4\n");
   gtk_button_set_image(GTK_BUTTON(button->button), NULL);
   gtk_button_set_image(GTK_BUTTON(button->button), button->im_selc);
   printf("5\n");
   gtk_button_set_image(GTK_BUTTON(button->button), NULL);
   gtk_button_set_image(GTK_BUTTON(button->button), button->im_deselc);
   printf("6\n");
   gtk_button_set_image(GTK_BUTTON(button->button), NULL);
   gtk_container_add(GTK_CONTAINER(button->eb), button->button);
   gtk_grid_attach(GTK_GRID(parent), button->eb, col,row, 1, 1);
   g_signal_connect (button->eb, "button_press_event", G_CALLBACK (func), NULL);

//   eb = gtk_event_box_new();
//   label = gtk_label_new(name);
//   gtk_container_add(GTK_CONTAINER(eb), label);
//   gtk_widget_set_name(label, "buttons");
//   gtk_grid_attach(GTK_GRID(parent), eb, col+1, row, 1, 1);
//   g_signal_connect (eb, "button_press_event", G_CALLBACK (func), NULL);

   printf("'%s' at 0x%16llX and 0x%16llX\n", name, button->im_selc, button->im_deselc);
}

/****************************************************
 * Code Implementation                ** EXTERNALS **
 ****************************************************/
/*
 * play_init()
 *
 * Setup for the complete DRO page for the GUI
 */
void play_init()
{
   GtkWidget *grid;

   play_container = gtk_event_box_new();
   grid = gtk_grid_new();
   gtk_container_add(GTK_CONTAINER(play_container), grid);

   gtk_widget_modify_bg(grid, GTK_STATE_NORMAL, &colour_black);

   playCreateButton(grid, "push-button-green.png" ,"push-button-green-dselc.png",  "Run!",  0, 1, (GCallback)playgreen_press_callback,  &play_eb_button[START]);
   playCreateButton(grid, "push-button-yellow.png","push-button-yellow-dselc.png", "Pause", 0, 2, (GCallback)playyellow_press_callback, &play_eb_button[PAUSE]);
   playCreateButton(grid, "push-button-red.png"   ,"push-button-red-dselc.png",    "STOP",  0, 3, (GCallback)playred_press_callback,    &play_eb_button[STOP]);
   playSetSelcState(&play_eb_button[START], true);
   playSetSelcState(&play_eb_button[START], false);

}

我机器的输出(主机:HP Z600,运行 Ubuntu 20.04)

Hostname = 'mike-HP-Z600-Workstation'
Initialising
State = Connecting
1
2
3

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.182: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
4

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.182: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
5

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.182: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
6
'Run!' at 0x    56446A49E2B0 and 0x    56446A49E420
1
2
3

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.185: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
4

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.185: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
5

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.185: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
6
'Pause' at 0x    56446A49E700 and 0x    56446A49E870
1
2
3

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.187: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
4

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.187: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
5

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.187: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
6
'STOP' at 0x    56446A49E870 and 0x    56446A49E2B0
Setting image (selc) 0x56446A49E2B0

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.187: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Setting image (deselc) 0x56446A49E420

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.188: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Setting image (selc) 0x56446A49E2B0

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:26.806: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Graphics terminated
Closing

我已经通过这个尝试了 GDB,并观察了 eb_button 数组,期待损坏或其他东西,但该结构很好。错误消息表明图像在调用“3”之前受到影响(图像指针保持非空)

如果我让事件处理程序代码运行,我可能会遇到其他 GTK 错误,这些错误都是围绕相同的调用结构来设置按钮图像...

(SiegDROsGtk3:556962): Gtk-CRITICAL **: 19:48:22.637: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Setting image (selc) 0x558A215F72B0

(SiegDROsGtk3:556962): Gtk-CRITICAL **: 19:48:25.225: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Setting image (deselc) 0x558A215F7420
Setting image (selc) 0x558A215F72B0

(SiegDROsGtk3:556962): Gtk-CRITICAL **: 19:48:30.341: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Setting image (deselc) 0x558A215F7420

(SiegDROsGtk3:556962): GLib-GObject-CRITICAL **: 19:48:32.898: g_object_ref: assertion 'G_IS_OBJECT (object)' failed

(SiegDROsGtk3:556962): Gtk-CRITICAL **: 19:48:32.898: gtk_widget_get_parent: assertion 'GTK_IS_WIDGET (widget)' failed
Setting image (selc) 0x558A215F72B0

(SiegDROsGtk3:556962): Gtk-CRITICAL **: 19:48:35.458: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Setting image (deselc) 0x558A215F7420

(SiegDROsGtk3:556962): Gtk-CRITICAL **: 19:48:38.022: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Setting image (selc) 0x558A215F72B0

在没有初始化调试的情况下运行它,按钮都被取消选择,我可以选择例如绿色,并以正确的选择图像突出显示;选择例如黄色按钮,绿色不取消选择(得到该错误),黄色成功选择。按 Reg 和黄色不会取消选择(得到错误),但红色会。在某些时候,我会完全丢失按钮图像,或者有时会得到一个未加载图像的默认图像。

看起来好像对 gtk_button_set_image 的连续调用正在破坏预加载的图像。

显然我在做一些愚蠢的事情,但我无法找到一个明智的谷歌搜索结果来说明我做错了什么。

帮忙?!

【问题讨论】:

    标签: c gtk3


    【解决方案1】:

    这是因为引用计数。详细说明见here,下面是简版。

    所有 GObject 都以 rc (reference count, refcount) 设置为 1 开头,但所有小部件都是 GInitiallyUnowned(它们有特殊标志,表明它还没有所有者)。

    当您第一次将小部件打包到容器中时,g_object_ref_sink 会被调用,它会获取小部件的所有权,将 rc 设置为 1 并删除标志。因此,当您将图像替换为新图像时,先前的图像将被取消引用。取消引用时,它的 rc 降为 0,对象被自动销毁。

    您可以通过将任何对象强制转换为 (GObject*) 并查看它的 ref_count 字段来检查 GDB 中的这种行为。

    可以做什么:

    1. 手动参考图像。如果您调用_ref_sink,您将获得对象所有权。当它被添加到容器中时,它将被引用(rc 将为 2)并在移除时取消引用(rc 将为 1,因此对象将仍然存在)。您还需要在程序结束时取消对图像的引用。
    2. 存储图标名称而不是小部件并在playSetSelcState 中创建新图像

    【讨论】:

    • Alexander - 感谢您提供非常清晰简洁的答案。我已经针对加载的图像调用了 _ref_sink ,这似乎可以按我的预期工作 - 以及您如何解释。我在文档中遇到了引用计数机制,但并没有指出替换图像会删除旧图像。非常感谢 - 我剩下的头发太少了。
    猜你喜欢
    • 2011-04-06
    • 2018-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多