DocumentProperties在XE 6中失败;在 Delphi 7中工作

de90aj5v  于 2023-06-22  发布在  其他
关注(0)|答案(2)|浏览(154)

Delphi XE 6中还有另一个bug(可能是在添加Unicode支持的时候添加的)。
最初可以通过尝试调用以下命令来公开它:

procedure TForm1.Button1Click(Sender: TObject);
begin
   Printer.Orientation := poLandscape; //use Vcl.Printers
end;

此操作失败,并显示隐藏错误

  • 所选打印机不支持此操作 *

调试他们的代码

当您跟踪VCL时,问题归结为全局TPrinter无法获得打印机的DEVMODE结构。当它尝试从Vcl调用Windows DocumentProperties函数时,此操作失败。打印机:

if DeviceMode = 0 then  // alloc new device mode block if one was not passed in
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 forgot to check the result

奇怪的是,DocumentProperties失败了:返回**-1**。这很奇怪,因为参数在概念上没有什么特别的错误。
DocumentProperties在失败时没有记录为SetLastError,但GetLastError始终返回:
50 -请求不受支持

代码审核

这里有一些非常糟糕的代码:

  • 它调用DocumentProperties,但不检查返回值(* 如果失败,则返回小于零的值 *)
  • DocumentProperties返回 * 有符号 * 32位整数
  • GlobalAlloc采用无符号32位整数时下溢
  • DocumentProperties失败,返回-1
  • 在传递给GlobalAlloc时转换为$ffffffff,然后尝试分配4 GB内存

但只在XE 6中失败

奇怪的是,同样的代码在 Delphi 7中也能工作。它不应该在支持Unicode的XE 6中失败。查看XE 6中Winapi.WinSpoolDocumentProperties的头转换:

function DocumentProperties( hWnd: HWND; hPrinter: THandle; pDeviceName: LPWSTR; const pDevModeOutput: TDeviceMode;  var pDevModeInput: TDeviceMode;  fMode: DWORD): Longint; stdcall; overload;
function DocumentProperties( hWnd: HWND; hPrinter: THandle; pDeviceName: LPWSTR;       pDevModeOutput: PDeviceMode;      pDevModeInput: PDeviceMode;  fMode: DWORD): Longint; stdcall; overload;
function DocumentPropertiesA(hWnd: HWND; hPrinter: THandle; pDeviceName: LPSTR;  const pDevModeOutput: TDeviceModeA; var pDevModeInput: TDeviceModeA; fMode: DWORD): Longint; stdcall; overload;
function DocumentPropertiesA(hWnd: HWND; hPrinter: THandle; pDeviceName: LPSTR;        pDevModeOutput: PDeviceModeA;     pDevModeInput: PDeviceModeA; fMode: DWORD): Longint; stdcall; overload;
function DocumentPropertiesW(hWnd: HWND; hPrinter: THandle; pDeviceName: LPWSTR; const pDevModeOutput: TDeviceModeW; var pDevModeInput: TDeviceModeW; fMode: DWORD): Longint; stdcall; overload;
function DocumentPropertiesW(hWnd: HWND; hPrinter: THandle; pDeviceName: LPWSTR;       pDevModeOutput: PDeviceModeW;     pDevModeInput: PDeviceModeW; fMode: DWORD): Longint; stdcall; overload;

他们在那里做了一些非常花哨的const-var/typed-untyped过载步法。
其中 Delphi 7具有更简单的:

function DocumentProperties( hWnd: HWND; hPrinter: THandle; pDeviceName: PChar;     const pDevModeOutput: TDeviceMode;  var pDevModeInput: TDeviceMode;  fMode: DWORD): Longint; stdcall;
function DocumentPropertiesA(hWnd: HWND; hPrinter: THandle; pDeviceName: PAnsiChar; const pDevModeOutput: TDeviceModeA; var pDevModeInput: TDeviceModeA; fMode: DWORD): Longint; stdcall;
function DocumentPropertiesW(hWnd: HWND; hPrinter: THandle; pDeviceName: PWideChar; const pDevModeOutput: TDeviceModeW; var pDevModeInput: TDeviceModeW; fMode: DWORD): Longint; stdcall;

