24 June 2011

Тайна пропавшего цветка

Хочу рассказать о паре проблем, которые приходилось решать во время аврала по нашему проекту.

С Qt мы работаем довольно давно, с 2007-го года, но использовали мы его, в основном, на уровне QtCore/QtNetwork как кросс-платформенное решение для таких вещей как многопоточность, файлы или сокеты. В новом направлении проекта пошел сильный крен в сторону GUI, поэтому мы стали более плотно общаться с этой частью библиотеки. Одна из решаемых задач -- UI, выглядящий не как унылая морда среднестатистической программы для ПК; мы начали двигаться в сторону виджетов, в которых прописан наш код рендринга. В связи с этим, полезли смотреть демки, касающиеся работы с QPainter, в том числе, и демку Composition, в которой нас интересовала работа с альфа каналом.



Запустив демку, я с удивлением обнаружил, что в ней отсутствует изображение цветка.


Оглядевшись по сторонам, и убедившись, что jpeg с цветочком вроде как должен валяться в виде ресурса в самом exe файле, я недоуменно пожал плечами, посмотрел часть кода, касающуюся прозрачности, и пошел дальше по своим делам.

Однако с проблемой этой мне пришлось снова столкнуться через несколько дней, и уже в ситуации, когда забить болт на нее уже никак не получалось. Я писал код для работы с записной книжкой, где у абонента, помимо всего прочего, была еще и ассоциированная с ним картинка. Картинку я пытался грузить из jpeg файла, и получал почему-то при этом жестокий облом -- изображение не грузилось и рапортовало, что оно isNull, причем диагностики проблемы при работе с QImage нет вообще никакой. Переписав код на использование QImageReader, я получил более детальную информацию об отказе -- не поддерживаемый формат изображения. WTF?

Вспомнив о проблеме демки Composition, я понял почему на ней не было цветка -- ситуация выходила аналогичная! Так как с Qt мы работаем не первый день, на винте нашлась версия 4.6 и даже старая, как гавно мамонта, версия 4.3. Демка из 4.6 показала ту же картинку, что и 4.7, а вот самый старый код, из 4.3, волшебный образом показал цветок (при этом подгрузив dll от версии 4.7, которая была прописана в PATH!). Первое предположение, возникшее в голове -- Qt 4.6 и 4.7 мы каким-то хреном умудрились собрать без опции, отвечающей за поддержку jpeg. Маловероятно, с учетом того, что вряд ли кто-то будет в трезвом уме и здравой памяти отключать этот формат в настройках сборки по умолчанию... Полез в интернет, пара минут гугления показала, что jpeg поддерживается через систему плагинов, путь к которым надо выставить через переменную окружения QT_PLUGIN_PATH. Еще через минуту цветок появился во всех демках -- уря!

Вернулся к коду записной книжки... и получил тот же самый отлуп, картинка отказывала грузиться. В списке поддерживаемых форматов (QImageReader::supportedImageFormats) jpeg так и не появился. Меня вся эта история начинала потихоньку напрягать. Копания в гугле и попытки найти хоть какие-то инструменты по диагностированию проблемы загрузки плагинов в Qt ничего не дали. Осенило меня только через час приседаний с бубном.

SxS.
Давно хотел написать в блоге заметку на тему этой чудесной технологии, придуманной Microsoft'ом для решения одного из аспектов проблемы Dll Hell, да все руки не доходили.

Гугловскому поиску по картинкам "SxS" видится так

Если вкратце, то суть этого дела такая -- dll теперь различаются не только по имени, но и по версии. Начиная с WinXP в системе может быть зарегистрировано множество версий одной и той же библиотеки, что очень напоминает поведение .NET сборок. В программу, использующую SxS технологию, на этапе билда зашивается специальный манифест, в котором указывается версия библиотеки, с которой эта программа желает работать, а если чего не так, то exe'шник просто не запускается.

Знакомились мы с этим чудом инженерной мысли в нашем проекте постепенно.

