最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Control taskbar in Windows from java using FFM and winapi - Stack Overflow

programmeradmin2浏览0评论

I'm trying to control a taskbar so I can show a progress of some long running task in the JavaFX application. For communicating with winapi I want to use the new Java FFM API, which should replace the JNI one day.

So far I was able successfully create instance of ITaskbarList3 instance, but I'm not able to call any method on it.

I'm using jextract to extract functions from winapi to make sure they are correctly mapped to 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"

In the code below, you can find complete application with my attempt to in the end call function SetProgressValue. My issue is that I'm not able to successfully call function HrInit which should be called to initialize the 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

                // 
                // 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
                // 
                // 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);
     }
 }

I'm not able to call the function SetProgressState directly on interface ITaskbarList3 because generated sources does not have the ability to do so. Instead I have to manually obtain vtbl structure and call the function on this structure.

As you can see on the picture below, the address of vtblPtr and function for HrInit are completely off. Calling function HrInit will fail, because it is accesssing wrong memory.

Does anyone have idea what am I doing wrong?

Thank you. Petr

Edit: I have applied suggestions from comments. Now, there is only one instance ITaskbarList3 created and all functions are called on it. I have also extended the code to simulate some progress to see if it can set the progress. The code seems to be running, but unfortunately the taskbar is still without any changes.

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

                // 
                // 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());

                // 
                // 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);
    }
}

I'm trying to control a taskbar so I can show a progress of some long running task in the JavaFX application. For communicating with winapi I want to use the new Java FFM API, which should replace the JNI one day.

So far I was able successfully create instance of ITaskbarList3 instance, but I'm not able to call any method on it.

I'm using jextract to extract functions from winapi to make sure they are correctly mapped to 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"

In the code below, you can find complete application with my attempt to in the end call function SetProgressValue. My issue is that I'm not able to successfully call function HrInit which should be called to initialize the 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);
     }
 }

I'm not able to call the function SetProgressState directly on interface ITaskbarList3 because generated sources does not have the ability to do so. Instead I have to manually obtain vtbl structure and call the function on this structure.

As you can see on the picture below, the address of vtblPtr and function for HrInit are completely off. Calling function HrInit will fail, because it is accesssing wrong memory.

Does anyone have idea what am I doing wrong?

Thank you. Petr

Edit: I have applied suggestions from comments. Now, there is only one instance ITaskbarList3 created and all functions are called on it. I have also extended the code to simulate some progress to see if it can set the progress. The code seems to be running, but unfortunately the taskbar is still without any changes.

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);
    }
}

Share Improve this question edited Jan 20 at 20:43 Petr Štechmüller asked Jan 20 at 10:59 Petr ŠtechmüllerPetr Štechmüller 2311 silver badge9 bronze badges 4
  • 2 You need to call each Vtbl.Method.invoke with exact param order (vtable, instance, ...), where vtable is derived from IXXXVtbl.Method(IXXX.lpVtbl(instance)). See examples in Windows Shortcut COM/OLE. eg => ITaskbarListVtbl.HrInit.invoke(functionHrInitPtr, taskbarListInstance) – DuncG Commented Jan 20 at 12:06
  • Thank you for your advice. The code now runs without any errors, but unfortunately I do not see any changes in taskbar. Can I post edited code into the original post so you can check it? – Petr Štechmüller Commented Jan 20 at 12:56
  • 3 Why are you creating 2 completely separate TaskBarList instances? You need only 1 instance. To work with 2 different interfaces on the same object, you create the object first and then query it for the interfaces you want to use from it. You don't need to call CoCreateInstance() 2 times. You need to call it 1 time and then call QueryInterface() on the object. So, either 1) create ITaskBarList and then query for ITaskBarList3, or 2) just create ITaskBarList3 by itself (as it inherits all of ITaskBarList's methods) – Remy Lebeau Commented Jan 20 at 18:20
  • I have updated the code. Unfortunately it still does not work. The taskbar is still without any change. – Petr Štechmüller Commented Jan 20 at 20:44
