Устранение ошибки установки альбомной ориентации печати в Delphi XE6
В последнее время разработчики, использующие среду разработки Delphi XE6, столкнулись с неприятной ошибкой, которая проявляется при попытке установить альбомную ориентацию печати. Ошибка возникает в результате вызова функции Printer.Orientation := poLandscape; и сопровождается криптическим сообщением:
Операция не поддерживается на выбранном принтере
Отладка кода
При отладке кода, связанного с функционалом печати, выяснилось, что проблема заключается в том, что глобальный объект TPrinter не может получить структуру DEVMODE для принтера. Это происходит из-за ошибки в вызове функции DocumentProperties из модуля Vcl.Printers. В частности, проблема заключается в следующих строках:
if DeviceMode = 0 then // выделение нового блока устройства, если он не был передан
begin
DeviceMode := GlobalAlloc(GHND,
DocumentProperties(0, FPrinterHandle, ADevice, nil, nil, 0));
// ...snip...
end;
bufferSize := DocumentProperties(0, FPrinterHandle, ADevice, PDeviceMode(@dummyDevMode), PDeviceMode(@dummyDevMode), 0); //20160522 Borland забыл проверить результат
Функция DocumentProperties возвращает -1, что является ошибкой, так как параметры, передаваемые в функцию, не содержат явных ошибок. Несмотря на то, что DocumentProperties не документирована как функция, устанавливающая код последней ошибки при сбое, вызов GetLastError возвращает код ошибки 50 - Запрос не поддерживается.
Обзор кода
В коде присутствуют серьезные недочеты:
Вызов DocumentProperties без проверки возвращаемого значения (возвращает значение меньше нуля при сбое).
При вызове GlobalAlloc происходит потеря данных, так как он ожидает беззнаковое 32-битное целое.
DocumentProperties возвращает -1, который преобразуется в FFFFFFFF при передаче в GlobalAlloc, и в результате попытка выделения 4 ГБ памяти.
Почему ошибка возникает только в XE6
Тот же самый код работает в Delphi 7, что делает проблему особенно странной. Это связано с изменениями в переводах заголовков функций DocumentProperties в Winapi.WinSpool в XE6, где присутствует сложная система перегрузок функций.
Минимальная тестовая программа
Для воспроизведения ошибки можно использовать следующий минимальный тестовый проект:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, Windows, WinSpool;
var
dwBufferLen: DWORD;
defaultPrinter: string;
ADevice: PChar; // Указатель на имя принтера
printerHandle: THandle;
devModeSize: Integer;
deviceMode: THandle;
begin
dwBufferLen := 1024;
SetLength(defaultPrinter, dwBufferLen);
GetDefaultPrinter(PChar(defaultPrinter), @dwBufferLen);
SetLength(defaultPrinter, dwBufferLen);
ADevice := PChar(defaultPrinter);
if not OpenPrinter(ADevice, {var}printerHandle, nil) then
raise Exception.Create('Ошибка при открытии принтера');
devModeSize := DocumentProperties(0, printerHandle, ADevice, nil, nil, 0);
if devModeSize < 0 then
begin
// DocumentProperties возвращает значение меньше нуля, что означает сбой.
// Он также должен установить код последней ошибки; но мы все равно поднимем его.
RaiseLastOSError;
Exit;
// Это хорошо, что мы терпим неудачу, так как возвращаемое значение -1 преобразуется в $FFFFFFFF.
// Delphi затем просит GlobalAlloc попытаться выделить 4 ГБ памяти.
end;
deviceMode := GlobalAlloc(GHND, NativeUInt(devModeSize));
if deviceMode = 0 then
raise Exception.Create('Ошибка выделения памяти для DEVMODE');
end.
Подтвержденный ответ
После долгих попыток решения проблемы, был найден следующий способ устранения ошибки:
DevSize := DocumentPropertiesA(0, FDriverHandle, FDeviceName, nil, nil, 0);
if DevSize = -1 then
begin
log('Ошибка при общении с драйвером принтера! Попытка обойти ошибку ');
GetDriverInfos(FDriverHandle);
DevSize := DocumentPropertiesA(0, FDriverHandle, FDeviceName, nil, nil, 0);
if DevSize <> -1 then
log('Ошибка обойдена.');
end;
Этот метод помог в Delphi XE6 и более поздних версиях, включая Delphi 10.1 Berlin. Ошибка возникала случайным образом и, по-видимому, была связана с обновлением драйверов или операционной системы.
Альтернативный ответ
Возможно, проблема связана с разрядностью приложения. Если переключить тестовый код с 32-битной на 64-битную разрядность, то проблема может быть решена. Это указывает на то, что 32-битное приложение пытается работать с 64-битными драйверами, что приводит к ошибке.
Выводы
Проблема установки альбомной ориентации печати в Delphi XE6 связана с ошибкой в работе с драйверами принтеров и может быть вызвана изменениями в операционной системе. Для её решения можно использовать функцию GetDriverInfos, которая может "сбросить" систему управления принтерами, или же убедиться, что приложение и драйверы принтеров работают в одной разрядности.
Описание контекста: Разработчики сталкиваются с проблемой установки альбомной ориентации печати в Delphi XE6, связанной с ошибками в работе с драйверами принтеров.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.