GTK+,基于 C 的软件,图像损坏

GTK+, C-based software, corruption with Images

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

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

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

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

每个 'page' 创建一个 main() 附加到 GTK notebook 的容器,因此此启动代码不用担心 window 设置 - 那在别处。

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

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

/****************************************************
 * 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,运行ning 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

运行 这个没有初始化调试的东西,按钮都是 de-selected,我可以 select 例如绿色,并以正确选择的图像突出显示;选择例如黄色按钮绿色没有 de-select (得到那个错误),黄色成功 selects。按 Reg 和黄色不会 deselect (得到错误)但红色会。在某些时候,我要么完全丢失按钮图像,要么有时会得到一个未加载图像的默认图像。

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

显然我在做一些愚蠢的事情,但我找不到一个明智的 google 来判断我做错了什么。

求助?!

那是因为引用计数。详细描述可以在下面找到 here,简短版本。

所有的GObject都以rc(reference count, refcount)设置为1开始,但是所有的widgets都是GInitiallyUnowned(它们有特殊的flag,表明它还没有一个owner)

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

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

可以做什么:

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