Add a comment  | 

2 Answers 2

Reset to default 1

The code changes you have edited get you much closer, firstly by calling each callback with vtable+instance. The jextract generated code gives calls to lookup each Method in the vtable of each OLE interface IXXX. The MemorySegment must be reinterpreted to match the exact memory sizes or the bounds checking of MemorySegment will fail:

// 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's comment eliminates the unnecessary CoCreateInstance.

The last issue is that you have not assigned hWnd correctly, it can be resolved as an address using:

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

I could not run your JavaFX example, but packaged the foreign memory calls to a new method and tested with a hWnd of a JFrame - deliberately not tidying your code here:

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();
    }
}

The accepted answer already answers your question, but I wanted to present a different approach. Based on your code and the solution, working with COM objects directly via FFM does not seem pleasant. Even when the FFM code is generated via jextract. If all you want to do is set the progress state and value in the Windows taskbar, then it may be easier to create a small native library that exposes only the functionality you need. Then you would create the FFM code for your custom library instead (possibly using jextract, though I didn't use that tool in the example below).

Since this is in the context of a JavaFX application, writing your own native library shouldn't make deploying your application significantly more complex. You can use a JMOD file to bundle the native library with a runtime image created via jlink. Or you could bundle the library in some other way via jpackage or maybe even a GraalVM native image.

Example

Native library

Here's a small native library written in C++ that provides an API to create, set progress state of, set progress value of, and release an ITaskbarList3. I'm not an expert with C++ nor COM, so there may be improvements that can be made or mistakes that should be fixed (let me know if there are any).

Note the TbProg class is inspired by https://stackoverflow.com/a/15002979/6395627.

tbprog.hpp

#define EXPORT_C extern "C" __declspec(dllexport)

#include <ShObjIdl.h>

class TbProg final
{
public:
  TbProg();
  TbProg(HWND hwnd);
  ~TbProg();

  HRESULT setState(TBPFLAG flag);
  HRESULT setValue(ULONGLONG workDone, ULONGLONG totalWork);

private:
  HWND m_hwnd;
  ITaskbarList3 *m_tbList;
  HRESULT m_initResult;

  bool init();
};

EXPORT_C TbProg *createTbProg();
EXPORT_C TbProg *createTbProgForWindow(HWND hwnd);
EXPORT_C HRESULT setTbProgState(TbProg *prog, TBPFLAG flag);
EXPORT_C HRESULT setTbProgValue(TbProg *prog, ULONGLONG workDone, ULONGLONG totalWork);
EXPORT_C void releaseTbProg(TbProg *prog);

tbprog.cpp

#include "tbprog.hpp"

TbProg::TbProg() : TbProg(GetForegroundWindow()) {}

TbProg::TbProg(HWND hwnd) : m_hwnd(hwnd), m_tbList(nullptr), m_initResult(S_OK) {}

TbProg::~TbProg()
{
  if (SUCCEEDED(m_initResult) && m_tbList != nullptr)
  {
    m_tbList->Release();
    CoUninitialize();
  }
}

HRESULT TbProg::setState(TBPFLAG flag)
{
  if (init())
    return m_tbList->SetProgressState(m_hwnd, flag);
  else
    return m_initResult;
}

HRESULT TbProg::setValue(ULONGLONG workDone, ULONGLONG totalWork)
{
  if (init())
    return m_tbList->SetProgressValue(m_hwnd, workDone, totalWork);
  else
    return m_initResult;
}

bool TbProg::init()
{
  if (FAILED(m_initResult))
    return false;
  if (m_tbList != nullptr)
    return true;

  m_initResult = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  if (FAILED(m_initResult))
    return false;

  ITaskbarList3 *tbList = nullptr;
  m_initResult = CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_ITaskbarList3, (void **)&tbList);
  if (SUCCEEDED(m_initResult))
  {
    m_initResult = tbList->HrInit();
    if (FAILED(m_initResult))
      tbList->Release();
  }

  if (FAILED(m_initResult))
  {
    CoUninitialize();
    return false;
  }
  m_tbList = tbList;
  return true;
}

