Search code examples
javawinapijna

Detect size of HICON with JNA


My final goal is to obtain the icon of a HWND in Java with help of JNA library. Everything works fine except of one important thing: I need the size of the icon for further processing steps in Java.

It seems that I cannot request the size. I always obtain the size 0x0. What am I doing wrong? The basic code example looks like the following. Most API function templates were not part of JNA. So, I had to define them on my own.

    final long hicon = ExtUser32.INSTANCE.SendMessageA(hwnd, ExtUser32.WM_GETICON, ExtUser32.ICON_BIG, 0);
    final Pointer hIcon = new Pointer(hicon);
    final ICONINFO info = new ICONINFO();
    final BITMAP bmp = new BITMAP();
    final SIZE size = new SIZE();

    System.out.println(ExtUser32.INSTANCE.GetIconInfo(hIcon, info));
    System.out.println(info);
    System.out.println(ExtGdi32.INSTANCE.GetBitmapDimensionEx(info.hbmColor, size));
    System.out.println(size);

    if (info.hbmColor != null)
    {
        final int nWrittenBytes = ExtGdi32.INSTANCE.GetObjectA(info.hbmColor, bmp.size(), bmp.getPointer());
        System.out.println(nWrittenBytes);
        System.out.println(bmp);
    }

The sysouts print this:

true
ICONINFO(auto-allocated@0x5b72b4f0 (32 bytes)) {
  WinDef$BOOL fIcon@0=1
  WinDef$DWORD xHotspot@4=16
  WinDef$DWORD yHotspot@8=16
  WinDef$HBITMAP hbmMask@10=native@0xffffffffb00515e8 (com.sun.jna.platform.win32.WinDef$HBITMAP@b00515e7)
  WinDef$HBITMAP hbmColor@18=native@0xffffffffa50515c8 (com.sun.jna.platform.win32.WinDef$HBITMAP@a50515c7)
}
true
WinUser$SIZE(auto-allocated@0x652a3000 (8 bytes)) {
  int cx@0=0
  int cy@4=0
}
32
BITMAP(auto-allocated@0x5b72b5b0 (32 bytes)) {
  WinDef$LONG bmType@0=0
  WinDef$LONG bmWidth@4=0
  WinDef$LONG bmHeight@8=0
  WinDef$LONG bmWidthBytes@c=0
  WinDef$WORD bmPlanes@10=0
  WinDef$WORD bmBitsPixel@12=0
  WinDef$LPVOID bmBits@18=0
}

The request of ICONINFO structure seems to be correct. But if I try to request the dimension for the set hbmColor structure component by Gdi32.GetBitmapDimensionEx() then the structure keeps initialized with zeros. This approach via hbmColor or hbmMask was suggested by:

How to determine the size of an icon from a HICON?

UPDATE 1

Error tracing added!

As the sysouts indicate (true), the concerning function invocations didn't fail.

UPDATE 2

Further observation: In Java, these recreated structure types are intialized with zeros after instantiation. I set the initial values of the structure components in SIZE and BITMAP to a value that deviates from zero. GetBitmapDimensionEx sets it back to zero. But GetObjectA doesn't modify the structure! The function's return result indicates that bytes were written but that's not true!

        ...
        size.cx = 1;
        size.cy = 2;

        bmp.bmType.setValue(1);
        bmp.bmWidth.setValue(2);
        bmp.bmHeight.setValue(3);
        bmp.bmWidthBytes.setValue(4);
        bmp.bmPlanes.setValue(5);
        bmp.bmBitsPixel.setValue(6);
        bmp.bmBits.setValue(7);

        System.out.println(ExtGdi32.INSTANCE.GetBitmapDimensionEx(info.hbmColor, size));
        System.out.println(size);

        if (info.hbmColor != null)
        {
            final int nWrittenBytes = ExtGdi32.INSTANCE.GetObjectA(info.hbmColor, bmp.size(), bmp.getPointer());
            System.out.println(nWrittenBytes);
            System.out.println(bmp);
        }

Results:

true
WinUser$SIZE(auto-allocated@0x64fbcb20 (8 bytes)) {
  int cx@0=0
  int cy@4=0
}
32
BITMAP(auto-allocated@0x64fb91f0 (32 bytes)) {
  WinDef$LONG bmType@0=1
  WinDef$LONG bmWidth@4=2
  WinDef$LONG bmHeight@8=3
  WinDef$LONG bmWidthBytes@c=4
  WinDef$WORD bmPlanes@10=5
  WinDef$WORD bmBitsPixel@12=6
  WinDef$LPVOID bmBits@18=7
}

Solution

  • I would have added this as a comment but my reputation is too low:

    You are not showing your BITMAP or GetObjectA definitions so I'm guessing but in your line:

            final int nWrittenBytes = ExtGdi32.INSTANCE.GetObjectA(info.hbmColor, bmp.size(), bmp.getPointer());
    

    you fail to call 'bmp.read()' afterwards.

    If you look at the javadoc for Struture.getPointer()

    https://jna.java.net/javadoc/com/sun/jna/Structure.html

    you see that you are responsible for calling Structure.write() and Structure.read() before and after making the call to native method that uses a pointer obtained with getPointer(). In your case the write is superfluous but it is a good practice.

    To understand why this is necessary consider that your BITMAP/bmp object is a Java object living in Java heap where it can get moved around during garbage collection. Hence the getPointer() cannot return the real address of the 'real' object. Instead it returns a pointer to a separate fixed (non movable) chunk of memory in the native heap (which chunk JNA allocates and associates with your Java object. Now your getObjectA() routine will write its stuff to that memory but JNA or anyone on the java side cannot have a clue that this is what happens. So you need to call the read() to tell JNA to copy the native side stuff to the Java object.