我如何初始化 Guchar Gtk3

问题描述

我正在尝试在 Gtk 窗口中显示图像我将图像作为 std::string 存储在内存中,我正在尝试显示它,但我似乎无法将图像放入 {{1} }. 这是我获取图像数据的函数,我知道它有效,因为如果我将数据写入文件,我可以打开它

GdkPixbuf*

这是调用 get string getFileInMem(string url){ curlpp::Easy handle; std::ostream test(nullptr); std::stringbuf str; test.rdbuf(&str); char* error[CURL_ERROR_SIZE]; handle.setopt(curlpp::Options::Url(url)); handle.setopt(curlpp::options::FollowLocation(true)); handle.setopt(curlpp::options::WriteStream(&test)); handle.setopt(curlpp::options::ErrorBuffer(*error)); //cout << error << endl; handle.perform(); string tmp = str.str(); return tmp; } 的主循环。我已经将数据输入 guchar* 并打印出来,但是一旦我这样做了,我就无法编写任何其他指令,或者出现核心转储错误 无需写入即可在窗口中显示图像的任何方法磁盘会很棒

FileInMem()

解决方法

只需将数据转换为 (const guchar*) 即可

int main(int argc,char *argv[]){

string data = getFileInMem("0.0.0.0:8000/test.txt");


GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader,(const guchar*)data.data(),data.size(),nullptr);
GdkPixbuf* imagedata = gdk_pixbuf_loader_get_pixbuf(loader);
GtkWidget *image = gtk_image_new_from_pixbuf(imagedata);

//  creating the window with the image
GtkWidget *window;
GtkWidget *button;
gtk_init (&argc,&argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_container_add(GTK_CONTAINER(window),image);
gtk_widget_show_all(window);
gtk_main ();
return 0;
,

我必须承认,我不是 GDK 方面的专家,但我已经使用 C 多年,而且我使用 C++ 多年,顺便说一句。几年前,我已经使用 gtkmm(GTK+ 的 C++ 绑定)进行了编程。所以,我觉得能够理清可能会让 OP 感到困惑的内容。

C 字符串

过去处理字符串是一些不同方法的主题。

例如在 PASCAL 中,字符串总是一个 256 字节的数组。第一个字节是为字符串的长度保留的,resp。包含字符的其他字节数。这是一个安全的解决方案,但它有缺点:即使是最短的字符串也总是消耗 256 字节。更糟糕的是,超过 255 个字符的字符串是不可能的。在这种情况下,可以通过一些变通方法来制作字符串列表,但这实际上很烦人。

在 C 中,它以不同的方式解决。字符串可以有任意长度(只要它们适合计算机的内存)。长度本身不存储。取而代之的是,字符串的结尾用一个特殊字符 '\0' 进行标记——一个值为 0 的字节——专门为此目的保留。缺点是:字符串的长度必须单独存储,或者必须确定计数到'\0'第一次出现的字符数。 C 标准库为此提供了一个现成的函数:strlen()。这使得可以通过字符串的第一个字符的地址来处理字符串。因此,C 字符串由 char* 处理(如果 C 字符串不可修改,则为 const char*)。

C 库提供了一些额外的函数来支持 C 字符串的工作,例如strcpy()strcpy() 将连续字节从源指针(第二个参数)复制到目标指针(第一个参数),直到出现 '\0' 字节。 (它也被复制,但函数随后结束。)

二进制数据

二进制数据(由具有任意值的字节组成)可以像 C 字符串一样处理。 char 是一个整数类型,大小为 1 个字节。因此,它也是合适的候选人。但是,二进制数据可能在任何位置包含任何可能的值 0 … 255。因此,用 '\0' 标记结尾的原则不起作用。相反,长度必须始终单独存储。

对于二进制数据的处理,通常首选 unsigned char。恕我直言,这有两个重要原因:

  1. 它帮助程序员区分 C 字符串和指向任意二进制数据的指针。
  2. char 可能(根据 C 标准以及 C++ 标准)有符号或无符号(取决于编译器供应商的决定)。如果 char 值的签名有问题,则必须改用 signed charunsigned char。对于处理二进制数据的字节,显式处理它们通常更方便unsigned

标准 C 库提供了相应的功能。用于处理二进制数据的函数,例如memcpy()。请注意,memcpy() 提供了第三个参数来定义要从源指针复制到目标指针的字节大小。

存储

除了 C 字符串的优点之外,它们还带来了负担:程序员负责提供始终足够的存储空间。在 C 中,有多种可能性:

  • 使用带有 char 数组的全局变量,例如static char data[1024];
  • 使用带有 char 数组的局部变量(在函数中)例如char data[1024];
  • 在堆上分配内存,例如char *data = malloc(1024);

字符数组的大小必须在程序中定义(在编译时)。无法在程序运行时更改此设置。 (一个例外是 Variable Length Arrays。根据 C99 标准,它们是一个可选特性,但即使在最近的 C++ 标准中也没有这样的东西,尽管一些 C++ 编译器将它们作为专有扩展提供。) 如果在运行之前不知道存储的大小,则分配动态内存是唯一的解决方案(即 size_t n = somehowDetermined(); char *data = malloc(n);)。

管理足够的存储听起来实际上并没有那么复杂,但正确地组织它并始终正确地显示为多年来 C 和 C++ 程序中的基本问题之一。 (C++ 从 C 继承了这个问题。添加了一个 new 运算符和 delete 运算符以允许在堆上进行类型安全的分配,但实际上这并没有多大帮助。)因此,C++ 标准委员会有多年来,在更安全的替代品上投入了大量资金。

标准::字符串

在 C++ 中,字符串可能存储为 std::string。它使字符串的生活变得更加轻松。例如。虽然 C 字符串必须与 strcmp() 或类似的东西进行比较,但 C++ std::string 提供了一个重载的 operator==(),它允许直观的代码,例如std::string text = input(); if (text == "exit") exit();std::string 的另一个重要优点是内部内存管理。字符串可以添加到字符串中,插入到字符串中等等,std::string 会关心内部存储的正确分配。

此外,std::string 在内部存储其内容的大小。 (作者可能发现将额外的字节用于另一个整数是值得的,这样就不必为任何字符串长度的检索计算字符数。)这也使得 std::string 成为二进制数据的足够容器。

为了与 C API 兼容,std::string 提供了一个“后门”std::string::c_str()。它以 C 字符串的形式提供 std::string 的原始内容。它允许返回的 C 字符串在最后一个字符后有一个 '\0' 字节,即 std::string::c_str()[std::string::size()] 必须返回 '\0'。还有一个 std::string::data() 函数可以访问 std::string 的原始数据。在 C++11 之前,只有 std::string::c_str() 必须授予终止 0 而不是 std::string::data()。在 C++11 中,这被改变了。现在,std::string::data()std::string::c_str() 的返回值应该没有任何区别——这两个函数都只是返回指向内部存储的原始数据的指针。因此,无论内容如何,​​std::string 都必须在末尾有效地放置一个 '\0' 字符。这可能看起来是一种浪费,但实际上,我们谈论的是一个额外的字节,这是一个很小的代价,却能带来代码健壮性的巨大优势。

操作代码

考虑到OP要从内存中加载图像文件(通常由任意字节组成),以下代码是错误的:

std::string data;
// image file somehow read in
guchar* pixdata = new guchar[data.size()+1];// creating a guchar* with space for image data
strcpy((char*)pixdata,data.c_str());// copying data from string to the guchar*

对于任意二进制数据,strcpy() 是错误的。它一直复制直到找到第一个 0 字节。图像数据中的任何地方都可能有 0 个字节(例如,如果它包含黑色像素)。因此,strcpy() 复制的字节很可能太少。在这种情况下,memcpy() 将是更好的选择。

实际上,两者都不是必需的。

std::string data; 已经包含了所有必须输入到 gdk_pixbuf_loader_write() 中的内容、指向原始数据的指针和大小。 因此,我建议完全删除 new[]delete 内容并用以下内容替换它:

std::string data;
// image file somehow read in
GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader,nullptr);

对于最后一行,我也可以使用:

gdk_pixbuf_loader_write(loader,(const guchar*)data.c_str(),nullptr);

正如我已经解释过的,自 C++11 以来,这并没有什么不同。我使用 data.data() 只是因为它看起来更好(考虑到 std::string 的内容是二进制数据而不是 C 字符串)。

关于const guchar*演员表的说明:

std::string 在内部存储动态分配的 char 数组。因此,std::string::data() 返回 const char*(或 char*)。 gdk_pixbuf_loader_write() 需要 const guchar* 作为第二个参数。

guchar 只是一个

typedef unsigned char   guchar;

因此,const char* 被转换为 const unsigned char*。指针类型转换是应该小心完成的事情。 (一般来说,它们是某些可能被设计破坏并带有 Undefined Behavior 危险的最后手段 - 每个 C 和 C++ 程序员的瘟疫。)在这种情况下,转换是安全的,并且是合法的根据 C++ 标准。我找到了另一个详细解释了这一点的答案:SO: Can I turn unsigned char into char and vice versa?

OP 尝试修复代码

在我花了一些提示后,OP 建议进行以下修复:

string data = getFileInMem("0.0.0.0:8000/test.txt");
guchar* pixdata = (const guchar*)data.data();

GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader,pixdata,sizeof(pixdata),nullptr);