TbProg *createTbProg()
{
  return new TbProg();
}

TbProg *createTbProgForWindow(HWND hwnd)
{
  return new TbProg(hwnd);
}

HRESULT setTbProgState(TbProg *prog, TBPFLAG flag)
{
  return prog->setState(flag);
}

HRESULT setTbProgValue(TbProg *prog, ULONGLONG workDone, ULONGLONG totalWork)
{
  return prog->setValue(workDone, totalWork);
}

void releaseTbProg(TbProg *prog)
{
  delete prog;
}

Java code

And here's the Java code using the above native library.

TbProgLib

This class wraps the FFM code to interact with the native library. It looks for the library in the following way:

  1. If the tbprog.lib.path system property is set, it attempts to load the library from the property's value which must be a path to the DLL.

  2. Otherwise, if the TBPROG_LIB environment variable is set, it attempts to load the library from the variable's value which must be a path to the DLL.

  3. Otherwise, it attempts to load the library by name in an OS-specific manner using SymbolLookup::libraryLookup(String,Arena).

  4. Finally, it attempts to load the library by name in a Java-specific manner using System::loadLibrary(String).

package com.example;

import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static java.lang.foreign.ValueLayout.JAVA_LONG;

import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.invoke.MethodHandle;
import java.nio.file.Path;
import java.util.Objects;

public final class TbProgLib {

  private TbProgLib() {}

  /* **************************************************************************
   *                                                                          *
   * API                                                                      *
   *                                                                          *
   ****************************************************************************/

  public static MemorySegment createTbProg() {
    try {
      return (MemorySegment) CREATE_TBPROG.invokeExact();
    } catch (Throwable t) {
      throw new RuntimeException(t);
    }
  }

  public static MemorySegment createTbProgForWindow(long hwnd) {
    try {
      return (MemorySegment) CREATE_TBPROG_FOR_WINDOW.invokeExact(hwnd);
    } catch (Throwable t) {
      throw new RuntimeException(t);
    }
  }

  public static int setTbProgState(MemorySegment prog, int flag) {
    Objects.requireNonNull(prog);
    try {
      return (int) SET_TBPROG_STATE.invokeExact(prog, flag);
    } catch (Throwable t) {
      throw new RuntimeException(t);
    }
  }

  public static int setTbProgValue(MemorySegment prog, long workDone, long totalWork) {
    Objects.requireNonNull(prog);
    try {
      return (int) SET_TBPROG_VALUE.invokeExact(prog, workDone, totalWork);
    } catch (Throwable t) {
      throw new RuntimeException(t);
    }
  }

  public static void releaseTbProg(MemorySegment prog) {
    Objects.requireNonNull(prog);
    try {
      RELEASE_TBPROG.invokeExact(prog);
    } catch (Throwable t) {
      throw new RuntimeException(t);
    }
  }

  /* **************************************************************************
   *                                                                          *
   * FFM Handling                                                             *
   *                                                                          *
   ****************************************************************************/

  private static final String PROP_KEY = "tbprog.lib.path";
  private static final String ENV_KEY = "TBPROG_LIB";
  private static final String LIB_NAME = "tbprog";

  private static final MethodHandle CREATE_TBPROG;
  private static final MethodHandle CREATE_TBPROG_FOR_WINDOW;
  private static final MethodHandle SET_TBPROG_STATE;
  private static final MethodHandle SET_TBPROG_VALUE;
  private static final MethodHandle RELEASE_TBPROG;

