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