不幸的是,此解决方案引入了一个新错误:sizeof(pixdata)。 虽然 data.size() 返回 data 中字符串长度的大小,但 sizeof(pixdata) 运算符在这里是错误的选择。

sizeof 是一个操作符,它总是在编译时解析——在它的右侧返回类型的大小。可以使用类型或表达式调用它:

std::cout << sizeof (char) << std::endl;
char c;
std::cout << sizeof c << std::endl;

将输出:

1
1

因此,表达式甚至不需要在运行时具有有效的存储空间,因为 sizeof 始终在编译时根据结果表达式的类型进行解析:

char *c = nullptr;
std::cout << sizeof *c << std::endl;

将输出:

1

这可能令人惊讶,因为 *c 看起来像是访问空指针的内容(通常是未定义行为)。在这种情况下,实际上并非如此。由于 sizeof 运算符在编译时评估类型,生成的代码仅包含此评估的结果。因此,在运行时不会发生 *c,代码中也不会出现未定义的行为。

然而,sizeof pixdata 不返回 data 的大小,而只返回指针 guchar* 的大小。如果 OP 在 32 位平台上编译,则可能是 4,在 64 位平台上编译时可能是 8 — 但对于某个平台,它总是相同的值。

所以,要解决这个问题,它必须是:

string data = getFileInMem("0.0.0.0:8000/test.txt");
const guchar* pixdata = (const guchar*)data.data();

GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader,nullptr);

string data = getFileInMem("0.0.0.0:8000/test.txt");
const guchar* pixdata = (const guchar*)data.data();
gsize pixdatasize = (gsize)data.size();
GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader,pixdatasize,nullptr);

这变成了一个很长的答案。它可能说明即使是某些 C++ 代码行也需要大量的背景知识才能正确编写它们。因此,入门级程序员经常被暗示获得 good C++ book 是有道理的。我不会坚持认为不可能以另一种方式学习 C++。然而,恕我直言,一本好的 C++ 书值得考虑。 C++ 有很多陷阱,其中大部分是从 C 继承而来的,还有一些是在 C++ 本身中专门引入的。