  static {
    var symbols = loadLibrary();
    var linker = Linker.nativeLinker();

    CREATE_TBPROG = linker.downcallHandle(symbols.findOrThrow("createTbProg"), fd(ADDRESS));
    CREATE_TBPROG_FOR_WINDOW =
        linker.downcallHandle(symbols.findOrThrow("createTbProgForWindow"), fd(ADDRESS, JAVA_LONG));
    SET_TBPROG_STATE = linker.downcallHandle(
        symbols.findOrThrow("setTbProgState"), fd(JAVA_INT, ADDRESS, JAVA_INT));
    SET_TBPROG_VALUE = linker.downcallHandle(
        symbols.findOrThrow("setTbProgValue"), fd(JAVA_INT, ADDRESS, JAVA_LONG, JAVA_LONG));
    RELEASE_TBPROG = linker.downcallHandle(symbols.findOrThrow("releaseTbProg"), fdVoid(ADDRESS));
  }

  private static SymbolLookup loadLibrary() {
    var value = System.getProperty(PROP_KEY, System.getenv(ENV_KEY));
    if (value != null && !value.isBlank()) {
      return SymbolLookup.libraryLookup(Path.of(value), Arena.global());
    }

    Throwable lookupError;
    try {
      return SymbolLookup.libraryLookup(LIB_NAME, Arena.global());
    } catch (Throwable t) {
      lookupError = t;
    }

    try {
      System.loadLibrary(LIB_NAME);
    } catch (Throwable t) {
      t.addSuppressed(lookupError);
      throw new RuntimeException("Unable to load native library: " + LIB_NAME, t);
    }
    return SymbolLookup.loaderLookup();
  }

  private static FunctionDescriptor fd(MemoryLayout retLayout, MemoryLayout... argLayouts) {
    return FunctionDescriptor.of(retLayout, argLayouts);
  }

  private static FunctionDescriptor fdVoid(MemoryLayout... argLayouts) {
    return FunctionDescriptor.ofVoid(argLayouts);
  }
}

TbProg.java

A class for managing a pointer to a native TbProg instance. It also exposes a nicer API to use.

package com.example;

import java.lang.foreign.MemorySegment;
import java.util.EnumSet;
import java.util.Set;
import javafx.application.Platform;

public final class TbProg implements AutoCloseable {

  private static void requireFxThread() {
    if (!Platform.isFxApplicationThread()) {
      throw new WrongThreadException("Operation must be performed on 'JavaFX Application Thread'");
    }
  }

  private final MemorySegment prog;
  private boolean released;

  public TbProg() {
    requireFxThread();
    prog = TbProgLib.createTbProg().asReadOnly();
  }

  public TbProg(long hwnd) {
    requireFxThread();
    prog = TbProgLib.createTbProgForWindow(hwnd).asReadOnly();
  }

  public void setState(State state) {
    Objects.requireNonNull(state);
    requireFxThread();
    requireNotReleased();

    int hr = TbProgLib.setTbProgState(prog, state.flag);
    checkResult(hr);
  }

  public void setValue(long workDone, long totalWork) {
    requireFxThread();
    requireNotReleased();

    int hr = TbProgLib.setTbProgValue(prog, workDone, totalWork);
    checkResult(hr);
  }

  public void release() {
    requireFxThread();
    if (!released) {
      released = true;
      TbProgLib.releaseTbProg(prog);
    }
  }

  @Override
  public void close() {
    release();
  }

  private void requireNotReleased() {
    if (released) {
      throw new IllegalStateException("released");
    }
  }

  private void checkResult(int hresult) {
    if (hresult < 0) {
      throw new RuntimeException("Operation failed with error code = " + hresult);
    }
  }

  /* **************************************************************************
   *                                                                          *
   * State enum                                                               *
   *                                                                          *
   ****************************************************************************/

  public enum State {
    NO_PROGRESS(0x0),
    INDETERMINATE(0x1),
    NORMAL(0x2),
    ERROR(0x4),
    PAUSED(0x8);

    private final int flag;

    State(int flag) {
      this.flag = flag;
    }

    public int flag() {
      return flag;
    }
  }
}

Main.java

