代码之家  ›  专栏  ›  技术社区  ›  MineRickStar

如何在Java中从Windows获取当前选定的应用程序

  •  0
  • MineRickStar  · 技术社区  · 3 月前

    我正在尝试找出当前正在运行的应用程序中的哪一个是所选的应用程序。

    例如,如果打开的应用程序是Chrome、Spotify、文件管理器和设置。用户位于“设置”应用程序中,因此是当前选定的应用程序。

    我想获取Java中标识此应用程序的名称、PID或任何东西。

    0 回复  |  直到 3 月前
        1
  •  2
  •   DuncG    3 月前

    如果你使用JDK16+,使用纯Java代码很容易读取当前前台窗口的PID,利用 Foreign Function Memory API 。最好从JDK22开始使用,因为FFM在发布之前正在孵化或预览。您需要调用以下Windows API方法:

    此演示只是循环,因此您可以在运行时选择不同的窗口进行测试,并根据需要进行重构:

    // Needs import java.lang.foreign.*; java.lang.invoke.MethodHandle 
    
    final int maxChars = 1024;
    try(Arena arena = Arena.ofConfined()) {
    
        // Allocations for GetWindowTextW + GetWindowThreadProcessId
        MemorySegment lpString = arena.allocate(JAVA_CHAR, maxChars);
        MemorySegment lpdwProcessId = arena.allocate(Native_h.DWORD);
    
        for (int i = 0; i < 100; i++) {
            if (i > 0) Thread.sleep(1000L);
    
            MemorySegment hWnd = Native_h.GetForegroundWindow();
            int used = Native_h.GetWindowTextW(hWnd, lpString, maxChars-1);
            Objects.checkIndex(used, maxChars);
            String text = lpString.getString(0, StandardCharsets.UTF_16LE);
    
            int rc = Native_h.GetWindowThreadProcessId(hWnd, lpdwProcessId);
            // NB check that (rc != 0)
            int pid = lpdwProcessId.get(Native_h.DWORD, 0);
            System.out.format("GetForegroundWindow  hWnd:%x => `%s` PID %d%n", hWnd.address(), text, pid);
        }
    }
    

    上面的代码非常简单。但有一个不平凡的问题—— Native_h ?幸运的是,您可以使用 jextract 为这些方法所需的任何Windows标头定义构建Java代码。如果您没有标题,请获取Microsoft Visual Studio。

    echo #include ^<shlobj_core.h^> > Native.h
    
    jextract -luser32 -t yourpackage.name --output src --include-function GetForegroundWindow --include-function GetWindowTextW --include-function GetWindowThreadProcessId Native.h
    

    为了简化,这里有一个版本,它提供了与jextract相同的功能,可以处理这些原生Windows方法的解析:

    class Native_h {
        static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.libraryLookup(System.mapLibraryName("user32"), Arena.ofAuto());
    
        public static final ValueLayout.OfByte C_CHAR =(ValueLayout.OfByte)Linker.nativeLinker().canonicalLayouts().get("char");
        public static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("int");
        public static final ValueLayout.OfInt C_LONG = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("long");
        public static final OfInt DWORD = C_LONG;
        public static final AddressLayout C_POINTER = ((AddressLayout) Linker.nativeLinker().canonicalLayouts().get("void*"))
                .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR));
    
        public static final MethodHandle GetForegroundWindow$HANDLE = Linker.nativeLinker().downcallHandle(SYMBOL_LOOKUP.find("GetForegroundWindow").get(), FunctionDescriptor.of(Native_h.C_POINTER));
        public static MemorySegment GetForegroundWindow() {
            var mh$ = GetForegroundWindow$HANDLE;
            try {
                return (MemorySegment)mh$.invokeExact();
            } catch (Throwable ex$) {
               throw new AssertionError("should not reach here", ex$);
            }
        }
        public static final MethodHandle GetWindowTextW$HANDLE = Linker.nativeLinker().downcallHandle(
                SYMBOL_LOOKUP.find("GetWindowTextW").get(),
                FunctionDescriptor.of(Native_h.C_INT, Native_h.C_POINTER, Native_h.C_POINTER, Native_h.C_INT));
    
        public static int GetWindowTextW(MemorySegment hWnd, MemorySegment lpString, int nMaxCount) {
            var mh$ = GetWindowTextW$HANDLE;
            try {
                return (int)mh$.invokeExact(hWnd, lpString, nMaxCount);
            } catch (Throwable ex$) {
               throw new AssertionError("should not reach here", ex$);
            }
        }
        public static final MethodHandle GetWindowThreadProcessId$HANDLE = Linker.nativeLinker().downcallHandle(
                SYMBOL_LOOKUP.find("GetWindowThreadProcessId").get(), FunctionDescriptor.of(Native_h.C_LONG, Native_h.C_POINTER, Native_h.C_POINTER));
        public static int GetWindowThreadProcessId(MemorySegment hWnd, MemorySegment lpdwProcessId) {
            var mh$ = GetWindowThreadProcessId$HANDLE;
            try {
                return (int)mh$.invokeExact(hWnd, lpdwProcessId);
            } catch (Throwable ex$) {
               throw new AssertionError("should not reach here", ex$);
            }
        }
    }
    
        2
  •  1
  •   VGR    3 月前

    在DuncG发布他的答案之前不久,我写了这篇文章。我意识到这与他的回答有很大重叠。(有趣的是,我们对使用哪些Windows本机函数的想法非常相似。)无论如何,我决定发布这篇文章,作为一种更紧凑的替代方案,不需要任何提取或代码生成。

    像他一样,我建议使用本地语言 GetForegroundWindow , GetWindowThreadProcessId ,以及 GetWindowTextW Windows的功能:

    import java.nio.charset.StandardCharsets;
    
    import java.util.Optional;
    
    import java.lang.invoke.MethodHandle;
    
    import java.lang.foreign.Linker;
    import java.lang.foreign.SymbolLookup;
    import java.lang.foreign.FunctionDescriptor;
    import java.lang.foreign.ValueLayout;
    import java.lang.foreign.MemorySegment;
    import java.lang.foreign.Arena;
    
    public class WindowsForegroundWindowDetector {
        public record WindowInfo(long pid,
                                 String title,
                                 MemorySegment hwnd) {
            // Deliberately empty.
        }
    
        private final MethodHandle GetForegroundWindow;
    
        private final MethodHandle GetWindowThreadProcessId;
    
        private final MethodHandle GetWindowTextW;
    
        public WindowsForegroundWindowDetector() {
            Linker linker = Linker.nativeLinker();
            SymbolLookup lookup =
                SymbolLookup.libraryLookup("User32", Arena.global());
    
            GetForegroundWindow = linker.downcallHandle(
                lookup.findOrThrow("GetForegroundWindow"),
                FunctionDescriptor.of(
                    ValueLayout.ADDRESS)); // returns HWND
    
            GetWindowThreadProcessId = linker.downcallHandle(
                lookup.findOrThrow("GetWindowThreadProcessId"),
                FunctionDescriptor.of(
                    ValueLayout.JAVA_INT,                           // returns DWORD
                    ValueLayout.ADDRESS.withName("hWnd"),           // HWND
                    ValueLayout.ADDRESS.withName("lpdwProcessId")));// LPDWORD 
    
            GetWindowTextW = linker.downcallHandle(
                lookup.findOrThrow("GetWindowTextW"),
                FunctionDescriptor.of(
                    ValueLayout.JAVA_INT,                           // returns int
                    ValueLayout.ADDRESS.withName("hWnd"),           // HWND
                    ValueLayout.ADDRESS.withName("lpString"),       // LPWSTR
                    ValueLayout.JAVA_INT.withName("nMaxCount")));   // int
        }
    
        private WindowInfo toWindowInfo(MemorySegment hwnd) {
            int rawPID;
            String title;
            try (Arena arena = Arena.ofConfined()) {
                MemorySegment lpdwProcessId =
                    arena.allocateFrom(ValueLayout.JAVA_INT, 0);
    
                int threadID;
                try {
                    threadID = (int) GetWindowThreadProcessId.invokeExact(
                        hwnd, lpdwProcessId);
                } catch (Throwable t) {
                    throw new RuntimeException(
                        "GetWindowThreadProcessId failed.", t);
                }
                if (threadID == 0) {
                    throw new RuntimeException("GetWindowThreadProcessId failed.");
                }
                rawPID = lpdwProcessId.get(ValueLayout.JAVA_INT, 0);
    
                int maxTitleLength = 1024;
                MemorySegment titleBuffer =
                    arena.allocate(ValueLayout.JAVA_CHAR, maxTitleLength);
    
                int titleCharCount;
                try {
                    titleCharCount = (int) GetWindowTextW.invokeExact(
                        hwnd, titleBuffer, maxTitleLength);
                } catch (Throwable t) {
                    throw new RuntimeException("GetWindowTextW failed.", t);
                }
                if (titleCharCount == 0) {
                    throw new RuntimeException("GetWindowTextW failed.");
                }
                title = titleBuffer.getString(0, StandardCharsets.UTF_16LE);
            }
    
            long pid = ((long) rawPID) & 0xffffffffL;
            return new WindowInfo(pid, title, hwnd);
        }
    
        public Optional<WindowInfo> detectForegroundWindow() {
            MemorySegment hwnd;
            try {
                hwnd = (MemorySegment) GetForegroundWindow.invokeExact();
            } catch (Throwable t) {
                throw new RuntimeException("GetForegroundWindow failed.", t);
            }
    
            if (hwnd.equals(MemorySegment.NULL)) {
                return Optional.empty();
            } else {
                return Optional.of(toWindowInfo(hwnd));
            }
        }
    
        public static void main(String[] args) {
            Optional<WindowInfo> info =
                new WindowsForegroundWindowDetector().detectForegroundWindow();
    
            if (info.isPresent()) {
                System.out.println(info.get());
            } else {
                System.out.println("No foreground window found.");
            }
        }
    }
    
        3
  •  -1
  •   rzwitserloot    3 月前

    你不能从java本身做到这一点。你需要更多的库。

    Java是一种多平台语言,就像许多其他语言一样。所有这些语言都需要做出选择:操作系统提供的各种功能并不是通用的;毕竟,操作系统并不完全相同。它们以特定的方式做某些事情,其他操作系统根本不做,并以完全不同的方式满足底层用户的需求。那么,对于这种非(近乎)普遍的特征,语言应该做什么呢?

    Java选择根本不让这些内容可访问。这些功能本质上是不可测试的,除非有一台运行该操作系统的机器——这只是提供它的问题之一。其他语言选择不同的路线;例如,python非常愿意提供高度特定于操作系统的库。

    解决方案-JNI

    JNI或“Java本机接口”允许您为某些本机平台部署编译后的工件,并从Java代码中与它们进行交互。稍微简化一点,“从java调用C代码”(如果你的操作系统是基于C的……windows或多或少是)。因此,您需要一个用非java编写的库来做您想做的事情,然后您可以从java代码中调用它。这不是“java启动另一个应用程序并与之对话”(java也可以这样做,通过 ProcessBuilder 例如)-这是“JVM在自己的进程中运行一个DLL,并将类C数据结构转换为类java数据结构,以使同一应用程序的这两个部分相互通信”。

    所以,这是可能的,但并不容易;JNI相当笨拙。你还需要一个用C编写的东西来实现你的所有调用。你可以自己编写,但这意味着你需要有效地用C++编写并将其编译成DLL,从你的问题中,我感觉你不能这样做或不想这样做。

    所以,你必须寻找为你完成这项工作的项目。

    你有点幸运;JNA+JNA平台随心所欲:以下是 JNA's Platform Library 这可以做到这一点。

    但是,它的使用很复杂,JNA并没有被广泛使用或积极开发。这是一个11岁的随机片段,它提供了 getWindowTextW 要获取窗口标题,请执行以下操作: random snippet .

    正如评论所说,java不是用于高度特定于操作系统的交互的好工具。但是,如果你坚持用旧鞋的背面砸碎这个特殊的螺丝,而不是使用正确的工具箱工具,你的答案就在那里。当然,现在你需要知道如何使用JNA,它有自己显著的学习曲线。