使用 FFM 和 winapi 从 java 控制 Windows 中的任务栏

问题描述 投票:0回答:1

我正在尝试控制任务栏,以便可以显示 JavaFX 应用程序中某些长时间运行的任务的进度。为了与 winapi 通信,我想使用新的 Java FFM API,它有一天会取代 JNI。

到目前为止,我能够成功创建

ITaskbarList3
实例的实例,但我无法对其调用任何方法。

我正在使用

jextract
从 winapi 中提取函数,以确保它们正确映射到 API:

jextract --output target/generated-sources/jextract -t "taskbar_test.gen" -l :shell32 -l :Explorerframe -l :ole32 -I "C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared" -I "C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um" -I "C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\km" -I "C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\km\crt" "C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um\ShObjIdl_core.h"

在下面的代码中,您可以通过我尝试最终调用函数来找到完整的应用程序

SetProgressValue
。我的问题是我无法成功调用函数
HrInit
,应该调用该函数来初始化
ITaskbarList

package taskbar_test;
import com.sun.glass.ui.Window;
import javafx.application.Application;
import javafx.stage.Stage;
import taskbar_test.gen.CLSID;
import taskbar_test.gen.IID;
import taskbar_test.gen.ITaskbarList;
import taskbar_test.gen.ITaskbarList3;
import taskbar_test.gen.ITaskbarList3Vtbl;
import taskbar_test.gen.ITaskbarListVtbl;
import taskbar_test.gen.ShObjIdl_core_h;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executors;
public class FxWinTaskbar extends Application {
     public static final String GUID_FORMAT = "{%s}";
     // CLSID of ITaskbarList3
     public static final String CLSID_CONST = "56FDF344-FD6D-11d0-958A-006097C9A090";
     // IID of ITaskbarList3
     public static final String IID_ITASKBAR_LIST = "56FDF342-FD6D-11d0-958A-006097C9A090";
     public static final String IID_ITASKBAR_LIST_3 = "EA1AFB91-9E28-4B86-90E9-9E9F8A5EEFAF";
     @Override
     public void start(Stage stage) throws Exception {
         var button = new javafx.scene.control.Button("Click Me");
         button.setOnAction(e -> handleClick());
         var root = new javafx.scene.layout.StackPane(button);
         var scene = new javafx.scene.Scene(root, 300, 200);
         stage.setTitle("JavaFX Stage with Button");
         stage.setScene(scene);
         stage.show();
     }
    void handleClick() {
        long rawHandle = Window.getWindows().getFirst().getRawHandle();
        Executors.newSingleThreadExecutor().submit(() -> {
            try (var arena = Arena.ofConfined()) {
                // 1. Initialize variables

                // https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-clsidfromstring#remarks
                // The CLSID format is {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}.
                var clsidString = arena.allocateFrom(GUID_FORMAT.formatted(CLSID_CONST), StandardCharsets.UTF_16LE);
                var iidITaskbarList = arena.allocateFrom(GUID_FORMAT.formatted(IID_ITASKBAR_LIST), StandardCharsets.UTF_16LE);
                var iidITaskbarList3 = arena.allocateFrom(GUID_FORMAT.formatted(IID_ITASKBAR_LIST_3), StandardCharsets.UTF_16LE);
                var clsid = CLSID.allocate(arena);
                var iidTaskbarList = IID.allocate(arena);
                var iidTaskbarList3 = IID.allocate(arena);
                var taskbarPtrToPtr = arena.allocate(ShObjIdl_core_h.C_POINTER);
                var taskbar3PtrToPtr = arena.allocate(ShObjIdl_core_h.C_POINTER);
                MemorySegment windowHandle = arena.allocate(ValueLayout.ADDRESS, rawHandle);

                // 2. Initialize COM
                int hr = ShObjIdl_core_h.CoInitializeEx(MemorySegment.NULL, ShObjIdl_core_h.COINIT_MULTITHREADED());
                if (hr != ShObjIdl_core_h.S_OK()) {
                    throw new RuntimeException("CoInitialize failed with error code: " + hr);
                }

                // 3. Create CLSID and IIDs
                hr = ShObjIdl_core_h.CLSIDFromString(clsidString, clsid);
                if (hr != ShObjIdl_core_h.S_OK()) {
                    throw new RuntimeException("CLSIDFromString failed with error code: " + hr);
                }

                hr = ShObjIdl_core_h.IIDFromString(iidITaskbarList, iidTaskbarList);
                if (hr != ShObjIdl_core_h.S_OK()) {
                    throw new RuntimeException("IIDFromString failed with error code: " + hr);
                }

                hr = ShObjIdl_core_h.IIDFromString(iidITaskbarList3, iidTaskbarList3);
                if (hr != ShObjIdl_core_h.S_OK()) {
                    throw new RuntimeException("IIDFromString failed with error code: " + hr);
                }

                // 4. Create instance of ITaskbarList
                hr = ShObjIdl_core_h.CoCreateInstance(clsid, MemorySegment.NULL, ShObjIdl_core_h.CLSCTX_ALL(), iidTaskbarList, taskbarPtrToPtr);
                if (hr != ShObjIdl_core_h.S_OK()) {
                    if (hr == ShObjIdl_core_h.REGDB_E_CLASSNOTREG()) {
                        System.out.println("COM class is not registered!");
                    }
                    throw new RuntimeException("CoCreateInstance failed with error code: " + hr);
                }
                // CoCreateInstance returns pointer to pointer to ITaskbarList so here we obtain the "inner" pointer
                var taskbarPtr = taskbarPtrToPtr.get(ValueLayout.ADDRESS, 0);
                // Use reinterpret method to have access to the actual ITaskbarList instance
                var taskbarListInstance = ITaskbarList.reinterpret(taskbarPtr, arena, _ -> {
                    System.out.println("Some cleanup...");
                });

                // 5. Obtain lpVtbl pointer from ITaskbarList
                MemorySegment taskbarListVtblPtr = ITaskbarList.lpVtbl(taskbarListInstance);
                // Use reinterpret method to have access to the actual ITaskbarListVtbl instance
                MemorySegment taskbarListVtbl = ITaskbarListVtbl.reinterpret(taskbarListVtblPtr, arena, _ -> {
                    System.out.println("Some cleanup...");
                });

                // 6. Get pointer to function HrInit to initialize ITaskbarList
                // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist-hrinit
                // Initializes the taskbar list object. This method must be called before any other ITaskbarList methods can be called.
                MemorySegment functionHrInitPtr = ITaskbarListVtbl.HrInit(taskbarListVtbl);
                hr = ITaskbarListVtbl.HrInit.invoke(functionHrInitPtr, taskbarListVtbl);
                if (hr != ShObjIdl_core_h.S_OK()) {
                    throw new RuntimeException("HrInit failed with error code: " + hr);
                }

                // 7. Create instance of ITaskbarList3
                hr = ShObjIdl_core_h.CoCreateInstance(clsid, MemorySegment.NULL, ShObjIdl_core_h.CLSCTX_ALL(), iidTaskbarList3, taskbar3PtrToPtr);
                if (hr != ShObjIdl_core_h.S_OK()) {
                    if (hr == ShObjIdl_core_h.REGDB_E_CLASSNOTREG()) {
                        System.out.println("COM class is not registered!");
                    }
                    throw new RuntimeException("CoCreateInstance failed with error code: " + hr);
                }
                // 8. Obtain a pointer to the instance
                var taskbar3Ptr = taskbar3PtrToPtr.get(ValueLayout.ADDRESS, 0);
                // Use reinterpret method to have access to the actual ITaskbarList3 instance
                var taskbarList3Instance = ITaskbarList3.reinterpret(taskbar3Ptr, arena, _ -> {
                    System.out.println("Some cleanup...");
                });

                // 9. Obtain lpVtbl pointer from ITaskbarList3
                MemorySegment taskbarList3VtblPtr = ITaskbarList3.lpVtbl(taskbarList3Instance);
                // Use reinterpret method to have access to the actual ITaskbarList3Vtbl instance
                MemorySegment taskbarList3Vtbl = ITaskbarList3Vtbl.reinterpret(taskbarList3VtblPtr, arena, _ -> {
                    System.out.println("Some cleanup...");
                });

                // 10. Set progress state to indeterminate
                MemorySegment functionSetProgressStatePtr = ITaskbarList3Vtbl.SetProgressState(taskbarList3Vtbl);
                hr = ITaskbarList3Vtbl.SetProgressState.invoke(functionSetProgressStatePtr, taskbarList3Vtbl, windowHandle, ShObjIdl_core_h.TBPF_INDETERMINATE());
                if (hr != ShObjIdl_core_h.S_OK()) {
                    throw new RuntimeException("SetProgressState failed with error code: " + hr);
                }

            } catch (Throwable ex) {
                ex.printStackTrace();

            } finally {
                ShObjIdl_core_h.CoUninitialize();
            }
        });
    }