And here is a small JavaFX application demonstrating the use of everything previously shown.

package com.example;

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Subscription;

public class Main extends Application {

  @Override
  public void start(Stage primaryStage) throws Exception {
    var button = new Button("Run task...");
    button.setOnAction(e -> {
      e.consume();

      var prog = new TbProg();
      var task = new DummyTask();
      var sub = linkWorkerWithTaskbar(task, prog);

      Thread.ofVirtual().start(task);

      var dialog = createWorkerDialog(task);
      dialog.initOwner(primaryStage);
      dialog.showAndWait();

      sub.unsubscribe();
      prog.release();
      if (task.isRunning()) {
        task.cancel();
      }
    });

    var scene = new Scene(new StackPane(button), 500, 300);
    primaryStage.setScene(scene);
    primaryStage.show();
  }

  private <R> Dialog<R> createWorkerDialog(Worker<R> worker) {
    var dialog = new Dialog<R>();
    dialog.titleProperty().bind(worker.titleProperty());
    dialog.contentTextProperty().bind(worker.messageProperty());
    dialog.setResultConverter(bt -> bt == ButtonType.OK ? worker.getValue() : null);

    var indicator = new ProgressIndicator();
    indicator.progressProperty().bind(worker.progressProperty());
    dialog.setGraphic(indicator);

    var pane = dialog.getDialogPane();
    pane.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);

    var okButton = pane.lookupButton(ButtonType.OK);
    var cancelButton = pane.lookupButton(ButtonType.CANCEL);
    worker.stateProperty().subscribe(state -> {
      switch (state) {
        case READY -> {
          okButton.setDisable(true);
          cancelButton.setDisable(true);
        }
        case SCHEDULED, RUNNING -> {
          okButton.setDisable(true);
          cancelButton.setDisable(false);
        }
        case SUCCEEDED, CANCELLED, FAILED -> {
          okButton.setDisable(false);
          cancelButton.setDisable(true);
        }
      }
    });

    return dialog;
  }

  private Subscription linkWorkerWithTaskbar(Worker<?> worker, TbProg prog) {
    var stateSub = worker.stateProperty().subscribe(state -> {
      switch (state) {
        case SCHEDULED -> prog.setState(TbProg.State.INDETERMINATE);
        case FAILED -> prog.setState(TbProg.State.ERROR);
        default -> {}
      }
    });
    var progSub = worker.progressProperty().subscribe((oldVal, newVal) -> {
      double oldProg = oldVal.doubleValue();
      double newProg = newVal.doubleValue();
      if (oldProg != -1 && newProg == -1) {
        prog.setState(TbProg.State.INDETERMINATE);
      } else if (oldProg == -1 && newProg != -1) {
        prog.setState(TbProg.State.NORMAL);
      }

      if (newProg != -1) {
        long workDone = (long) worker.getWorkDone();
        long totalWork = (long) worker.getTotalWork();
        prog.setValue(workDone, totalWork);
      }
    });
    return Subscription.combine(stateSub, progSub, () -> prog.setState(TbProg.State.NO_PROGRESS));
  }

  static class DummyTask extends Task<Void> {

    private static final int TOTAL_WORK = 100;

    DummyTask() {
      updateTitle("Dummy Task");
    }

    @Override
    protected Void call() throws Exception {
      updateMessage("Initializing...");
      Thread.sleep(3_000L);

      updateProgress(0, TOTAL_WORK);
      for (int i = 1; i <= TOTAL_WORK; i++) {
        updateMessage("Processing item %03d of %03d...".formatted(i, TOTAL_WORK));
        Thread.sleep(100L);
        updateProgress(i, TOTAL_WORK);
      }

      return null;
    }

    @Override
    protected void succeeded() {
      updateMessage("Done!");
    }

    @Override
    protected void failed() {
      updateMessage("Failed!");
      getException().printStackTrace();
    }
  }
}
发布评论

评论列表(0)

  1. 暂无评论