Сообщений 2 Оценка 0 Оценить |
Исходный код класса и пример – 1,6 Кб
Как известно (см. статьи в RSDN Magazine №1 и на сайте RSDN), графическая подсистема GDI+ реализована в виде библиотеки динамической компоновки GdiPlus.DLL. Все приводимые в MSDN примеры ориентированы на статическую компоновку с библиотекой импорта Gdiplus.Lib. При использовании GDI+ возник довольно интересный вопрос: а что, если система, в которой запущено приложение, не поддерживает GDI+? Как в таком случае можно избежать системного сообщения об ошибке и продолжить работу с использованием GDI? Ведь, в отличие от большинства обычных библиотек импорта, "начинка" GDI+ спрятана за структурой классов-оберток…
Здесь может помочь такая сравнительно малоизвестная возможность Visual C++, как отложенная загрузка (Delayed Loading) DLL. При использовании этой опции компоновщик генерирует специальные заглушки для всех импортируемых функций. Приложение стартует немного быстрее, а реальная загрузка библиотеки откладывается (отсюда и название) до первого вызова любой импортируемой функции. Подробно почитать об этой технике можно в декабрьском выпуске MSJ за 1998 год сразу в двух колонках: "Under The Hood" Мэтта Питрека и "QnA Win32" Джефри Рихтера.
Здесь же только упомяну, что для использования отложенной загрузки в проект необходимо включать библиотеку DELAYIMP.LIB.
Скрыть детали использования DLL и инициализации GDI+ можно, используя примерно такой класс:
// InitGdiPlus.h namespace RSDN { class InitGdiPlus { public: InitGdiPlus(); virtual ~InitGdiPlus(); bool Good(){ return present; } // Была ли инициализация удачной? private: bool present; ULONG_PTR token; }; } |
При рассмотрении реализации данного класса учтем следующее:
Во-первых, при попытке использовать любую функцию GdiPlus.DLL в системе, где эта библиотека не установлена, возникнет структурное исключение (SE). Класс должен обрабатывать такое исключение и сообщать о неудаче. В данном случае мы добавили в описание класса метод Good(), сигнализирующий об успехе или провале инициализации GDI+.
Во-вторых, по крайней мере в Visual Studio 6.0 SP4/SP5 существует ошибка, приводящая к аварийному завершению компоновщика при линковке Delay-loading модулей и модулей с отладочной информацией (см., например, сообщение в форуме RSDN: http://www.rsdn.ru/Forum/Message.aspx?mid=101066). Поэтому для отладочной версии нашего класса отложенная загрузка выполняться не будет (все равно, на при отладке на Вашей системе GDI+ установлена).
В-третьих, желательно максимально упростить использование класса и не выносить наружу детали реализации (в частности опции компоновщика имеет смысл задать в виде #pragma comment).
ПРИМЕЧАНИЕ К сожалению, компилятор Visual C++ 7.0 воспринимает директиву линкера /delayload как ошибочную и выдает соответствующее предупреждение. Пользователям VC7 придется задавать эту опцию самостоятельно. |
Таким образом, этот класс самостоятельно занимается инициализацией/очисткой GDI+, а также обрабатывает структурное исключение, возникающее при отсутствии библиотеки Gdiplus.dll. Для инициализации библиотеки достаточно объявить экземпляр такого класса.
Для более детального анализа возникшего исключения в конструкторе нашего класса можно применить функцию DelayLoadDllExceptionFilter, описанную в указанной статье Рихтера. |
Приведем небольшой пример использования данного класса. Напишем маленькую утилиту для сохранения содержимого экрана в файл формата PNG.
// Компиляция: // cl grab.cpp InitGdiPlus.cpp user32.lib kernel32.lib gdi32.lib // для пользователей VC7 добавьте ключ /link /delayload:gdiplus.dll #define STRICT #include <windows.h> #include <GdiPlus.h> #include "InitGdiPlus.h" //Внимание: в будущих версиях GDI+ GUID кодека может измениться //В таком случае, для определения GUID обратитесь к статье в RSDN Magazine №1 static const GUID png = { 0x557cf406, 0x1a04, 0x11d3, { 0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e } }; int CALLBACK WinMain(IN HINSTANCE hInstance, IN HINSTANCE hPrevInstance, IN LPSTR lpCmdLine, IN int nShowCmd ) { HDC hdc = ::GetDC(NULL); RECT rc={0, 0, GetSystemMetrics(SM_CXFULLSCREEN), GetSystemMetrics(SM_CYFULLSCREEN)}; HDC hdcMem = CreateCompatibleDC(hdc); HBITMAP hBitmap = CreateCompatibleBitmap(hdc, (int)rc.right, (int)rc.bottom); HBITMAP hOldBmp = (HBITMAP)SelectObject(hdcMem, hBitmap); BitBlt(hdcMem, 0, 0, (int)rc.right, (int)rc.bottom, hdc, 0, 0, SRCCOPY); hBitmap =(HBITMAP) SelectObject(hdcMem, hOldBmp); DeleteDC(hdcMem); RSDN::InitGdiPlus init; if(init.Good()) { Gdiplus::Bitmap bitmap(hBitmap, NULL); bitmap.Save(L"c:\\screen.png", &png); } else { MessageBox(0, "Ошибка инициализации GDI+", 0, MB_OK|MB_ICONHAND); } DeleteObject(hBitmap); return 0; } |
Только не забывайте при использовании класса InitGdiPlus, что время его жизни должно быть больше времени жизни создаваемых в Вашей программе объектов GDI+, иначе их деструкторы выполнятся после вызова GdiplusShutdown.
Сообщений 2 Оценка 0 Оценить |