как мне инициализировать Guchar Gtk3

Я пытаюсь отобразить изображение в окне Gtk. У меня есть изображение, хранящееся в памяти как std::string, и я пытаюсь его отобразить, но не могу получить изображение в GdkPixbuf*. Это моя функция, которая получает данные изображения, и я знаю, что она работает, потому что, если я запишу данные в файл, я смогу его открыть.

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;
}

это основной цикл, в котором вызывается get FileInMem(). Я получил данные в guchar* и распечатал их, но как только я это сделаю, я не смогу писать какие-либо другие инструкции, или я получаю ошибку дампа ядра Любой способ отобразить изображение в окне без записи в диск был бы отличным

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

    
    string data1 = getFileInMem("0.0.0.0:8000/test.txt");
    ofstream f("test.jpg");//  image file
    f << data;//  writing image data the file
    f.close();//  closing the file





    GdkPixbufLoader* loader = gdk_pixbuf_loader_new(); // creating a pixbuf loader
    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*
    gdk_pixbuf_loader_write(loader,pixdata,sizeof(pixdata),nullptr);// trying to write the data to the loader
    GdkPixbuf* imagedata = gdk_pixbuf_loader_get_pixbuf(loader);// creating the pixbuf*
    GtkWidget *image = gtk_image_new_from_pixbuf(imagedata);
    delete pixdata;// deleting pixdata once done with it


    //  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;
}

person Ican'tThinkOfAName    schedule 27.04.2021    source источник
comment
Я совершенно уверен, что strcpy() - очень плохой выбор для копирования двоичных данных (для изображения). Это для копирования строк с нулевым завершением. В любом месте двоичного изображения могут быть нули. Вместо этого вы должны использовать memcpy(). Вы даже можете передать необработанный указатель данных в std::string с помощью std::string::c_str() или std::string::data(). (Конечно, вы должны преобразовать в (const guchar*).) Если вы сделаете это, вы должны позаботиться о том, чтобы GTK+ не захотел взять на себя ответственность за данные (но тогда new[], вероятно, также будет неправильным), и вы должны обеспечить правильное срок службы для вашего std::string.   -  person Scheff's Cat    schedule 27.04.2021
comment
Примечание. Ваш код не будет работать в Windows. Двоичные данные следует читать с флагом std::ios::binary в потоке. Вы находитесь в Linux, где это не имеет никакого эффекта, но в Windows отсутствие двоичного флага приводит к внутренней обработке окончаний строк в потоке, что неверно для двоичных данных (где 13 и 10 - это просто двоичные значения без шансов чтобы узнать, кодируют ли они окончания строк или что-то еще).   -  person Scheff's Cat    schedule 27.04.2021
comment
Кстати. пожалуйста, delete[] что вы создали с помощью new[], и delete что вы создали с помощью new. Сочетание guchar* pixdata = new guchar[data.size()+1]; с delete pixdata; — это неопределенное поведение. (Кстати, увидев это, мои опасения по поводу владения и срока службы устарели.) Вам действительно следует выбросить свои new и delete и просто вызвать gdk_pixbuf_loader_write(loader, (const guchar*)data.data(), data.size(), nullptr); (при условии, что data имеет тип std::string). ... И не мешало бы проверить возвращаемое значение gdk_pixbuf_loader_write().   -  person Scheff's Cat    schedule 27.04.2021
comment
@Scheff Спасибо за все советы, которые я действительно ценю. ваше решение приведения к const guchar* с std::string.data() вместо std::string.c_str() устранило проблему! новый и удаленный были удалены, но я все еще буду помнить ваш совет о различиях между new ; new[]; delete и delete[] тоже буду реализовывать std::ios::binary. Я хотел бы отметить решение, если вы хотите написать решение, я отмечу его как решение, однако, если вы не хотите, будет ли нормально, если я напишу решение и отмечу его?   -  person Ican'tThinkOfAName    schedule 27.04.2021
comment
На самом деле, некоторое время назад я разрабатывал с помощью GTK+. Следовательно, у меня нет ничего под рукой, чтобы сделать MCVE. Приветствуется самостоятельный ответ.   -  person Scheff's Cat    schedule 27.04.2021


Ответы (2)


Я должен признать, что я не эксперт по GDK, но я использовал C в течение многих лет, и я использую C++ в течение многих лет, и кстати. Я программировал с помощью gtkmm (привязка C++ для GTK+) несколько лет назад. Итак, я чувствую, что могу разобраться в том, что может показаться запутанным для ОП.

C струны

В прошлом к ​​обработке строк применялись различные подходы.

Например. в PASCAL строка всегда представляла собой массив из 256 байтов. Первый байт был зарезервирован для длины строки, соответственно. количество других байтов, содержащих символы. Это безопасное решение, но оно имеет недостатки: даже самая короткая строка всегда занимает 256 байт. Хуже того, строка длиной более 255 символов невозможна. В этом случае возможны обходные пути для создания списка строк, но это на самом деле раздражает.

В C это было решено по-другому. Строки могут иметь произвольную длину (при условии, что они помещаются в память компьютера). Сама длина не сохраняется. Вместо этого конец строки отмечается специальным символом '\0' — байтом со значением 0 — который зарезервирован исключительно для этой цели. Недостаток: длина строки должна храниться отдельно или определяться путем подсчета символов до первого вхождения '\0'. Стандартная библиотека C предоставляет для этого готовую функцию: strlen() . Это позволяет обрабатывать строки по адресу их первого символа. Следовательно, строка C обрабатывается char* (или const char*, если строка C не может быть изменена).