    public static void main(String[] args) {
         launch(args);
     }
 }

我无法直接在界面

SetProgressState
上调用函数
ITaskbarList3
,因为生成的源没有能力这样做。相反,我必须手动获取
vtbl
结构并调用该结构上的函数。

如下图所示,

vtblPtr
的地址和
HrInit
的功能完全关闭。调用函数
HrInit
将会失败,因为它访问了错误的内存。

有人知道我做错了什么吗?

谢谢你。 彼得

Memory example

编辑:我已经应用了评论中的建议。现在,只创建了一个实例

ITaskbarList3
,并且所有函数都在其上调用。我还扩展了代码来模拟一些进度,看看是否可以设置进度。代码似乎正在运行,但不幸的是任务栏仍然没有任何变化。

package taskbar_test;

import com.sun.glass.ui.Window;
import javafx.application.Application;
import javafx.stage.Stage;
import taskbar_test.gen.CLSID;
import taskbar_test.gen.IID;
import taskbar_test.gen.ITaskbarList3;
import taskbar_test.gen.ITaskbarList3Vtbl;
import taskbar_test.gen.ShObjIdl_core_h;

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executors;

public class FxWinTaskbar extends Application {

    public static final String GUID_FORMAT = "{%s}";

    // CLSID of ITaskbarList3
    public static final String CLSID_CONST = "56FDF344-FD6D-11d0-958A-006097C9A090";
    // IID of ITaskbarList3
    public static final String IID_ITASKBAR_LIST_3 = "EA1AFB91-9E28-4B86-90E9-9E9F8A5EEFAF";

