Почему вызов GetDIBits не работает в Win64?

У меня есть вызов GetDIBits, который отлично работает в 32-битной версии, но не работает в 64-битной. Несмотря на разные значения дескрипторов, содержимое структуры bitmapinfo одинаково.

Вот самый маленький (по крайней мере, слегка структурированный) пример кода, который я смог придумать для воспроизведения ошибки. Я тестировал с Delphi 10 Seattle Update 1, но ошибка возникает даже с другими версиями Delphi.

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  Winapi.Windows,
  System.SysUtils,
  Vcl.Graphics;

type
  TRGBALine = array[Word] of TRGBQuad;
  PRGBALine = ^TRGBALine;

type
  { same structure as TBitmapInfo, but adds space for two more entries in bmiColors }
  TMyBitmapInfo = record
    bmiHeader: TBitmapInfoHeader;
    bmiColors: array[0..2] of TRGBQuad;
  public
    constructor Create(AWidth, AHeight: Integer);
  end;

constructor TMyBitmapInfo.Create(AWidth, AHeight: Integer);
begin
  FillChar(bmiHeader, Sizeof(bmiHeader), 0);
  bmiHeader.biSize := SizeOf(bmiHeader);
  bmiHeader.biWidth := AWidth;
  bmiHeader.biHeight := -AHeight;  //Otherwise the image is upside down.
  bmiHeader.biPlanes := 1;
  bmiHeader.biBitCount := 32;
  bmiHeader.biCompression := BI_BITFIELDS;
  bmiHeader.biSizeImage := 4*AWidth*AHeight; // 4 = 32 Bits/Pixel div 8 Bits/Byte
  bmiColors[0].rgbRed := 255;
  bmiColors[1].rgbGreen := 255;
  bmiColors[2].rgbBlue := 255;
end;

procedure Main;
var
  bitmap: TBitmap;
  res: Cardinal;
  Bits: PRGBALine;
  buffer: TMyBitmapInfo;
  BitmapInfo: TBitmapInfo absolute buffer;
  BitsSize: Cardinal;
  icon: TIcon;
  IconInfo: TIconInfo;
begin
  bitmap := TBitmap.Create;
  try
    icon := TIcon.Create;
    try
      icon.LoadFromResourceID(0, Integer(IDI_WINLOGO));
      if not GetIconInfo(icon.Handle, IconInfo) then begin
        Writeln('Error GetIconInfo: ', GetLastError);
        Exit;
      end;
      bitmap.PixelFormat := pf32bit;
      bitmap.Handle := IconInfo.hbmColor;
      BitsSize := BytesPerScanline(bitmap.Width, 32, 32) * bitmap.Height;
      Bits := AllocMem(BitsSize);
      try
        ZeroMemory(Bits, BitsSize);
        buffer := TMyBitmapInfo.Create(bitmap.Width, bitmap.Height);
        res := GetDIBits(bitmap.Canvas.Handle, bitmap.Handle, 0, bitmap.Height, Bits, BitmapInfo, DIB_RGB_COLORS);
        if res = 0 then begin
          Writeln('Error GetDIBits: ', GetLastError);
          Exit;
        end;
        Writeln('Succeed');
      finally
        FreeMem(Bits);
      end;
    finally
      icon.Free;
    end;
  finally
    bitmap.Free;
  end;
end;

begin
  try
    Main;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

person Uwe Raabe    schedule 18.12.2015    source источник
comment
Может ли это быть связано с проблемой GetDIBits requires a buffer from VirtualAlloc? msdn.microsoft.com/ru -ru/library/windows/desktop/   -  person MBo    schedule 18.12.2015


Ответы (1)


Обновить Комментарий к этому ответу указывает, почему ваш код не работает. Порядок оценки bitmap.Handle и bitmap.Canvas.Handle имеет значение. Поскольку порядок оценки параметров не определен, ваша программа имеет неопределенное поведение. И это объясняет, почему программы x86 и x64 различаются по поведению.