Библиотека C предоставляет некоторые дополнительные функции для поддержки работы со строками C, например, strcpy(). strcpy() копирует последовательные байты из указателя источника (2-й аргумент) в указатель назначения (1-й аргумент), пока не встретится байт '\0'. (Он также копируется, но после этого функция завершается.)

Двоичные данные

Двоичные данные (состоящие из байтов с произвольными значениями) можно обрабатывать так же, как строки C. char — целочисленный тип размером 1 байт. Следовательно, это также подходящий кандидат. Однако двоичные данные могут содержать любое из возможных значений 0 … 255 в любом месте. Таким образом, принцип отмечать конец с помощью '\0' не работает. Вместо этого длина всегда должна храниться отдельно.

Для обработки двоичных данных часто предпочтительнее использовать unsigned char. ИМХО, на это есть две существенные причины:

  1. Это помогает программисту отличать строки C от указателей на произвольные двоичные данные.
  2. char может быть (по стандарту C, а также по стандарту C++) подписанным или неподписанным (в зависимости от решения поставщика компилятора). Если проблема заключается в подписании значения char, вместо него следует использовать signed char или unsigned char. Для обработки байтов двоичных данных часто удобнее обрабатывать их явно unsigned.

Стандартная библиотека C предоставляет соотв. функции для обработки двоичных данных, например, например. memcpy(). Обратите внимание, что memcpy() предоставляет третий параметр для определения размера байтов для копирования из исходного указателя в целевой.

Хранилище

Помимо преимуществ строк C, они сопряжены с определенным бременем: программист несет ответственность за обеспечение всегда достаточного объема памяти. В C есть несколько возможностей:

  • используя глобальную переменную с массивом char, например. static char data[1024];
  • используя локальную переменную с массивом char (в функции), например. char data[1024];
  • выделение памяти в куче, например. char *data = malloc(1024);.

Размер символьных массивов должен быть определен в программе (во время компиляции). Невозможно изменить это во время выполнения программы. (Исключением являются массивы переменной длины. Согласно стандарту C99, они являются необязательными. функция, но такой вещи нет даже в последних стандартах C++, хотя некоторые компиляторы C++ предоставляют их как проприетарное расширение.) Если размер хранилища неизвестен до выполнения, единственным решением является выделение динамической памяти (т.е. size_t n = somehowDetermined(); char *data = malloc(n);).

Управление достаточным объемом памяти на самом деле звучит не так уж и сложно, но правильная и всегда правильная организация этого показала одну из основных проблем в программах на C и C++ на протяжении многих лет. (C++ унаследовал эту проблему от C. Были добавлены операторы new и delete, чтобы обеспечить безопасное размещение памяти в куче, но на самом деле это мало помогло.) годы.

станд::строка

В 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(). Он предоставляет необработанное содержимое std::string в виде строки C. Он допускает, что возвращаемая строка 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, (const guchar*)data.data(), data.size(), nullptr);

Для последней строки я мог бы также использовать:

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

Как я уже объяснил, это не имеет значения, начиная с С++ 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: Могу ли я превратить неподписанный char в char и наоборот?.

ОП пытается исправить код

После того, как я провел несколько подсказок, ОП предложил следующее исправление:

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, и в коде не появляется Undefined Behavior.

Однако sizeof pixdata возвращает не размер data, а только размер указателя guchar*. Вероятно, это 4, если OP скомпилирован на 32-битной платформе, и 8 для 64-битной платформы, но это всегда одно и то же значение для определенной платформы.

Итак, чтобы исправить это, это должно быть:

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, pixdata, data.size(), nullptr);

or

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, pixdata, pixdatasize, nullptr);

Это стало длинным ответом. Это может служить иллюстрацией того, что даже некоторые строки кода на C++ требуют больших базовых знаний для их правильного написания. Следовательно, вполне логично, что программистам начального уровня часто советуют получить хорошую книгу по C++. Я бы не стал настаивать на том, что невозможно выучить C++ другим способом. Тем не менее, хорошая книга по С++, ИМХО, заслуживает внимания. В C++ много подводных камней, большинство из них унаследовано от C, а некоторые специально введены в самом C++.

person Scheff's Cat    schedule 29.04.2021

простое приведение данных как (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;
person Ican'tThinkOfAName    schedule 28.04.2021
comment
На самом деле нет никакой разницы, используете ли вы std::string::data() или std::string::c_str(). Завершающий 0 всегда находится во внутреннем буфере std::string. Ваша проблема исходного вопроса заключалась в strcpy(). Он предназначен для копирования строки C, заканчивающейся 0. Вы храните не строку C в своем data, а двоичные данные, где 0 могут отклоняться где угодно. Следовательно, strcpy() может закончиться слишком рано. Вместо этого вы могли бы использовать memcpy(), который имеет параметр длины и не учитывает какие-либо терминаторы в скопированных данных. - person Scheff's Cat; 29.04.2021
comment
Однако на самом деле все копирование совершенно не нужно, так как вы можете передать данные напрямую в gdk_pixbuf_loader_write() (как я уже упоминал). И: gdk_pixbuf_loader_write(loader, data.c_str(), data.size(), nullptr); тоже будет работать. НО: guchar* pixdata = (const guchar*)data.data(); gdk_pixbuf_loader_write(loader,pixdata,sizeof(pixdata),nullptr); НЕПРАВИЛЬНО. pixdata относится к типу guchar*. sizeof pixdata равно 4 или 8 - всегда. Это размер этого указателя, и будет ли он равен 4 или 8, зависит от того, компилируете ли вы для x86 или x64. - person Scheff's Cat; 29.04.2021
comment
Я оглянулся назад, чтобы потратить свой голос на всякий случай. Но так как я не могу. Пожалуйста, отредактируйте свой ответ. - person Scheff's Cat; 29.04.2021