    @Override
    public void start(Stage stage) throws Exception {
        var button = new javafx.scene.control.Button("Click Me");
        button.setOnAction(e -> handleClick());

        var root = new javafx.scene.layout.StackPane(button);
        var scene = new javafx.scene.Scene(root, 300, 200);

        stage.setTitle("JavaFX Stage with Button");
        stage.setScene(scene);
        stage.show();
    }

    void handleClick() {
        long rawHandle = Window.getWindows().getFirst().getRawHandle();
        Executors.newSingleThreadExecutor().submit(() -> {
            try (var arena = Arena.ofConfined()) {
                // 1. Initialize variables

                // https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-clsidfromstring#remarks
                // The CLSID format is {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}.
                var clsidString = arena.allocateFrom(GUID_FORMAT.formatted(CLSID_CONST), StandardCharsets.UTF_16LE);
                var iidITaskbarList3 = arena.allocateFrom(GUID_FORMAT.formatted(IID_ITASKBAR_LIST_3), StandardCharsets.UTF_16LE);
                var clsid = CLSID.allocate(arena);
                var iidTaskbarList3 = IID.allocate(arena);
                var taskbar3PtrToPtr = arena.allocate(ShObjIdl_core_h.C_POINTER);
                MemorySegment windowHandle = arena.allocate(ValueLayout.ADDRESS, rawHandle);

                // 2. Initialize COM
                int hr = ShObjIdl_core_h.CoInitializeEx(MemorySegment.NULL, ShObjIdl_core_h.COINIT_MULTITHREADED());
                if (hr != ShObjIdl_core_h.S_OK()) {
                    throw new RuntimeException("CoInitialize failed with error code: " + hr);
                }

                // 3. Create CLSID and IIDs
                hr = ShObjIdl_core_h.CLSIDFromString(clsidString, clsid);
                if (hr != ShObjIdl_core_h.S_OK()) {
                    throw new RuntimeException("CLSIDFromString failed with error code: " + hr);
                }

                hr = ShObjIdl_core_h.IIDFromString(iidITaskbarList3, iidTaskbarList3);
                if (hr != ShObjIdl_core_h.S_OK()) {
                    throw new RuntimeException("IIDFromString failed with error code: " + hr);
                }

                // 4. Create instance of ITaskbarList3
                hr = ShObjIdl_core_h.CoCreateInstance(clsid, MemorySegment.NULL, ShObjIdl_core_h.CLSCTX_ALL(), iidTaskbarList3, taskbar3PtrToPtr);
                if (hr != ShObjIdl_core_h.S_OK()) {
                    if (hr == ShObjIdl_core_h.REGDB_E_CLASSNOTREG()) {
                        System.out.println("COM class is not registered!");
                    }
                    throw new RuntimeException("CoCreateInstance failed with error code: " + hr);
                }
                // 5. Obtain a pointer to the instance
                var taskbar3Ptr = taskbar3PtrToPtr.get(ValueLayout.ADDRESS, 0);
                // Use reinterpret method to have access to the actual ITaskbarList3 instance
                var taskbarList3Instance = taskbar3Ptr.reinterpret(ITaskbarList3.sizeof());

                // 6. Obtain lpVtbl pointer from ITaskbarList3
                MemorySegment taskbarList3VtblPtr = ITaskbarList3.lpVtbl(taskbarList3Instance);
                // Use reinterpret method to have access to the actual ITaskbarList3Vtbl instance
                MemorySegment taskbarList3Vtbl = taskbarList3VtblPtr.reinterpret(ITaskbarList3Vtbl.sizeof());

                // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist-hrinit
                // Initializes the taskbar list object. This method must be called before any other ITaskbarList methods can be called.
                MemorySegment functionHrInitPtr = ITaskbarList3Vtbl.HrInit(taskbarList3Vtbl);
                hr = ITaskbarList3Vtbl.HrInit.invoke(functionHrInitPtr, taskbarList3Instance);
                if (hr != ShObjIdl_core_h.S_OK()) {
                    throw new RuntimeException("HrInit failed with error code: " + hr);
                }

                // 7. Set progress state to indeterminate
                MemorySegment functionSetProgressStatePtr = ITaskbarList3Vtbl.SetProgressState(taskbarList3Vtbl);
                hr = ITaskbarList3Vtbl.SetProgressState.invoke(functionSetProgressStatePtr, taskbarList3Instance, windowHandle, ShObjIdl_core_h.TBPF_INDETERMINATE());
                if (hr != ShObjIdl_core_h.S_OK()) {
                    throw new RuntimeException("SetProgressState failed with error code: " + hr);
                }

                // 8. Simulate some progress
                for (int i = 0; i < 100; i+=20) {
                    System.out.println("Progress is: " + i);
                    MemorySegment functionSetProgressValuePtr = ITaskbarList3Vtbl.SetProgressValue(taskbarList3Vtbl);
                    hr = ITaskbarList3Vtbl.SetProgressValue.invoke(functionSetProgressValuePtr, taskbarList3Instance, windowHandle, i, 100);
                    if (hr != ShObjIdl_core_h.S_OK()) {
                        throw new RuntimeException("SetProgressValue failed with error code: " + hr);
                    }
                    Thread.sleep(500);

                }

                // 9. Reset progress state
                hr = ITaskbarList3Vtbl.SetProgressState.invoke(functionSetProgressStatePtr, taskbarList3Instance, windowHandle, ShObjIdl_core_h.TBPF_INDETERMINATE());
                if (hr != ShObjIdl_core_h.S_OK()) {
                    throw new RuntimeException("SetProgressState failed with error code: " + hr);
                }

            } catch (Throwable ex) {
                ex.printStackTrace();

            } finally {
                ShObjIdl_core_h.CoUninitialize();
            }
        });
    }

