Java JNA get desktop item position

Pieter12345 :

Goal

Obtaining the desktop coordinates of a desktop item/icon.

Attempt

I have obtained the SysListView32 window handle, which contains the desktop icons using:

HWND hWnd_Progman = User32.INSTANCE.FindWindow("Progman", "Program Manager");
HWND hWnd_SHELLDLL_DefView = User32.INSTANCE.FindWindowEx(hWnd_Progman, null, "SHELLDLL_DefView", null);
HWND hWnd_SysListView32 = User32.INSTANCE.FindWindowEx(hWnd_SHELLDLL_DefView, null, "SysListView32", "FolderView");

I have obtained the desktop item count:

LRESULT result = User32.INSTANCE.SendMessage(hWnd_SysListView32, LVM_GETITEMCOUNT, new WPARAM(), new LPARAM());
long desktopIconCount = result.longValue();

I have SET a desktop item position (validating that SysListView32 is the right list view for desktop items). The passed x and y coordinates corresponded with the offset from the left-top of my left-most monitor to the left-top corner of the desktop item. Code:

int itemIndex = 0; // Allows 0 to desktopIconCount - 1.
int x = ...;
int y = ...;
LRESULT res = User32.INSTANCE.SendMessage(hWnd_SysListView32, LVM_SETITEMPOSITION, new WPARAM(itemIndex), new LPARAM((x & 0xFFFF) | (y << 16)));

Now, to get a desktop item position, one needs to send LVM_GETITEMPOSITION to the SysListView32 and include a pointer to an address where it can write the position to. However, this pointer has to be a valid address in the memory of the process belonging to SysListView32. So what I tried to do is to:

  • Get the process belonging to SysListView32.
  • Allocate memory in that process.
  • Write a POINT object in this memory (used for the item position).
  • Send LVM_GETITEMPOSITION to SysListView32 with a pointer to this allocated memory.
  • Read this POINT object from memory. At this point the process should have written the desktop item position to it.

I've tried this with the following code:

// Get the SysListView32 process handle.
IntByReference processIdRef = new IntByReference();
User32.INSTANCE.GetWindowThreadProcessId(hWnd_SysListView32, processIdRef);
HANDLE procHandle = Kernel32.INSTANCE.OpenProcess(
        Kernel32.PROCESS_VM_OPERATION | Kernel32.PROCESS_VM_WRITE | Kernel32.PROCESS_VM_READ,
        false, processIdRef.getValue());

// Allocate memory in the SysView32 process.
int pointSize = Native.getNativeSize(POINT.class)); // 8 bytes.
LPVOID pMem = MyKernel32.INSTANCE.VirtualAllocEx(procHandle, new LPVOID(), new SIZE_T(pointSize),
        MyKernel32.MEM_COMMIT, MyKernel32.PAGE_READWRITE);

// Put some POINT-sized object in the process its memory.
boolean success = Kernel32.INSTANCE.WriteProcessMemory(
        procHandle, pMem.getPointer(), pMem.getPointer(), pointSize, null);
if(!success) {
    System.out.println("Write error = " + Kernel32.INSTANCE.GetLastError());
    System.exit(1);
}

// Send the LVM_GETITEMPOSITION message to the SysListView32.
int itemIndex = 0; // Allows 0 to desktopIconCount - 1.
LRESULT res = MyUser32.INSTANCE.SendMessage(
        hWnd_SysListView32, LVM_GETITEMPOSITION, new WPARAM(itemIndex), pMem.getPointer());
System.out.println("Message result = " + res.longValue());

// Read the earlier POINT-sized written memory.
POINT point = new POINT();
success = Kernel32.INSTANCE.ReadProcessMemory(
        procHandle, pMem.getPointer(), point.getPointer(), pointSize, null);
if(!success) {
    System.out.println("Read error = " + Kernel32.INSTANCE.GetLastError());
    System.exit(1);
}
System.out.println("Point found: x=" + pos.x + ", y=" + pos.y);

Here, MyUser32 is created as follows:

interface MyUser32 extends User32 {
    static MyUser32 INSTANCE =
        (MyUser32) Native.load("user32", MyUser32.class, W32APIOptions.DEFAULT_OPTIONS);
    LRESULT SendMessage(HWND hWnd, int msg, WPARAM wParam, Pointer pointer);
}

and MyKernel32 is created as follows:

interface MyKernel32 extends Kernel32 {
    static final MyKernel32 INSTANCE =
            (MyKernel32) Native.load("kernel32", MyKernel32.class, W32APIOptions.DEFAULT_OPTIONS);
    static int MEM_COMMIT = 0x1000;
    static int PAGE_READWRITE = 0x04;
    LPVOID VirtualAllocEx(HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, int flAllocationType, int flProtect);
}

To be complete, the following additional static values are used:

static final int LVM_FIRST = 0x1000;
static final int LVM_GETITEMCOUNT = LVM_FIRST + 4;
static final int LVM_SETITEMPOSITION = LVM_FIRST + 15;
static final int LVM_GETITEMPOSITION = LVM_FIRST + 16;

Problem

The WriteProcessMemory call often fails with error code 299 ERROR_PARTIAL_COPY and even when it does not fail, the returned POINT is always (0,0). I expect the problem to be in either the SendMessage / VirtualAllocEx method declarations in MyUser32 or MyKernel32, or by me not understanding properly which object/pointer to pass to VirtualAllocEx or WriteProcessMemory.

