Search code examples
c#.net64-bitpinvokegdi+

gdi32.GetObject doesn't work when running 64-bit


This code works fine when running 32-bit. But when I switch to 64-bit the GetObject method does not work and BITMAP struct is empty.

IntPtr hBmp = ObtainValidBitmapHandleFromSystem();
BITMAP bmpData = new BITMAP();
/* BITMAP consists of four 32bit ints,
 * two 16 bit uints and one IntPtr */
 * 4 + sizeof(UInt16) * 2 + IntPtr.Size;
int cbBuffer = sizeof(Int32) * 4 + sizeof(UInt16) * 2 + IntPtr.Size;
NativeMethods.GetObject(hBmp, cbBuffer, out bmpData);

Bitmap bmp = new Bitmap(bmpData.Width, bmpData.Height, PixelFormat.Format32bppPArgb);

The native method implementation:

private static class NativeMethods
{
    [DllImport("gdi32", CharSet = CharSet.Auto)]
    internal extern static int GetObject(
        IntPtr hgdiobj,     // handle to graphics object
        int cbBuffer,       // size of buffer for object information
        out BITMAP lpvObject    // Should be IntPtr, but we know we will use it only for BITMAP.
    );
}

The BITMAP structure implementation (removed documentation to keep code compact):

[StructLayoutAttribute(LayoutKind.Sequential)]
private struct BITMAP
{
    public Int32 Type;
    public Int32 Width;
    public Int32 Height;
    public Int32 WidthBytes;
    public UInt16 Planes;
    public UInt16 BitsPixel;
    public IntPtr Bits;
}

The idea behind this code is fully described in this question.

At first I thought that the issue is caused by different size of IntPtr resulting in different size of cbBuffer, but it seems that this is not the case as changing cbBuffer size did not help.

What is the correct way to use GDI's GetObject method on 64-bit system?


Solution

  • The problem is this line:

    cbBuffer = sizeof(Int32) * 4 + sizeof(UInt16) * 2 + IntPtr.Size;
    

    That works on the 32 bit version because the alignment of the struct has no padding. But on the 64 bit version, there are 4 bytes of padding before the pointer. So cbBuffer is 4 bytes short.

    That's the problem. The solution is to stop calculating the size yourself and use Marshal.SizeOf() which is designed for this very purpose.