完成最小测试程序

这里已经过了午夜了你们中的一些人刚刚醒来。而我已经过了睡觉时间,伴随着一大堆的咒骂和脏话:

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, Windows, WinSpool;

var
    dwBufferLen: DWORD;
    defaultPrinter: string;
    ADevice: PChar; //Pointer to printer name
    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('Error checking removed for expository purposes');

    devModeSize := DocumentProperties(0, printerHandle, ADevice, nil, nil, 0);
    if devModeSize < 0 then
    begin
        //DocumentProperties is documented to have failed if it returns a value less than zero.
        //It's not documented to have also SetLastError; but we'll raise it anyway (Error code 50 - The request is not supported)
        RaiseLastOSError;
        Exit;
        //It's a good thing we fail. Because the return value -1 is coerced into an unsigned $FFFFFFFF.
        //Delphi then asks GlobalAlloc to try to allocate 4 GB of memory. *le sigh*
    end;

    deviceMode := GlobalAlloc(GHND, NativeUInt(devModeSize));
    if deviceMode = 0 then
        raise Exception.Create('It''s DocumentProperties above that fails. GlobalAlloc is just the victim of being asked to allocate 4GB of memory.');
end.

怎么走?

奖励聊天

  • QC#6725:一个单独的打印相关bug,8年前关闭,现在仍然存在于VCL中。他们将无效参数传递给他们甚至不应该使用的Windows函数。该函数随后因参数无效而失败,导致代码回退到使用检查win.ini的16位Windows旧版兼容性函数。而不是首先使用正确的功能。
ffvjumwh

ffvjumwh1#

我也有这个bug……它总是返回-1,但只有当我在IDE中调试时。窃听器突然出现了。我认为这是一个Windows更新或自动驱动程序更新。我没有改变任何具体的工作站设置。经过几个小时的测试和调试,我注意到一个解决问题的技巧:
查询“GetDriverInfos”似乎会发出某种重置,PrinterSystem开始工作。

DevSize := DocumentPropertiesA(0,FDriverHandle,FDeviceName,nil, nil,0);
if DevSize = -1 then
begin
  log('Failed to communicate with printer driver! Trying to ByPass Bug ');
  GetDriverInfos(FDriverHandle);
  DevSize := DocumentPropertiesA(0,FDriverHandle,FDeviceName,nil, nil,0);
  if DevSize <> -1 then
     log('Bug bypassed.');
end;

我知道这很奇怪,它为我工作(使用柏林10.1)。我们以前在所有 Delphi 版本中都有这个bug,随机出现。

vngu2lb8

vngu2lb82#

我只是想发布一个“TL; www.example.com信息的“DR”pinvoke.net
我不得不更换

hMode := GlobalAlloc(GHND, DocumentProperties(0, FPrinterHandle, pcName, StubDevMode, StubDevMode, 0));

hMode := GlobalAlloc(GHND, DocumentProperties(0, FPrinterHandle, pcName, PDeviceMode(0), @StubDevMode, 0));

完整的代码是:

FDevMode := nil;
   if OpenPrinter(pcName, FPrinterHandle, nil) then
   begin
    if hMode = 0 then  // alloc new device mode block if one was not passed in
    begin
//      hMode := GlobalAlloc(GHND, DocumentProperties(0, FPrinterHandle, pcName, StubDevMode,StubDevMode, 0));
      hMode := GlobalAlloc(GHND, DocumentProperties(0, FPrinterHandle, pcName, PDeviceMode(0), @StubDevMode, 0));
      if hMode <> 0 then
      begin
        FDevMode := GlobalLock(hMode);
        if FDevMode = nil then RaiseLastOSError;
        if DocumentProperties(0, FPrinterHandle, pcName, FDevMode^,FDevMode^, DM_OUT_BUFFER) < 0 then
        begin
          GlobalUnlock(hMode);
          GlobalFree(hMode);
          hMode := 0;
        end
      end;
    end;
   end;

由于第一次使用DocumentProperties()的唯一目的是确定devmode的大小,因此将out参数设置为空指针没有问题。

相关问题