I've done a lot of research and figured out how it should work in C/C++, but I could not find any working code example using JNA for my case.

Thank you for showing interest and trying to help if you made it all the way through my message.

Matthias Bläsing :

The problem is that com.sun.jna.Native.getNativeSize(Class) is not the right function to use in this case. The problem is visible, when using a 32bit JVM (it is not visible a 64bit VM).

For structures the above mentioned function assumes that they are passed by reference (pointer to the structure) and thus the function returns the value of Native.POINTER_SIZE. On a 64Bit VM, this matches by luck the size of the POINT structure. On a 32Bit VM Native.POINTER_SIZE is 4 byte, and so can only hold parts of the result structure.

The most relevant parts: to determine the size of a Structure in JNA, use the Structure#size function. In this case it is also helpful to use the final parameter of ReadProcessMemory. The function returns the number of bytes that were read and shows the difference (4 vs. 8).

Further comments: remember to free the memory you allocated and also close the process handle to received.

Here is the full runnable sample (only missing imports, tested with JNA 5.2):

public class Test {

    private interface Kernel32 extends com.sun.jna.platform.win32.Kernel32 {

        Kernel32 INSTANCE = Native.load("kernel32", Kernel32.class, W32APIOptions.DEFAULT_OPTIONS);

        public Pointer VirtualAllocEx(HANDLE hProcess, Pointer lpAddress, SIZE_T dwSize, int flAllocationType, int flProtect);

        public boolean VirtualFreeEx(HANDLE hProcess, Pointer lpAddress, SIZE_T dwSize, int dwFreeType);

        int MEM_COMMIT = 0x00001000;
        int MEM_RESERVE = 0x00002000;
        int MEM_RESET = 0x00080000;
        int MEM_RESET_UNDO = 0x1000000;
        int MEM_LARGE_PAGES = 0x20000000;
        int MEM_PHYSICAL = 0x00400000;
        int MEM_TOP_DOWN = 0x00100000;
        int MEM_COALESCE_PLACEHOLDERS = 0x00000001;
        int MEM_PRESERVE_PLACEHOLDER = 0x00000002;
        int MEM_DECOMMIT = 0x4000;
        int MEM_RELEASE = 0x8000;
    }

    private static final int LVM_FIRST = 0x1000;
    private static final int LVM_GETITEMCOUNT = LVM_FIRST + 4;
    private static final int LVM_GETITEMPOSITION = LVM_FIRST + 16;

    public static void main(String[] args) throws IOException, InterruptedException {
        // Find the HWND for the "desktop" list view
        HWND hWnd_Progman = User32.INSTANCE.FindWindow("Progman", "Program Manager");
        HWND hWnd_SHELLDLL_DefView = User32.INSTANCE.FindWindowEx(hWnd_Progman, null, "SHELLDLL_DefView", null);
        HWND hWnd_SysListView32 = User32.INSTANCE.FindWindowEx(hWnd_SHELLDLL_DefView, null, "SysListView32", "FolderView");
        // Fetch the icon count
        int itemCount = User32.INSTANCE.SendMessage(hWnd_SysListView32, LVM_GETITEMCOUNT, new WPARAM(), new LPARAM()).intValue();
        System.out.println("Desktop Icons: " + itemCount);

        // Get the SysListView32 process handle.
        IntByReference processIdRef = new IntByReference();
        User32.INSTANCE.GetWindowThreadProcessId(hWnd_SysListView32, processIdRef);
        HANDLE procHandle = Kernel32.INSTANCE.OpenProcess(
                Kernel32.PROCESS_VM_OPERATION | Kernel32.PROCESS_VM_WRITE | Kernel32.PROCESS_VM_READ,
                false, processIdRef.getValue());

        // Allocate memory in the SysView32 process.
        int pointSize = new POINT().size(); // 8 bytes.
        Pointer pMem = Kernel32.INSTANCE.VirtualAllocEx(procHandle, null, new SIZE_T(pointSize),
                Kernel32.MEM_COMMIT, Kernel32.PAGE_READWRITE);

        for (int i = 0; i < itemCount; i++) {

            // Send the LVM_GETITEMPOSITION message to the SysListView32.
            LRESULT res = User32.INSTANCE.SendMessage(
                    hWnd_SysListView32, LVM_GETITEMPOSITION, new WPARAM(i), new LPARAM(Pointer.nativeValue(pMem)));

            if(res.intValue() != 1) {
                throw new IllegalStateException("Message sending failed");
            }

            // Read the earlier POINT-sized written memory.
            POINT point = new POINT();
            IntByReference read = new IntByReference();
            boolean success = Kernel32.INSTANCE.ReadProcessMemory(
                    procHandle, pMem, point.getPointer(), pointSize, read);

            if (!success) {
                System.out.println("Read error = " + Kernel32.INSTANCE.GetLastError());
                System.exit(1);
            }
            point.read();
            System.out.println("Point found: x=" + point.x + ", y=" + point.y);
        }

        // Release allocated memory
        Kernel32.INSTANCE.VirtualFreeEx(procHandle, pMem, new SIZE_T(0), Kernel32.MEM_RELEASE);

        // Close Process Handle
        Kernel32.INSTANCE.CloseHandle(procHandle);
    }
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=81499&siteId=1