Таким образом, вы можете решить эту проблему, назначив дескриптор растрового изображения и контекст устройства локальным переменным в соответствующем порядке, а затем передав их в качестве аргументов в GetDIBits. Но я по-прежнему считаю, что в коде лучше избегать класса VCL TBitmap и напрямую использовать вызовы GDI, как в приведенном ниже коде.


Я считаю, что ваша ошибка заключается в передаче дескриптора растрового изображения и его дескриптора холста. Вместо этого вы должны передать, например, контекст устройства, полученный вызовом CreateCompatibleDC(0). Или передать IconInfo.hbmColor на GetDIBits. Но не передавайте дескриптор TBitmap и дескриптор его холста.

Я также не вижу никакой цели в том TBitmap, что вы создаете. Все, что вам нужно сделать с ним, это получить ширину и высоту IconInfo.hbmColor. Для этого не нужно создавать TBitmap.

Так что на вашем месте я бы удалил TBitmap и использовал CreateCompatibleDC(0) для получения контекста устройства. Это должно значительно упростить код.

Вам также нужно будет удалить растровые изображения, возвращаемые вызовом GetIconInfo, но я думаю, что вы уже это знаете и удалили этот код из вопроса для простоты.

Откровенно говоря, объекты VCL здесь только мешают. На самом деле намного проще напрямую вызывать функции GDI. Возможно что-то вроде этого:

procedure Main;
var
  res: Cardinal;
  Bits: PRGBALine;
  bitmap: Winapi.Windows.TBitmap;
  DC: HDC;
  buffer: TMyBitmapInfo;
  BitmapInfo: TBitmapInfo absolute buffer;
  BitsSize: Cardinal;
  IconInfo: TIconInfo;
begin
  if not GetIconInfo(LoadIcon(0, IDI_WINLOGO), IconInfo) then begin
    Writeln('Error GetIconInfo: ', GetLastError);
    Exit;
  end;
  try
    if GetObject(IconInfo.hbmColor, SizeOf(bitmap), @bitmap) = 0 then begin
      Writeln('Error GetObject');
      Exit;
    end;

    BitsSize := BytesPerScanline(bitmap.bmWidth, 32, 32) * abs(bitmap.bmHeight);
    Bits := AllocMem(BitsSize);
    try
      buffer := TMyBitmapInfo.Create(bitmap.bmWidth, abs(bitmap.bmHeight));
      DC := CreateCompatibleDC(0);
      res := GetDIBits(DC, IconInfo.hbmColor, 0, abs(bitmap.bmHeight), Bits, BitmapInfo,
        DIB_RGB_COLORS);
      DeleteDC(DC);
      if res = 0 then begin
        Writeln('Error GetDIBits: ', GetLastError);
        Exit;
      end;
      Writeln('Succeed');
    finally
      FreeMem(Bits);
    end;
  finally
    DeleteObject(IconInfo.hbmMask);
    DeleteObject(IconInfo.hbmColor);
  end;
end;
person David Heffernan    schedule 18.12.2015
comment
Спасибо, Дэвид! Я надеялся, что вы вмешаетесь... На самом деле, CreateCompatibleDC кажется достаточным. Хотя мне интересно, почему это работает в Win32. TBitmap заботится об освобождении дескрипторов растрового изображения. И да, для простоты я опустил растровое изображение маски. - person Uwe Raabe; 18.12.2015
comment
Хотя это работает, когда я использую CreateCompatibleDC напрямую, я все еще удивляюсь, почему Canvas,Handle не работает. Если я правильно прочитал код TBitmapCanvas.CreateHandle, он также использует вызов CreateCompatibleDC(0). - person Uwe Raabe; 18.12.2015
comment
Я нашел подсказку в TIcon.Assign. Кажется, важен порядок получения Bitmap.Handle и Bitmap.Canvas.Handle: { FreeContext must be called first } // Forces evaluation of Bitmap.Handle before Bitmap.Canvas.Handle hBmp := Bmp.Handle; if (GetDIBits(Bmp.Canvas.Handle, hBmp, 0, Height, pBitmap, Bmi, DIB_RGB_COLORS) = Height) - person Uwe Raabe; 19.12.2015
comment
Да, так и будет. Порядок оценки параметров не определен и отличается для x86 и x64. - person David Heffernan; 19.12.2015