Сначала возникли серьезные проблемы с деплоем. CRT dll подхватывались строго из SxS кэша. Как в нем (без нереальных извращений) зарегистрировать что-то свое мы не понимали, и все, что нам оставалась, это делать сборки только на основе релизных билдов плюс вкладывать в них волшебный файлик vcredist_x86.exe, который ставил на пользовательскую машину release CRT dll в SxS кэш.

Потом возникла еще одна проблема. Все библиотеки (Qt, boost, etc.) в целях экономии времени мы обычно собираем на одной машине, а потом просто копируем двоичные файлы на остальные ПК разработчиков. С какого-то момента готовые двоичные сборки, собранные на этой машине, перестали работать на других. Стали разбираться, и оказалось, что на этой машине был, блять, включен Windows Update, который молча поставил в SxS более свежую версию CRT, и с участием именно этой версии начали собираться все бинарики и библиотеки, которые получались совершенно не пригодными для использования на других машинах (кстати, диагностировать проблемы такого рода можно только через системные логи).

Стали чесать репу, что с этой новой бедой делать.
Во-первых, написали патчилку, которая в бинарных файлах тупо перебивает версию SxS библиотеки (вся эта информация хранится в специальном манифесте в xml формате).
Во-вторых, нашли волшебные слова для компилятора, которые принуждают его использовать библиотеки нужной версии (_SXS_ASSEMBLY_VERSION и другие).
Жизнь стала налаживаться, тем более, что мы наконец-то поняли, что программа, на самом деле, может подхватывать версионные dll не только из SxS кэша, если эти самые dll ложить в соответствующие папочки рядом с исполняемым файлом. Пакеты стали собираться и для debug билдов...

Теперь возвращаемся к плагинам Qt... Плагин это обычная dll. Релиз 4.7 собирался на другой машине, и плагины не были, в свое время, пропатчены, т.к. мы ими просто не пользовались. Загрузка dll обламывалась -- по этой причине в программе так и не появлялась поддержка jpeg формата.
Такие вот пироги.

Мораль? На самом деле, мораль из всей этой истории простая -- мужики, обрабатывайте как следует ошибки!
Composition должен был падать с сообщением "не могу загрузить flower.jpg -- не поддерживаемый формат файла; возможно у вас проблема с плагинами", а не продолжать работу и притворяться, что все хорошо (подробнее о теме обработки ошибок смотри тут и тут). Тоже самое касается и отказа при попытке загрузить dll...
Qt, кстати, по соображениям чисто религиозного характера, не использует исключения. А зря. Ежу понятно, что любая приличная библиотека при ошибке загрузки файла будет что есть мочи вопить об этом, а не дожидаться, пока юзер проверит Null или не Null картинка после операции загрузки.

зы. По работе проводили ряд бенчмарков. Скорость софтового растеризатора Qt под Windows удручает. На более медленном железе под Linux скорость работы почему-то раза в четыре выше.
Плюс очень странно работает поддержка OpenGL -- на полудохлой рабочей машине картинка крутится со скоростью пара fps, зато программа почти не жрет CPU. На домашней -- хороший fps, но одна ядро загружено почти полностью, это при том, что софтовый рендринг жрет раз в десять меньше...
В общем, я бы сильно подумал перед тем, как писать что-то кросс-платформенное на Qt требующее быстрого вывода графики.

зы2. Натрахались с COM портами. В Linux полный бардак с именами -- на одних машинах все работает через "/dev/ttyS", на других только через "/dev/ttySAC".

Отдельная большая беда -- тайминги.
В Linux выставляется все это через жопу и работает с кучей оговорок.
Дополнительную свинью нам подложила используемая либа, в которой параметры тайминга описаны так, что без 100 грамм (читай -- копания в сырцах) ни черта не поймешь.
Смотрели в сторону boost ASIO, но там свои приколы, а интерфейса для управления таймингами нет вообще.

Короче вы поняли, нет в этом мире счастья.

зы3. Да, работа программистов сродни работе следователя. Поиск бага или причин той или иной проблемы тот еще детектив или авантюрное приключение. Скандалы, интриги, расследования...

No comments:

Post a Comment