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 Answers
Reset to default 1The 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:
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.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.Otherwise, it attempts to load the library by name in an OS-specific manner using
SymbolLookup::libraryLookup(String,Arena)
.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();
}
}
}
Vtbl.Method.invoke
with exact param order(vtable, instance, ...)
, where vtable is derived fromIXXXVtbl.Method(IXXX.lpVtbl(instance))
. See examples in Windows Shortcut COM/OLE. eg =>ITaskbarListVtbl.HrInit.invoke(functionHrInitPtr, taskbarListInstance)
– DuncG Commented Jan 20 at 12:06TaskBarList
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 callCoCreateInstance()
2 times. You need to call it 1 time and then callQueryInterface()
on the object. So, either 1) createITaskBarList
and then query forITaskBarList3
, or 2) just createITaskBarList3
by itself (as it inherits all ofITaskBarList
's methods) – Remy Lebeau Commented Jan 20 at 18:20