    public static void main(String[] args) {
        launch(args);
    }
}

java winapi javafx taskbar java-ffm
1个回答
0
投票

您编辑的代码更改让您更接近,首先使用 vtable+instance 调用每个回调。 jextract 生成的代码提供了在每个 OLE 接口 IXXX 的 vtable 中查找每个方法的调用。必须重新解释

MemorySegment
以匹配确切的内存大小,否则
MemorySegment
的边界检查将失败:

// instance is CoCreateInstance return value
MemorySegment instance = pointer.get(ValueLayout.ADDRESS, 0); 

// order of Method invoke() is vTable, instance, ... params
// IXXXVtbl.Method.invoke(IXXXVtbl.Method(IXXX.lpVtbl(instance)), instance, ...)
// Interpret correct memory bounds:
MemorySegment iUnknown = instance.reinterpret(IUnknown.sizeof());
MemorySegment vtabXXX = IUnknown.lpVtbl(iUnknown ).reinterpret(IXXXVtbl.sizeof());
IXXXVtbl.Method.invoke(vtabXXX, instance, ...)

Remy Lebeau 的评论消除了不必要的

CoCreateInstance

最后一个问题是你没有正确分配hWnd,可以使用以下方法将其解析为地址:

// Wrong: allocates new address containing hWnd
// MemorySegment windowHandle = arena.allocate(ValueLayout.ADDRESS, rawHandle);
// hWnd is address:
MemorySegment windowHandle = MemorySegment.ofAddress(rawHandle);

我无法运行您的 JavaFX 示例,但将外部内存调用打包到一个新方法并使用 JFrame 的 hWnd 进行测试 - 故意不在这里整理您的代码:

