来自 GDK 事件处理程序和 GtkDrawingArea on_draw 函数的并发内存访问
Concurent memory access from a GDK Event handler and GtkDrawinaArea on_draw function
我正在实现一个简单的程序,当我单击网格中的空白位置时,它允许我在网格中放置矩形。我还让它可以滚动,这样网格的大小就可以很大了。以下是它的作用以及我面临的问题的简要概述。
这里是我定义的全局变量:
//The initial height of the scroll window
#define SCROLL_WINDOW_HEIGHT 480
//The initial width of the scroll window
#define SCROLL_WINDOW_WIDTH 320
//The number of verticall lines
#define NUMBER_OF_KEYS 14
// The number of larger horizontal lines
#define MINUTE 60
//The width of the entire drawing area
#define FULL_WIDTH 24000
//The height of the entire drawing area
#define FULL_HEIGHT 1400
//Height of one rectangle
#define RECT_HEIGHT FULL_HEIGHT / NUMBER_OF_KEYS
//Width of one rectangle
#define RECT_WIDTH FULL_WIDTH / (MINUTE * 20)
//The current x possition of the cursor
int x;
//The current y possition of the cursor
int y;
//The linked list sructure storing the rectangles
node *rect_list;
我制作了一个结构来表示 矩形 :
typedef struct rectangle
{
int x;
int y;
int width;
int height;
int id;
}rectangle;
为了保存我已经创建的 矩形 ,我只需将它们添加到我的 链接列表 中,该列表具有 哨兵。这是它的结构,build_sentinel 函数和向链表添加新元素的函数:
typedef struct node
{
struct rectangle *value;
struct node *next;
} node;
rectangle * init_null_rectangle()
{
rectangle * new_rectangle = malloc(sizeof(rectangle));
new_rectangle->height = 0;
new_rectangle->width = 0;
new_rectangle->x = 0;
new_rectangle->y = 0;
new_rectangle->id = -1;
return new_rectangle;
}
// Builds the sentinell of the node structure
node *node_build_sentinel()
{
// Creates the sentinel.
node *head = mem_alloc(sizeof(node));
head->value = init_null_rectangle();
head->next = NULL;
// Returns the head of the node which is the sentinell.
return head;
}
// Frees the allocated node
void node_free(node *head)
{
node *previous;
while (head)
{
previous = head;
head = head->next;
free(previous->value);
free(previous);
}
}
// Inserts a value right after the head
/*
HEAD -> 1 -> 2 -> ..... -> 8
node_insert_beg(node* HEAD, int 42);
HEAD -> 42 -> 1 -> 2 -> ..... -> 8
*/
void node_insert_beg(node *head, rectangle *value)
{
node *tmp = malloc(sizeof(node));
tmp->value = value;
tmp->next = head->next;
head->next = tmp;
}
这里有一些其他实用函数,用于计算矩形的 ID,以及该矩形的最左上角
void set_closest_rectangle()
{
// First check if the square has no rectangle already
int new_x = x%(RECT_WIDTH);
int new_y = y%(RECT_HEIGHT);
x -= new_x;
y -= new_y;
}
int id_from_coordinate(int x, int y)
{
int id = (y / (RECT_HEIGHT)) * MINUTE * 20 + x / (RECT_WIDTH);
return id;
}
这些功能完美地独立工作,但我提供它们以提供详细信息。
现在让我们谈谈我组织绘图区和事件管理的方式:
绘图区
以下是用于设置绘图区域和绘制不同部分的函数,同样它们都能完美地工作。
//Sets up the drawing space to dynamically manage the resize
void set_up_grid(GtkWidget *widget, cairo_t *cr,double *dx,double *dy,double *clip_y1, double *clip_y2,double *clip_x1, double *clip_x2,GdkRectangle * da )
{
GdkWindow *window = gtk_widget_get_window(widget);
/* Determine GtkDrawingArea dimensions */
gdk_window_get_geometry(window, &da->x, &da->y, &da->width, &da->height);
/* Draw on a black background */
cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
cairo_paint(cr);
/* Determine the data points to calculate (ie. those in the clipping zone */
cairo_device_to_user_distance(cr, dx, dy);
cairo_clip_extents(cr, clip_x1, clip_y1, clip_x2, clip_y2);
}
//Draws the grid on which the rectangles are placed
void on_draw_grid(cairo_t *cr, double dx, double clip_y2, double clip_x2)
{
//Horizontal lines
for (size_t i = 0; i < FULL_WIDTH; i += (FULL_WIDTH / MINUTE))
{
cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
cairo_move_to(cr, i, 0.0);
cairo_line_to(cr, i, clip_y2);
cairo_stroke(cr);
cairo_set_line_width(cr, dx / 4);
cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
for (size_t j = 0; j < (FULL_WIDTH / MINUTE); j += (FULL_WIDTH / (MINUTE * 20)))
{
cairo_move_to(cr, j + i, 0.0);
cairo_line_to(cr, j + i, clip_y2);
}
cairo_stroke(cr);
}
// vertical lines
for (size_t i = 0; i < FULL_HEIGHT; i += FULL_HEIGHT / NUMBER_OF_KEYS)
{
cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
cairo_move_to(cr, 0.0, i);
cairo_line_to(cr, clip_x2, i);
cairo_stroke(cr);
}
}
//Draws one rectangle on the cr, with left top most corner (x,y)
void on_draw_rectangle(cairo_t *cr,int x, int y)
{
cairo_move_to(cr,x,y);
cairo_line_to(cr,x,y + RECT_HEIGHT);
cairo_line_to(cr,x + RECT_WIDTH,y + RECT_HEIGHT);
cairo_line_to(cr,x + RECT_WIDTH,y);
cairo_line_to(cr,x,y);
cairo_set_source_rgb(cr, 0.2, 0.6, 0.1);
cairo_fill(cr);
}
下面是完成最终绘图的主要绘图区函数:
请注意,链表中的每个矩形都应该在 while 条件下绘制,当然除了哨兵。
static gboolean on_draw(GtkWidget *widget, cairo_t *cr, __attribute_maybe_unused__ gpointer user_data)
{
GdkRectangle da; /* GtkDrawingArea size */
gdouble dx = 4.0, dy = 4.0; /* Pixels between each point */
gdouble clip_x1 = 0.0, clip_y1 = 0.0, clip_x2 = 0.0, clip_y2 = 0.0;
set_up_grid(widget,cr,&dx,&dy,&clip_y1,&clip_y2,&clip_x1,&clip_x2, &da);
on_draw_grid(cr,dx,clip_y2,clip_x2);
while(rect_list->next)
{
rect_list = rect_list->next;
on_draw_rectangle(cr,rect_list->value->x,rect_list->value->y);
}
gtk_widget_queue_draw_area(widget, 0, 0, da.width, da.height);
return G_SOURCE_REMOVE;
}
为了能够只显示绘图区域的一部分,我实现了滚动功能。它由这个结构和相应的调用函数管理。此功能再次完美运行,我不认为它与问题有任何关系,但我提供它是为了保持一致性。
此结构包含主表面和绘制表面:
typedef struct cairo_surfaces
{
cairo_surface_t *main_surface, *seen_surface;
} cairo_surfaces;
这是在 DrawingArea 发出配置事件时触发的调用函数:
static gboolean on_configure(GtkWidget *widget, __attribute_maybe_unused__ GdkEventConfigure *event_p, gpointer user_data)
{
cairo_t *cr_p;
cairo_surfaces *my_data;
my_data = (cairo_surfaces *)user_data;
if (my_data->seen_surface)
{
cairo_surface_destroy(my_data->seen_surface);
}
my_data->seen_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, SCROLL_WINDOW_HEIGHT, SCROLL_WINDOW_WIDTH);
gtk_widget_set_size_request(widget, FULL_WIDTH, FULL_HEIGHT);
cr_p = cairo_create(my_data->seen_surface);
cairo_set_source_surface(cr_p, my_data->main_surface, 0, 0);
cairo_paint(cr_p);
cairo_destroy(cr_p);
return FALSE;
}
事件
现在让我们谈谈事件管理:所以我简单地添加了我想要获取的事件的 GDK 掩码,这里是在检测到按下事件时添加一个新矩形的函数。
// Gets the current event and adds a rectangle to rect_list
static gboolean
current_key_click(GtkWidget *da,GdkEvent *event, __attribute_maybe_unused__ gpointer user_data)
{
GdkDisplay *display = gdk_display_get_default();
GdkSeat *seat = gdk_display_get_default_seat(display);
GdkDevice *device = gdk_seat_get_pointer(seat);
if (gdk_event_get_event_type(event) == GDK_BUTTON_PRESS)
{
gdk_window_get_device_position(gtk_widget_get_window(GTK_WIDGET(da)), device, &x, &y, NULL);
set_closest_rectangle();
//Create the rectangle if there is no rectangle at this coordinate
rectangle * new_rect = init_null_rectangle();
new_rect->id = id_from_coordinate(x,y);
new_rect->x = x;
new_rect->y = y;
new_rect->width = RECT_WIDTH;
new_rect->height = RECT_HEIGHT;
node_insert_beg(rect_list,new_rect);
}
return G_SOURCE_REMOVE;
}
然而,在链表上循环的绘图函数似乎存在并发内存访问,并且 current_key_click 函数试图添加一个新的链表的元素到链表。为了尝试解决这个问题,我在关键部分添加了 pthread_mutex。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
也就是我插入节点的时候
***
pthread_mutex_lock(&mutex);
node_insert_beg(rect_list,new_rect);
pthread_mutex_unlock(&mutex);
***
而当我 运行 while 循环
while(rect_list->next)
{
pthread_mutex_lock(&mutex);
rect_list = rect_list->next;
pthread_mutex_unlock(&mutex);
on_draw_rectangle(cr,rect_list->value->x,rect_list->value->y);
}
问题
但这并没有解决我的问题,当我尝试打印链表时,它总是只包含一个项目,而其他所有项目都丢失了。这肯定会发生,因为添加和遍历列表是同时发生的。关于如何解决此问题的任何想法?
为了完整起见并为您节省一些时间(如果您希望提供帮助),这里是整个文件。
我正在构建的 .glade:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<object class="GtkWindow" id="window">
<property name="can-focus">False</property>
<property name="default-width">480</property>
<property name="default-height">320</property>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkViewport" id="view_port">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkDrawingArea" id="da">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>
实际main.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <gtk/gtk.h>
#include <pthread.h>
#define SCROLL_WINDOW_HEIGHT 480
#define SCROLL_WINDOW_WIDTH 320
#define NUMBER_OF_KEYS 14
#define MINUTE 60
#define FULL_WIDTH 24000
#define FULL_HEIGHT 1400
#define RECT_HEIGHT FULL_HEIGHT/NUMBER_OF_KEYS
#define RECT_WIDTH FULL_WIDTH/(MINUTE * 20)
/*
Functions and structs for the linked lists
*/
typedef struct rectangle
{
int x;
int y;
int width;
int height;
int id;
}rectangle;
typedef struct node
{
struct rectangle *value;
struct node *next;
} node;
rectangle * init_null_rectangle()
{
rectangle * new_rectangle = malloc(sizeof(rectangle));
new_rectangle->height = 0;
new_rectangle->width = 0;
new_rectangle->x = 0;
new_rectangle->y = 0;
new_rectangle->id = -1;
return new_rectangle;
}
void print_rectangle(rectangle * rect)
{
printf(" Id: %d ---> x: %d, y: %d, width: %d, height: %d\n",rect->id,rect->x,rect->y,rect->width,rect->height);
}
// Verify if node is empty
int node_is_empty(node *head)
{
return head == NULL;
}
// Verify if node is not empty
int node_is_not_empty(node *head)
{
return head != NULL;
}
// Builds the sentinell of the node structure
node *node_build_sentinel()
{
// Creates the sentinel.
node *head = malloc(sizeof(node));
head->value = init_null_rectangle();
head->next = NULL;
// Returns the head of the node which is the sentinell.
return head;
}
// Prints the contents of a node node* node_build_sentinel()
void node_print(node *head)
{
while (head->next)
{
head = head->next;
print_rectangle(head->value);
}
}
// Frees the allocated node
void node_free(node *head)
{
node *previous;
while (head)
{
previous = head;
head = head->next;
free(previous->value);
free(previous);
}
}
// Inserts a value right after the head
/*
HEAD -> 1 -> 2 -> ..... -> 8
node_insert_beg(node* HEAD, int 42);
HEAD -> 42 -> 1 -> 2 -> ..... -> 8
*/
void node_insert_beg(node *head, rectangle *value)
{
node *tmp = malloc(sizeof(node));
tmp->value = value;
tmp->next = head->next;
head->next = tmp;
}
/*
All Other Functions
*/
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int x;
int y;
node * rect_list;
typedef struct cairo_surfaces
{
cairo_surface_t *main_surface, *seen_surface;
} cairo_surfaces;
void set_closest_rectangle()
{
//First check if the square has no rectangle already
int new_x = x%(RECT_WIDTH);
int new_y = y%(RECT_HEIGHT);
x -= new_x;
y -= new_y;
}
int id_from_coordinate(int x,int y)
{
int id = (y/(RECT_HEIGHT))*MINUTE * 20+ x/(RECT_WIDTH);
printf("id: %d\n",id);
return id;
}
// Gets the current event and sets the x,y possition
static gboolean
current_key_click(GtkWidget *da,GdkEvent *event, __attribute_maybe_unused__ gpointer user_data)
{
GdkDisplay *display = gdk_display_get_default();
GdkSeat *seat = gdk_display_get_default_seat(display);
GdkDevice *device = gdk_seat_get_pointer(seat);
if (gdk_event_get_event_type(event) == GDK_BUTTON_PRESS)
{
gdk_window_get_device_position(gtk_widget_get_window(GTK_WIDGET(da)), device, &x, &y, NULL);
set_closest_rectangle();
//Create the rectangle if there is no rectangle at this coordinate
rectangle * new_rect = init_null_rectangle();
new_rect->id = id_from_coordinate(x,y);
new_rect->x = x;
new_rect->y = y;
new_rect->width = RECT_WIDTH;
new_rect->height = RECT_HEIGHT;
pthread_mutex_lock(&mutex);
node_insert_beg(rect_list,new_rect);
pthread_mutex_unlock(&mutex);
}
return G_SOURCE_REMOVE;
}
void on_quit(__attribute_maybe_unused__ GtkWidget *widget, gpointer user_data)
{
cairo_surfaces *my_data;
my_data = (cairo_surfaces *)user_data;
cairo_surface_destroy(my_data->main_surface);
cairo_surface_destroy(my_data->seen_surface);
gtk_main_quit();
}
static gboolean on_configure(GtkWidget *widget, __attribute_maybe_unused__ GdkEventConfigure *event_p, gpointer user_data)
{
cairo_t *cr_p;
cairo_surfaces *my_data;
my_data = (cairo_surfaces *)user_data;
if (my_data->seen_surface)
{
cairo_surface_destroy(my_data->seen_surface);
}
my_data->seen_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, SCROLL_WINDOW_HEIGHT, SCROLL_WINDOW_WIDTH);
gtk_widget_set_size_request(widget, FULL_WIDTH, FULL_HEIGHT);
cr_p = cairo_create(my_data->seen_surface);
cairo_set_source_surface(cr_p, my_data->main_surface, 0, 0);
cairo_paint(cr_p);
cairo_destroy(cr_p);
return FALSE;
}
//Sets up the drawing space to dynamically manage the resize
void set_up_grid(GtkWidget *widget, cairo_t *cr,double *dx,double *dy,double *clip_y1, double *clip_y2,double *clip_x1, double *clip_x2,GdkRectangle * da )
{
GdkWindow *window = gtk_widget_get_window(widget);
/* Determine GtkDrawingArea dimensions */
gdk_window_get_geometry(window, &da->x, &da->y, &da->width, &da->height);
/* Draw on a black background */
cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
cairo_paint(cr);
/* Determine the data points to calculate (ie. those in the clipping zone */
cairo_device_to_user_distance(cr, dx, dy);
cairo_clip_extents(cr, clip_x1, clip_y1, clip_x2, clip_y2);
}
//Draws the grid on which the rectangles are placed
void on_draw_grid(cairo_t *cr, double dx, double clip_y2, double clip_x2)
{
//Horizontal lines
for (size_t i = 0; i < FULL_WIDTH; i += (FULL_WIDTH / MINUTE))
{
cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
cairo_move_to(cr, i, 0.0);
cairo_line_to(cr, i, clip_y2);
cairo_stroke(cr);
cairo_set_line_width(cr, dx / 4);
cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
for (size_t j = 0; j < (FULL_WIDTH / MINUTE); j += (FULL_WIDTH / (MINUTE * 20)))
{
cairo_move_to(cr, j + i, 0.0);
cairo_line_to(cr, j + i, clip_y2);
}
cairo_stroke(cr);
}
// vertical lines
for (size_t i = 0; i < FULL_HEIGHT; i += FULL_HEIGHT / NUMBER_OF_KEYS)
{
cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
cairo_move_to(cr, 0.0, i);
cairo_line_to(cr, clip_x2, i);
cairo_stroke(cr);
}
}
//Draws one rectangle on the cr, with left top most corner (x,y)
void on_draw_rectangle(cairo_t *cr,int x, int y)
{
cairo_move_to(cr,x,y);
cairo_line_to(cr,x,y + RECT_HEIGHT);
cairo_line_to(cr,x + RECT_WIDTH,y + RECT_HEIGHT);
cairo_line_to(cr,x + RECT_WIDTH,y);
cairo_line_to(cr,x,y);
cairo_set_source_rgb(cr, 0.2, 0.6, 0.1);
cairo_fill(cr);
}
static gboolean on_draw(GtkWidget *widget, cairo_t *cr, __attribute_maybe_unused__ gpointer user_data)
{
GdkRectangle da; /* GtkDrawingArea size */
gdouble dx = 4.0, dy = 4.0; /* Pixels between each point */
gdouble clip_x1 = 0.0, clip_y1 = 0.0, clip_x2 = 0.0, clip_y2 = 0.0;
set_up_grid(widget,cr,&dx,&dy,&clip_y1,&clip_y2,&clip_x1,&clip_x2, &da);
on_draw_grid(cr,dx,clip_y2,clip_x2);
while(rect_list->next)
{
pthread_mutex_lock(&mutex);
rect_list = rect_list->next;
pthread_mutex_unlock(&mutex);
on_draw_rectangle(cr,rect_list->value->x,rect_list->value->y);
}
gtk_widget_queue_draw_area(widget, 0, 0, da.width, da.height);
return G_SOURCE_REMOVE;
}
int main()
{
gtk_init(NULL, NULL);
cairo_surfaces my_data;
rect_list = node_build_sentinel();
my_data.seen_surface = NULL;
my_data.main_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, FULL_WIDTH, FULL_HEIGHT);
GtkBuilder *builder = gtk_builder_new();
GError *error = NULL;
if (gtk_builder_add_from_file(builder, "file.glade", &error) == 0)
{
g_printerr("Error loading file: %s\n", error->message);
g_clear_error(&error);
return 1;
}
GtkWindow *window = GTK_WINDOW(gtk_builder_get_object(builder, "window"));
GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW(gtk_builder_get_object(builder, "scrolled_window"));
GtkDrawingArea *da = GTK_DRAWING_AREA(gtk_builder_get_object(builder, "da"));
gtk_widget_set_size_request(GTK_WIDGET(scrolled_window), SCROLL_WINDOW_HEIGHT, SCROLL_WINDOW_WIDTH);
gtk_widget_set_events(GTK_WIDGET(da), gtk_widget_get_events(GTK_WIDGET(da)) | GDK_SCROLL_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK );
g_signal_connect(G_OBJECT(da), "configure-event", G_CALLBACK(on_configure), &my_data);
g_signal_connect(G_OBJECT(da), "draw", G_CALLBACK(on_draw), NULL);
g_signal_connect(G_OBJECT(da), "event", G_CALLBACK(current_key_click), NULL);
g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(on_quit), &my_data);
gtk_widget_show_all(GTK_WIDGET(window));
gtk_main();
node_print(rect_list);
node_free(rect_list);
return 0;
}
这是我正在编译的内容:
gcc -Wall -Wextra `pkg-config --cflags gtk+-3.0` -g -fsanitize=address main.c -o visualiser `pkg-config --libs gtk+-3.0` -lm -pthread
在此先感谢您的帮助!
我将您的代码复制到我的沙箱中并测试了您的程序。在你的程序中散布了一堆“printf”语句后,我相信我找到了问题所在。您通过哨兵函数设置了“rect_list”节点,最初它持有指向您的第一个结构元素的指针。但是,当单击矩形时,该指针中的值将设置为已创建的新节点结构,并且其“下一个节点”指针始终为 NULL,就像最新条目的情况一样。因此,最后当程序尝试打印出记录列表时,它会引用最后创建的节点条目并结束。此外,none 分配的元素被释放,因此内存泄漏错误也会打印出来。
为了解决这个问题,我只是添加了第二个节点结构指针来保存调用哨兵函数时的初始节点指针(我称之为“starting_point”)。
node * rect_list, *starting_point;
然后在main函数往下,调用sentinel函数后,将初始节点指针保存在这个指针副本中,以备后用。
rect_list = node_build_sentinel();
starting_point = rect_list; /* Save the initial pointer */
然后在终结函数中,起点指针作为节点指针引用。
node_print(starting_point);
node_free(starting_point);
return 0;
这导致生成了一个元素列表并消除了内存泄漏警告消息。
希望对您有所帮助。
此致。
我正在实现一个简单的程序,当我单击网格中的空白位置时,它允许我在网格中放置矩形。我还让它可以滚动,这样网格的大小就可以很大了。以下是它的作用以及我面临的问题的简要概述。
这里是我定义的全局变量:
//The initial height of the scroll window
#define SCROLL_WINDOW_HEIGHT 480
//The initial width of the scroll window
#define SCROLL_WINDOW_WIDTH 320
//The number of verticall lines
#define NUMBER_OF_KEYS 14
// The number of larger horizontal lines
#define MINUTE 60
//The width of the entire drawing area
#define FULL_WIDTH 24000
//The height of the entire drawing area
#define FULL_HEIGHT 1400
//Height of one rectangle
#define RECT_HEIGHT FULL_HEIGHT / NUMBER_OF_KEYS
//Width of one rectangle
#define RECT_WIDTH FULL_WIDTH / (MINUTE * 20)
//The current x possition of the cursor
int x;
//The current y possition of the cursor
int y;
//The linked list sructure storing the rectangles
node *rect_list;
我制作了一个结构来表示 矩形 :
typedef struct rectangle
{
int x;
int y;
int width;
int height;
int id;
}rectangle;
为了保存我已经创建的 矩形 ,我只需将它们添加到我的 链接列表 中,该列表具有 哨兵。这是它的结构,build_sentinel 函数和向链表添加新元素的函数:
typedef struct node
{
struct rectangle *value;
struct node *next;
} node;
rectangle * init_null_rectangle()
{
rectangle * new_rectangle = malloc(sizeof(rectangle));
new_rectangle->height = 0;
new_rectangle->width = 0;
new_rectangle->x = 0;
new_rectangle->y = 0;
new_rectangle->id = -1;
return new_rectangle;
}
// Builds the sentinell of the node structure
node *node_build_sentinel()
{
// Creates the sentinel.
node *head = mem_alloc(sizeof(node));
head->value = init_null_rectangle();
head->next = NULL;
// Returns the head of the node which is the sentinell.
return head;
}
// Frees the allocated node
void node_free(node *head)
{
node *previous;
while (head)
{
previous = head;
head = head->next;
free(previous->value);
free(previous);
}
}
// Inserts a value right after the head
/*
HEAD -> 1 -> 2 -> ..... -> 8
node_insert_beg(node* HEAD, int 42);
HEAD -> 42 -> 1 -> 2 -> ..... -> 8
*/
void node_insert_beg(node *head, rectangle *value)
{
node *tmp = malloc(sizeof(node));
tmp->value = value;
tmp->next = head->next;
head->next = tmp;
}
这里有一些其他实用函数,用于计算矩形的 ID,以及该矩形的最左上角
void set_closest_rectangle()
{
// First check if the square has no rectangle already
int new_x = x%(RECT_WIDTH);
int new_y = y%(RECT_HEIGHT);
x -= new_x;
y -= new_y;
}
int id_from_coordinate(int x, int y)
{
int id = (y / (RECT_HEIGHT)) * MINUTE * 20 + x / (RECT_WIDTH);
return id;
}
这些功能完美地独立工作,但我提供它们以提供详细信息。
现在让我们谈谈我组织绘图区和事件管理的方式:
绘图区
以下是用于设置绘图区域和绘制不同部分的函数,同样它们都能完美地工作。
//Sets up the drawing space to dynamically manage the resize
void set_up_grid(GtkWidget *widget, cairo_t *cr,double *dx,double *dy,double *clip_y1, double *clip_y2,double *clip_x1, double *clip_x2,GdkRectangle * da )
{
GdkWindow *window = gtk_widget_get_window(widget);
/* Determine GtkDrawingArea dimensions */
gdk_window_get_geometry(window, &da->x, &da->y, &da->width, &da->height);
/* Draw on a black background */
cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
cairo_paint(cr);
/* Determine the data points to calculate (ie. those in the clipping zone */
cairo_device_to_user_distance(cr, dx, dy);
cairo_clip_extents(cr, clip_x1, clip_y1, clip_x2, clip_y2);
}
//Draws the grid on which the rectangles are placed
void on_draw_grid(cairo_t *cr, double dx, double clip_y2, double clip_x2)
{
//Horizontal lines
for (size_t i = 0; i < FULL_WIDTH; i += (FULL_WIDTH / MINUTE))
{
cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
cairo_move_to(cr, i, 0.0);
cairo_line_to(cr, i, clip_y2);
cairo_stroke(cr);
cairo_set_line_width(cr, dx / 4);
cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
for (size_t j = 0; j < (FULL_WIDTH / MINUTE); j += (FULL_WIDTH / (MINUTE * 20)))
{
cairo_move_to(cr, j + i, 0.0);
cairo_line_to(cr, j + i, clip_y2);
}
cairo_stroke(cr);
}
// vertical lines
for (size_t i = 0; i < FULL_HEIGHT; i += FULL_HEIGHT / NUMBER_OF_KEYS)
{
cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
cairo_move_to(cr, 0.0, i);
cairo_line_to(cr, clip_x2, i);
cairo_stroke(cr);
}
}
//Draws one rectangle on the cr, with left top most corner (x,y)
void on_draw_rectangle(cairo_t *cr,int x, int y)
{
cairo_move_to(cr,x,y);
cairo_line_to(cr,x,y + RECT_HEIGHT);
cairo_line_to(cr,x + RECT_WIDTH,y + RECT_HEIGHT);
cairo_line_to(cr,x + RECT_WIDTH,y);
cairo_line_to(cr,x,y);
cairo_set_source_rgb(cr, 0.2, 0.6, 0.1);
cairo_fill(cr);
}
下面是完成最终绘图的主要绘图区函数: 请注意,链表中的每个矩形都应该在 while 条件下绘制,当然除了哨兵。
static gboolean on_draw(GtkWidget *widget, cairo_t *cr, __attribute_maybe_unused__ gpointer user_data)
{
GdkRectangle da; /* GtkDrawingArea size */
gdouble dx = 4.0, dy = 4.0; /* Pixels between each point */
gdouble clip_x1 = 0.0, clip_y1 = 0.0, clip_x2 = 0.0, clip_y2 = 0.0;
set_up_grid(widget,cr,&dx,&dy,&clip_y1,&clip_y2,&clip_x1,&clip_x2, &da);
on_draw_grid(cr,dx,clip_y2,clip_x2);
while(rect_list->next)
{
rect_list = rect_list->next;
on_draw_rectangle(cr,rect_list->value->x,rect_list->value->y);
}
gtk_widget_queue_draw_area(widget, 0, 0, da.width, da.height);
return G_SOURCE_REMOVE;
}
为了能够只显示绘图区域的一部分,我实现了滚动功能。它由这个结构和相应的调用函数管理。此功能再次完美运行,我不认为它与问题有任何关系,但我提供它是为了保持一致性。
此结构包含主表面和绘制表面:
typedef struct cairo_surfaces
{
cairo_surface_t *main_surface, *seen_surface;
} cairo_surfaces;
这是在 DrawingArea 发出配置事件时触发的调用函数:
static gboolean on_configure(GtkWidget *widget, __attribute_maybe_unused__ GdkEventConfigure *event_p, gpointer user_data)
{
cairo_t *cr_p;
cairo_surfaces *my_data;
my_data = (cairo_surfaces *)user_data;
if (my_data->seen_surface)
{
cairo_surface_destroy(my_data->seen_surface);
}
my_data->seen_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, SCROLL_WINDOW_HEIGHT, SCROLL_WINDOW_WIDTH);
gtk_widget_set_size_request(widget, FULL_WIDTH, FULL_HEIGHT);
cr_p = cairo_create(my_data->seen_surface);
cairo_set_source_surface(cr_p, my_data->main_surface, 0, 0);
cairo_paint(cr_p);
cairo_destroy(cr_p);
return FALSE;
}
事件
现在让我们谈谈事件管理:所以我简单地添加了我想要获取的事件的 GDK 掩码,这里是在检测到按下事件时添加一个新矩形的函数。
// Gets the current event and adds a rectangle to rect_list
static gboolean
current_key_click(GtkWidget *da,GdkEvent *event, __attribute_maybe_unused__ gpointer user_data)
{
GdkDisplay *display = gdk_display_get_default();
GdkSeat *seat = gdk_display_get_default_seat(display);
GdkDevice *device = gdk_seat_get_pointer(seat);
if (gdk_event_get_event_type(event) == GDK_BUTTON_PRESS)
{
gdk_window_get_device_position(gtk_widget_get_window(GTK_WIDGET(da)), device, &x, &y, NULL);
set_closest_rectangle();
//Create the rectangle if there is no rectangle at this coordinate
rectangle * new_rect = init_null_rectangle();
new_rect->id = id_from_coordinate(x,y);
new_rect->x = x;
new_rect->y = y;
new_rect->width = RECT_WIDTH;
new_rect->height = RECT_HEIGHT;
node_insert_beg(rect_list,new_rect);
}
return G_SOURCE_REMOVE;
}
然而,在链表上循环的绘图函数似乎存在并发内存访问,并且 current_key_click 函数试图添加一个新的链表的元素到链表。为了尝试解决这个问题,我在关键部分添加了 pthread_mutex。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
也就是我插入节点的时候
***
pthread_mutex_lock(&mutex);
node_insert_beg(rect_list,new_rect);
pthread_mutex_unlock(&mutex);
***
而当我 运行 while 循环
while(rect_list->next)
{
pthread_mutex_lock(&mutex);
rect_list = rect_list->next;
pthread_mutex_unlock(&mutex);
on_draw_rectangle(cr,rect_list->value->x,rect_list->value->y);
}
问题
但这并没有解决我的问题,当我尝试打印链表时,它总是只包含一个项目,而其他所有项目都丢失了。这肯定会发生,因为添加和遍历列表是同时发生的。关于如何解决此问题的任何想法?
为了完整起见并为您节省一些时间(如果您希望提供帮助),这里是整个文件。
我正在构建的 .glade:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<object class="GtkWindow" id="window">
<property name="can-focus">False</property>
<property name="default-width">480</property>
<property name="default-height">320</property>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkViewport" id="view_port">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkDrawingArea" id="da">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>
实际main.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <gtk/gtk.h>
#include <pthread.h>
#define SCROLL_WINDOW_HEIGHT 480
#define SCROLL_WINDOW_WIDTH 320
#define NUMBER_OF_KEYS 14
#define MINUTE 60
#define FULL_WIDTH 24000
#define FULL_HEIGHT 1400
#define RECT_HEIGHT FULL_HEIGHT/NUMBER_OF_KEYS
#define RECT_WIDTH FULL_WIDTH/(MINUTE * 20)
/*
Functions and structs for the linked lists
*/
typedef struct rectangle
{
int x;
int y;
int width;
int height;
int id;
}rectangle;
typedef struct node
{
struct rectangle *value;
struct node *next;
} node;
rectangle * init_null_rectangle()
{
rectangle * new_rectangle = malloc(sizeof(rectangle));
new_rectangle->height = 0;
new_rectangle->width = 0;
new_rectangle->x = 0;
new_rectangle->y = 0;
new_rectangle->id = -1;
return new_rectangle;
}
void print_rectangle(rectangle * rect)
{
printf(" Id: %d ---> x: %d, y: %d, width: %d, height: %d\n",rect->id,rect->x,rect->y,rect->width,rect->height);
}
// Verify if node is empty
int node_is_empty(node *head)
{
return head == NULL;
}
// Verify if node is not empty
int node_is_not_empty(node *head)
{
return head != NULL;
}
// Builds the sentinell of the node structure
node *node_build_sentinel()
{
// Creates the sentinel.
node *head = malloc(sizeof(node));
head->value = init_null_rectangle();
head->next = NULL;
// Returns the head of the node which is the sentinell.
return head;
}
// Prints the contents of a node node* node_build_sentinel()
void node_print(node *head)
{
while (head->next)
{
head = head->next;
print_rectangle(head->value);
}
}
// Frees the allocated node
void node_free(node *head)
{
node *previous;
while (head)
{
previous = head;
head = head->next;
free(previous->value);
free(previous);
}
}
// Inserts a value right after the head
/*
HEAD -> 1 -> 2 -> ..... -> 8
node_insert_beg(node* HEAD, int 42);
HEAD -> 42 -> 1 -> 2 -> ..... -> 8
*/
void node_insert_beg(node *head, rectangle *value)
{
node *tmp = malloc(sizeof(node));
tmp->value = value;
tmp->next = head->next;
head->next = tmp;
}
/*
All Other Functions
*/
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int x;
int y;
node * rect_list;
typedef struct cairo_surfaces
{
cairo_surface_t *main_surface, *seen_surface;
} cairo_surfaces;
void set_closest_rectangle()
{
//First check if the square has no rectangle already
int new_x = x%(RECT_WIDTH);
int new_y = y%(RECT_HEIGHT);
x -= new_x;
y -= new_y;
}
int id_from_coordinate(int x,int y)
{
int id = (y/(RECT_HEIGHT))*MINUTE * 20+ x/(RECT_WIDTH);
printf("id: %d\n",id);
return id;
}
// Gets the current event and sets the x,y possition
static gboolean
current_key_click(GtkWidget *da,GdkEvent *event, __attribute_maybe_unused__ gpointer user_data)
{
GdkDisplay *display = gdk_display_get_default();
GdkSeat *seat = gdk_display_get_default_seat(display);
GdkDevice *device = gdk_seat_get_pointer(seat);
if (gdk_event_get_event_type(event) == GDK_BUTTON_PRESS)
{
gdk_window_get_device_position(gtk_widget_get_window(GTK_WIDGET(da)), device, &x, &y, NULL);
set_closest_rectangle();
//Create the rectangle if there is no rectangle at this coordinate
rectangle * new_rect = init_null_rectangle();
new_rect->id = id_from_coordinate(x,y);
new_rect->x = x;
new_rect->y = y;
new_rect->width = RECT_WIDTH;
new_rect->height = RECT_HEIGHT;
pthread_mutex_lock(&mutex);
node_insert_beg(rect_list,new_rect);
pthread_mutex_unlock(&mutex);
}
return G_SOURCE_REMOVE;
}
void on_quit(__attribute_maybe_unused__ GtkWidget *widget, gpointer user_data)
{
cairo_surfaces *my_data;
my_data = (cairo_surfaces *)user_data;
cairo_surface_destroy(my_data->main_surface);
cairo_surface_destroy(my_data->seen_surface);
gtk_main_quit();
}
static gboolean on_configure(GtkWidget *widget, __attribute_maybe_unused__ GdkEventConfigure *event_p, gpointer user_data)
{
cairo_t *cr_p;
cairo_surfaces *my_data;
my_data = (cairo_surfaces *)user_data;
if (my_data->seen_surface)
{
cairo_surface_destroy(my_data->seen_surface);
}
my_data->seen_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, SCROLL_WINDOW_HEIGHT, SCROLL_WINDOW_WIDTH);
gtk_widget_set_size_request(widget, FULL_WIDTH, FULL_HEIGHT);
cr_p = cairo_create(my_data->seen_surface);
cairo_set_source_surface(cr_p, my_data->main_surface, 0, 0);
cairo_paint(cr_p);
cairo_destroy(cr_p);
return FALSE;
}
//Sets up the drawing space to dynamically manage the resize
void set_up_grid(GtkWidget *widget, cairo_t *cr,double *dx,double *dy,double *clip_y1, double *clip_y2,double *clip_x1, double *clip_x2,GdkRectangle * da )
{
GdkWindow *window = gtk_widget_get_window(widget);
/* Determine GtkDrawingArea dimensions */
gdk_window_get_geometry(window, &da->x, &da->y, &da->width, &da->height);
/* Draw on a black background */
cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
cairo_paint(cr);
/* Determine the data points to calculate (ie. those in the clipping zone */
cairo_device_to_user_distance(cr, dx, dy);
cairo_clip_extents(cr, clip_x1, clip_y1, clip_x2, clip_y2);
}
//Draws the grid on which the rectangles are placed
void on_draw_grid(cairo_t *cr, double dx, double clip_y2, double clip_x2)
{
//Horizontal lines
for (size_t i = 0; i < FULL_WIDTH; i += (FULL_WIDTH / MINUTE))
{
cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
cairo_move_to(cr, i, 0.0);
cairo_line_to(cr, i, clip_y2);
cairo_stroke(cr);
cairo_set_line_width(cr, dx / 4);
cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
for (size_t j = 0; j < (FULL_WIDTH / MINUTE); j += (FULL_WIDTH / (MINUTE * 20)))
{
cairo_move_to(cr, j + i, 0.0);
cairo_line_to(cr, j + i, clip_y2);
}
cairo_stroke(cr);
}
// vertical lines
for (size_t i = 0; i < FULL_HEIGHT; i += FULL_HEIGHT / NUMBER_OF_KEYS)
{
cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
cairo_move_to(cr, 0.0, i);
cairo_line_to(cr, clip_x2, i);
cairo_stroke(cr);
}
}
//Draws one rectangle on the cr, with left top most corner (x,y)
void on_draw_rectangle(cairo_t *cr,int x, int y)
{
cairo_move_to(cr,x,y);
cairo_line_to(cr,x,y + RECT_HEIGHT);
cairo_line_to(cr,x + RECT_WIDTH,y + RECT_HEIGHT);
cairo_line_to(cr,x + RECT_WIDTH,y);
cairo_line_to(cr,x,y);
cairo_set_source_rgb(cr, 0.2, 0.6, 0.1);
cairo_fill(cr);
}
static gboolean on_draw(GtkWidget *widget, cairo_t *cr, __attribute_maybe_unused__ gpointer user_data)
{
GdkRectangle da; /* GtkDrawingArea size */
gdouble dx = 4.0, dy = 4.0; /* Pixels between each point */
gdouble clip_x1 = 0.0, clip_y1 = 0.0, clip_x2 = 0.0, clip_y2 = 0.0;
set_up_grid(widget,cr,&dx,&dy,&clip_y1,&clip_y2,&clip_x1,&clip_x2, &da);
on_draw_grid(cr,dx,clip_y2,clip_x2);
while(rect_list->next)
{
pthread_mutex_lock(&mutex);
rect_list = rect_list->next;
pthread_mutex_unlock(&mutex);
on_draw_rectangle(cr,rect_list->value->x,rect_list->value->y);
}
gtk_widget_queue_draw_area(widget, 0, 0, da.width, da.height);
return G_SOURCE_REMOVE;
}
int main()
{
gtk_init(NULL, NULL);
cairo_surfaces my_data;
rect_list = node_build_sentinel();
my_data.seen_surface = NULL;
my_data.main_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, FULL_WIDTH, FULL_HEIGHT);
GtkBuilder *builder = gtk_builder_new();
GError *error = NULL;
if (gtk_builder_add_from_file(builder, "file.glade", &error) == 0)
{
g_printerr("Error loading file: %s\n", error->message);
g_clear_error(&error);
return 1;
}
GtkWindow *window = GTK_WINDOW(gtk_builder_get_object(builder, "window"));
GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW(gtk_builder_get_object(builder, "scrolled_window"));
GtkDrawingArea *da = GTK_DRAWING_AREA(gtk_builder_get_object(builder, "da"));
gtk_widget_set_size_request(GTK_WIDGET(scrolled_window), SCROLL_WINDOW_HEIGHT, SCROLL_WINDOW_WIDTH);
gtk_widget_set_events(GTK_WIDGET(da), gtk_widget_get_events(GTK_WIDGET(da)) | GDK_SCROLL_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK );
g_signal_connect(G_OBJECT(da), "configure-event", G_CALLBACK(on_configure), &my_data);
g_signal_connect(G_OBJECT(da), "draw", G_CALLBACK(on_draw), NULL);
g_signal_connect(G_OBJECT(da), "event", G_CALLBACK(current_key_click), NULL);
g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(on_quit), &my_data);
gtk_widget_show_all(GTK_WIDGET(window));
gtk_main();
node_print(rect_list);
node_free(rect_list);
return 0;
}
这是我正在编译的内容:
gcc -Wall -Wextra `pkg-config --cflags gtk+-3.0` -g -fsanitize=address main.c -o visualiser `pkg-config --libs gtk+-3.0` -lm -pthread
在此先感谢您的帮助!
我将您的代码复制到我的沙箱中并测试了您的程序。在你的程序中散布了一堆“printf”语句后,我相信我找到了问题所在。您通过哨兵函数设置了“rect_list”节点,最初它持有指向您的第一个结构元素的指针。但是,当单击矩形时,该指针中的值将设置为已创建的新节点结构,并且其“下一个节点”指针始终为 NULL,就像最新条目的情况一样。因此,最后当程序尝试打印出记录列表时,它会引用最后创建的节点条目并结束。此外,none 分配的元素被释放,因此内存泄漏错误也会打印出来。
为了解决这个问题,我只是添加了第二个节点结构指针来保存调用哨兵函数时的初始节点指针(我称之为“starting_point”)。
node * rect_list, *starting_point;
然后在main函数往下,调用sentinel函数后,将初始节点指针保存在这个指针副本中,以备后用。
rect_list = node_build_sentinel();
starting_point = rect_list; /* Save the initial pointer */
然后在终结函数中,起点指针作为节点指针引用。
node_print(starting_point);
node_free(starting_point);
return 0;
这导致生成了一个元素列表并消除了内存泄漏警告消息。
希望对您有所帮助。
此致。