static void updateTaskBar(long rawHandle) throws InterruptedException {
    try (var arena = Arena.ofConfined()) {
        // 1. Initialize variables

        // https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-clsidfromstring#remarks
        // The CLSID format is {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}.
        var clsidString = arena.allocateFrom(GUID_FORMAT.formatted(CLSID_CONST), StandardCharsets.UTF_16LE);
        var iidITaskbarList3 = arena.allocateFrom(GUID_FORMAT.formatted(IID_ITASKBAR_LIST_3), StandardCharsets.UTF_16LE);
        var clsid = CLSID.allocate(arena);
        var iidTaskbarList3 = IID.allocate(arena);
        var taskbar3PtrToPtr = arena.allocate(Win_h.C_POINTER);
        // FiXED:
        // MemorySegment windowHandle = arena.allocate(ValueLayout.ADDRESS, rawHandle);
        MemorySegment windowHandle = MemorySegment.ofAddress(rawHandle);

        // 2. Initialize COM
        int hr = Win_h.CoInitializeEx(MemorySegment.NULL, Win_h.COINIT_MULTITHREADED());
        if (hr != Win_h.S_OK()) {
            throw new RuntimeException("CoInitialize failed with error code: " + hr);
        }

        // 3. Create CLSID and IIDs
        hr = Win_h.CLSIDFromString(clsidString, clsid);
        if (hr != Win_h.S_OK()) {
            throw new RuntimeException("CLSIDFromString failed with error code: " + hr);
        }

        hr = Win_h.IIDFromString(iidITaskbarList3, iidTaskbarList3);
        if (hr != Win_h.S_OK()) {
            throw new RuntimeException("IIDFromString failed with error code: " + hr);
        }

        // 4. Create instance of ITaskbarList3
        hr = Win_h.CoCreateInstance(clsid, MemorySegment.NULL, Win_h.CLSCTX_ALL(), iidTaskbarList3, taskbar3PtrToPtr);
        if (hr != Win_h.S_OK()) {
            if (hr == Win_h.REGDB_E_CLASSNOTREG()) {
                System.out.println("COM class is not registered!");
            }
            throw new RuntimeException("CoCreateInstance failed with error code: " + hr);
        }
        // 5. Obtain a pointer to the instance
        var taskbar3Ptr = taskbar3PtrToPtr.get(ValueLayout.ADDRESS, 0);
        // Use reinterpret method to have access to the actual ITaskbarList3 instance
        var taskbarList3Instance = taskbar3Ptr.reinterpret(ITaskbarList3.sizeof());

        // 6. Obtain lpVtbl pointer from ITaskbarList3
        MemorySegment taskbarList3VtblPtr = ITaskbarList3.lpVtbl(taskbarList3Instance);
        // Use reinterpret method to have access to the actual ITaskbarList3Vtbl instance
        MemorySegment taskbarList3Vtbl = taskbarList3VtblPtr.reinterpret(ITaskbarList3Vtbl.sizeof());

        // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist-hrinit
        // Initializes the taskbar list object. This method must be called before any other ITaskbarList methods can be called.
        MemorySegment functionHrInitPtr = ITaskbarList3Vtbl.HrInit(taskbarList3Vtbl);
        hr = ITaskbarList3Vtbl.HrInit.invoke(functionHrInitPtr, taskbarList3Instance);
        if (hr != Win_h.S_OK()) {
            throw new RuntimeException("HrInit failed with error code: " + hr);
        }

        // 7. Set progress state to indeterminate
        MemorySegment functionSetProgressStatePtr = ITaskbarList3Vtbl.SetProgressState(taskbarList3Vtbl);
        hr = ITaskbarList3Vtbl.SetProgressState.invoke(functionSetProgressStatePtr, taskbarList3Instance, windowHandle, Win_h.TBPF_INDETERMINATE());
        if (hr != Win_h.S_OK()) {
            throw new RuntimeException("SetProgressState failed with error code: " + hr);
        }

        // 8. Simulate some progress
        int max = 100;
        for (int i = 0; i < max; i++) {
            System.out.println(windowHandle+ " SetProgressValue "+i);
            MemorySegment functionSetProgressValuePtr = ITaskbarList3Vtbl.SetProgressValue(taskbarList3Vtbl);
            hr = ITaskbarList3Vtbl.SetProgressValue.invoke(functionSetProgressValuePtr, taskbarList3Instance, windowHandle, i, max);
            if (hr != Win_h.S_OK()) {
                throw new RuntimeException("SetProgressValue failed with error code: " + hr);
            }
            Thread.sleep(500);
        }
        // 9. Reset progress state
        hr = ITaskbarList3Vtbl.SetProgressState.invoke(functionSetProgressStatePtr, taskbarList3Instance, windowHandle, Win_h.TBPF_INDETERMINATE());
        if (hr != Win_h.S_OK()) {
            throw new RuntimeException("SetProgressState failed with error code: " + hr);
        }
    } finally {
        Win_h.CoUninitialize();
    }
}
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.