JNR-FFI crash after calling c function copying struct - java

I am trying to interface with a C library libzbc using jnr-ffi.
The first function call zbc_open works and returns a pointer to a open device. Then the next call to zbc_get_device_info causes a JVM crash.
What is the cause? How to solve it?
I think the error is somewhere in the interface of zbc or my passing parameters but searching for any documentation for JNR yielded no helpful results in google.The crash happens too if i omit the array part in the structure.
I am using the project of jnr-fuse as a starting point because my goal is to write a FUSE filesystem.
Crash log at pastebin
C functions:
void zbc_set_log_level( char *log_level )
int zbc_open (const char *filename, int flags, struct zbc_device **dev)
void zbc_get_device_info( struct zbc_device *dev, struct zbc_device_info * info)
C structure zbc_device_info:
enum zbc_dev_type zbd_type
enum zbc_dev_model zbd_model
char zbd_vendor_id [ZBC_DEVICE_INFO_LENGTH]
uint32_t zbd_flags
uint64_t zbd_sectors
uint32_t zbd_lblock_size
uint64_t zbd_lblocks
uint32_t zbd_pblock_size
uint64_t zbd_pblocks
uint64_t zbd_max_rw_sectors
uint32_t zbd_opt_nr_open_seq_pref
uint32_t zbd_opt_nr_non_seq_write_seq_pref
uint32_t zbd_max_nr_open_seq_req
JNR interface:
public interface zbc{
public void zbc_set_log_level(String level);
public int zbc_open(String filename,int flags, PointerByReference p);
public void zbc_get_device_info(Pointer dev,zbc_device_info info);
}
public static class zbc_device_info extends Struct {
int zbd_type;
int zbd_model;
byte zbd_vendor_id[]=new byte[32];
u_int32_t zbd_flags;
u_int64_t zbd_sectors;
u_int32_t zbd_lblock_size;
u_int64_t zbd_lblocks;
u_int32_t zbd_pblock_size;
u_int64_t zbd_pblocks;
u_int64_t zbd_max_rw_sectors;
u_int32_t zbd_opt_nr_open_seq_pref;
u_int32_t zbd_opt_nr_non_seq_write_seq_pref;
u_int32_t zbd_max_nr_open_seq_pref;
protected zbc_device_info(Runtime runtime) {
super(runtime);
}
}
The main function:
public static void main(String[] args) {
zbc z = LibraryLoader.create(zbc.class).load("zbc");
Runtime runtime=Runtime.getRuntime(z);
z.zbc_set_log_level("debug");
PointerByReference pr = new PointerByReference();
int ret=z.zbc_open("/temp/test.img", IO_RDWR,pr);
zbc_device_info info=new zbc_device_info(runtime);
z.zbc_get_device_info(pr.getValue(),info);
}
JVM crash:
#
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0x00007fa794300c70, pid=29106, tid=0x00007fa7bd4a4700
#
# JRE version: OpenJDK Runtime Environment (8.0_131-b11) (build 1.8.0_131-8u131-b11-2ubuntu1.16.04.3-b11)
# Java VM: OpenJDK 64-Bit Server VM (25.131-b11 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C [jffi6917022509387828651.so+0x5c70] jffi_releaseArrays+0x60
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /home/mmmm/jnr-fuse/hs_err_pid29106.log
#
# If you would like to submit a bug report, please visit:
# http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#

The declaration of Struct is incorrect. The fields are not declared. Try to use the following declaration:
package com.github.goto1134.libzbc;
import jnr.ffi.Runtime;
import jnr.ffi.Struct;
import jnr.ffi.util.EnumMapper.IntegerEnum;
public class zbc_device_info
extends Struct {
public static final int ZBC_DEVICE_INFO_LENGTH = 32;
public final Enum<zbc_dev_type> zbd_type = new Enum<>(zbc_dev_type.class);
public final Enum<zbc_dev_model> zbd_model = new Enum<>(zbc_dev_model.class);
public final UTF8String zbd_vendor_id = new UTF8String(ZBC_DEVICE_INFO_LENGTH);
public final Unsigned32 zbd_flags = new Unsigned32();
public final Unsigned64 zbd_sectors = new Unsigned64();
public final Unsigned32 zbd_lblock_size = new Unsigned32();
public final Unsigned64 zbd_lblocks = new Unsigned64();
public final Unsigned32 zbd_pblock_size = new Unsigned32();
public final Unsigned64 zbd_pblocks = new Unsigned64();
public final Unsigned64 zbd_max_rw_sectors = new Unsigned64();
public final Unsigned32 zbd_opt_nr_open_seq_pref = new Unsigned32();
public final Unsigned32 zbd_opt_nr_non_seq_write_seq_pref = new Unsigned32();
public final Unsigned32 zbd_max_nr_open_seq_req = new Unsigned32();
protected zbc_device_info(Runtime runtime) {
super(runtime);
}
public enum zbc_dev_type
implements IntegerEnum {
/**
* Unknown drive type.
*/
ZBC_DT_UNKNOWN(0x00),
/**
* Zoned block device (for kernels supporting ZBC/ZAC).
*/
ZBC_DT_BLOCK(0x01),
/**
* SCSI device.
*/
ZBC_DT_SCSI(0x02),
/**
* ATA device.
*/
ZBC_DT_ATA(0x03),
/**
* Fake device (emulation mode).
*/
ZBC_DT_FAKE(0x04);
private final int value;
zbc_dev_type(int value) {
this.value = value;
}
#Override
public int intValue() {
return value;
}
}
public enum zbc_dev_model
implements IntegerEnum {
/**
* Unknown drive model.
*/
ZBC_DM_DRIVE_UNKNOWN(0x00),
/**
* Host-aware drive model: the device type/signature is 0x00
* and the ZONED field of the block device characteristics VPD
* page B1h is 01b.
*/
ZBC_DM_HOST_AWARE(0x01),
/**
* Host-managed drive model: the device type/signature is 0x14/0xabcd.
*/
ZBC_DM_HOST_MANAGED(0x02),
/**
* Drive-managed drive model: the device type/signature is 0x00
* and the ZONED field of the block device characteristics VPD
* page B1h is 10b.
*/
ZBC_DM_DEVICE_MANAGED(0x03),
/**
* Standard block device: the device type/signature is 0x00
* and the ZONED field of the block device characteristics VPD
* page B1h is 00b.
*/
ZBC_DM_STANDARD(0x04);
private final int value;
zbc_dev_model(int value) {
this.value = value;
}
#Override
public int intValue() {
return value;
}
}
}
In hs logs there is a call stack that made the program crash. Can you provide it?

Related

In Linux and Mac , using JAVA , how can we get the processId and window title of other running programs [duplicate]

Window.getWindows();
Doesn't get all the open windows, just the java ones. Is there a way to get all the windows the operating system has open. I'm making a java taskbar.
There is no solution using core Java, but the problem can be solved using JNI or for an easier time, JNA. As noted in the comments, no solution (that I know of) will be platform independent.
For instance, this demo program uses JNA to enumerate all Windows with title in a Windows platform, but will also include windows that are not top-level windows and even windows that aren't visible:
import java.util.ArrayList;
import java.util.List;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.win32.StdCallLibrary;
public class EnumAllWindowNames {
static interface User32 extends StdCallLibrary {
User32 INSTANCE = (User32) Native.loadLibrary("user32", User32.class);
interface WNDENUMPROC extends StdCallCallback {
boolean callback(Pointer hWnd, Pointer arg);
}
boolean EnumWindows(WNDENUMPROC lpEnumFunc, Pointer userData);
int GetWindowTextA(Pointer hWnd, byte[] lpString, int nMaxCount);
Pointer GetWindow(Pointer hWnd, int uCmd);
}
public static List<String> getAllWindowNames() {
final List<String> windowNames = new ArrayList<String>();
final User32 user32 = User32.INSTANCE;
user32 .EnumWindows(new User32.WNDENUMPROC() {
#Override
public boolean callback(Pointer hWnd, Pointer arg) {
byte[] windowText = new byte[512];
user32.GetWindowTextA(hWnd, windowText, 512);
String wText = Native.toString(windowText).trim();
if (!wText.isEmpty()) {
windowNames.add(wText);
}
return true;
}
}, null);
return windowNames;
}
public static void main(String[] args) {
List<String> winNameList = getAllWindowNames();
for (String winName : winNameList) {
System.out.println(winName);
}
}
}
Addition to #Hovercraft Full Of Eels, if you also have windows which are titled with unreadable characters and get "?" printed for those characters, try to:
convert byte[] to string by using different encoding (as I understood)
see below:
String wText = Native.toString(windowText, "windows-1254").trim();
Possible encodings instead of "windows-1254 (which worked well for Turkish characters)":
UTF-8
UTF-16
ASCII
ISO-8859-1
...
You can now call into native Windows APIs to read the titles of non-java windows with pure-Java code using Foreign Memory API and code bindings generated by a compatible jextract.
This example works with the preview code in JDK19 release and is the equivalent to Hovercraft Full Of Eels answer:
/**
* Run with Windows JDK19 and preview enabled:
* jdk-19\bin\java --enable-native-access=ALL-UNNAMED --enable-preview -cp your.jar EnumWindowsDemo
*/
public class EnumWindowsDemo {
// Wide to Java String
private static String toJavaString(MemorySegment wide, int len) {
final CharBuffer cb = wide.asByteBuffer().order(ByteOrder.nativeOrder()).asCharBuffer();
return cb.limit(len).toString();
}
public static void main(String[] args) {
// TODO: You could use someParam to detect specific hWnd inside the callback
long someParam = 0x1234;
final int isRunning = User32_h.TRUE();
final int maxChars = 1024;
System.out.format("EnumWindows Panama 19 Demo Java %s%n", System.getProperty("java.runtime.version"));
HashMap<Long,String> windowNames = new HashMap<>();
try(MemorySession arena = MemorySession.openConfined()) {
// JDK20 try(Arena arena = Arena.openConfined()) {
MemorySegment lpString = arena.allocateArray(JAVA_CHAR, maxChars);
// Windows callback WNDENUMPROC(MemorySegment hwnd, long lParam)
WNDENUMPROC callback = (hWnd, lParam) -> {
int used = User32_h.GetWindowTextW(hWnd, lpString, maxChars);
if (used > 0) {
String text = toJavaString(lpString, used);
System.out.format("hWnd:%x lParam:%x => `%s`%n", hWnd.toRawLongValue(), lParam, text);
// JDK20: System.out.format("hWnd:%x lParam:%x => `%s`%n", hWnd.address(), lParam, text);
if (used >= maxChars) {
System.out.format("*** Oops: increase maxChars > %d, or call GetWindowTextLength first ***%n", used);
}
windowNames.put(hWnd.toRawLongValue(), text);
// JDK20: windowNames.put(hWnd.address(), text);
}
return isRunning;
};
final MemorySegment ewProc = WNDENUMPROC.allocate(callback, arena);
// JDK20: final MemorySegment ewProc = WNDENUMPROC.allocate(callback, arena.scope());
// Note that someParam is passed to the callback as lParam value
int rc = User32_h.EnumWindows(ewProc, someParam);
System.out.format("EnumWindows rc:%s%n", rc == 0 ? "FAIL":"OK");
if (rc == 0) {
throw new RuntimeException("EnumWindows failed rc="+rc);
}
}
System.out.format("Found windowNames: %s%n", windowNames.toString());
}
}
The standalone jextract is used to generate bindings for calling native code. You need to create a header User32.h referencing suitable Windows API headers:
echo #include ^<shlobj_core.h^> > User32.h
Run jextract to generate bindings for a suitable set of Windows APIs:
set JEXTRACT_ARGS=--source -luser32 User32.h -t somepackage --output java
set JEXTRACT_SYMBOLS=--include-macro TRUE --include-function EnumWindows --include-function GetWindowTextW --include-function GetWindowTextLengthW --include-typedef WNDENUMPROC
jextract %JEXTRACT_ARGS% %JEXTRACT_SYMBOLS%
You can also save symbols to a file for easy editing, and re-generate by replacing use of %JEXTRACT_SYMBOLS% above by #your.symbols.
See also:
Holger's answer Windows wide string to Java String conversion.
EnumWindows
GetWindowTextW

Is it possible to access the MS Windows certificate template name of an X509 certificate from Java?

Using BouncyCastle library and the code below it is possible to access the MS Windows certificate template information extension of a SSL certificate stored in MS Windows' trust/certificate store.
final KeyStore keyStore = KeyStore.getInstance("Windows-My", "SunMSCAPI");
keyStore.load(null, null);
final X509Certificate certificate = (X509Certificate)keyStore.aliases().nextElement();
// see https://learn.microsoft.com/en-us/windows/win32/seccertenroll/supported-extensions#template
final String XCN_OID_CERTIFICATE_TEMPLATE = "1.3.6.1.4.1.311.21.7";
final byte[] extensionValue = certificate.getExtensionValue(XCN_OID_CERTIFICATE_TEMPLATE);
final ASN1InputStream aIn = new ASN1InputStream(extensionValue);
ASN1Primitive asn1obj = aIn.readObject();
if (asn1obj instanceof DEROctetString)
{
final DEROctetString octets = (DEROctetString) asn1obj;
asn1obj = ASN1Primitive.fromByteArray(octets.getOctets());
}
final ASN1Sequence asn1seq = ASN1Sequence.getInstance(asn1obj);
final ASN1Encodable obj1 = asn1seq.getObjectAt(0);
final ASN1Primitive certificateTemplateOID = obj1.toASN1Primitive();
System.out.println(certificateTemplateOID.toString());
It will print out something like 1.3.6.1.4.1.311.21.8.... which is the OID of the certificate template.
Now I would like to know if there is a Java method or Java library which provides a mapping of this OID to the certificate template name (as a user-friendly string) - like it is done when you click on details of a certificate in Windows certificiate store:
Certificate template information in MS Windows
Additional links:
Windows certificate extension for certificate templates
Windows certificate extension for certificate templates - data structure
ObjectIdentifiers (OID) in PKI
the only way to get mapping for this OID value is to call CryptEnumOIDInfo function on a domain-joined machine. These OIDs are generated dynamically and stored in Active Directory. This function can read OIDs from local OID database and from Active Directory. Note, that you must call this function in AD forest where that certificate template is installed.
17.02.2021 update:
I specified wrong function. Actual function is CryptFindOIDInfo. The function accepts three parameters with the following values (for your given case):
dwKeyType -- must be 1 (CRYPT_OID_INFO_OID_KEY)
*pvKey -- must be a pointer to an ANSI string.
dwGroupId -- must be 9 (CRYPT_TEMPLATE_OID_GROUP_ID)
the function returns a pointer to a CRYPT_OID_INFO structure. pwszName will store friendly name of the requested OID. In a given case, union value of CRYPT_OID_INFO structure is DWORD dwValue. I have no idea how easy this is in Java.
As another option, if you can access .NET API from Java, you can try instantiating an Oid class using Oid(String) constructor:
var oid = new System.Security.Cryptography.Oid("1.3.6.1.4.1.311.21.8...");
Console.WriteLine(oid.FriendlyName); // this will output template display name.
As pointed out by #Crypt32, the function CryptFindOIDInfo has to be called via Java NativeAccess (JNA):
Here is the code for JNA Version 4.1.0:
final NativeLibrary crypt32 = NativeLibrary.getInstance("Crypt32");
final int CRYPT_OID_INFO_OID_KEY = 1;
final int XCN_CRYPT_TEMPLATE_OID_GROUP_ID = 9;
final Function functionCryptFindOIDInfo = crypt32.getFunction("CryptFindOIDInfo");
final String ptrPvKey = certificateTemplateOID.toString();
final Object[] argsCryptFindOIDInfo = new Object[] { CRYPT_OID_INFO_OID_KEY, ptrPvKey,
XCN_CRYPT_TEMPLATE_OID_GROUP_ID };
final CRYPT_OID_INFO result = (CRYPT_OID_INFO) functionCryptFindOIDInfo.invoke(CRYPT_OID_INFO.class,
argsCryptFindOIDInfo);
System.out.println(result.pwszName);
class CRYPT_OID_INFO.java
public class CRYPT_OID_INFO extends Structure
{
public int cbSize;
public String pszOID;
public WString pwszName;
public int dwGroupId;
public DUMMYUNION DUMMYUNIONNAME;
public CRYPT_DATA_BLOB ExtraInfo;
public String pwszCNGAlgid;
public String pwszCNGExtraAlgid;
#Override
public List<String> getFieldOrder()
{
final List<String> result = new ArrayList<>();
result.add("cbSize");
result.add("pszOID");
result.add("pwszName");
result.add("dwGroupId");
result.add("DUMMYUNIONNAME");
result.add("ExtraInfo");
result.add("pwszCNGAlgid");
result.add("pwszCNGExtraAlgid");
return result;
}
}
class DUMMYUNION .java
public class DUMMYUNION extends Union
{
public int dwValue;
public int Algid;
public int dwLength;
}
class CRYPT_DATA_BLOB
public class CRYPT_DATA_BLOB extends Structure
{
public int cbData;
public Pointer pbData;
#Override
public List<String> getFieldOrder()
{
final String[] array = new String[] { "cbData", "pbData" };
return Arrays.asList(array);
}
}

How do I get the os name from the client program?

There are parts of the program that are affected by OS type.
so i try to branch by os name as below.
when I run it locally,it works. but I run it as a client program after uploading it to the server, it seems that it can not get the os name.
Please let me know how can i get the os name from the client program.
thanks.
public void getOSName() {
String osName = System.getProperty("os.name")
if(!osName.trim().toUpperCase().equals("WINDOWS 10")){
run();
}else{
}
}
Checkout below util class for OS validation.
Using System properties : Due to this issue this approach may fail. Please see Java's “os.name” for Windows 10?
/**
* The Class OSValidator.
*/
public final class OSValidator {
/** The Constant OS. */
public static final String OS = System.getProperty("os.name").toLowerCase();
/**
* Checks if is windows 7.
*
* #return true, if is windows 7
*/
public static final boolean isWindows7() {
return (OS.indexOf("windows 7") >= 0);
}
/**
* Checks if is windows 10.
*
* #return true, if is windows 10
*/
public static final boolean isWindows10() {
return (OS.indexOf("windows 10") >= 0);
}
/**
* Checks if is mac.
*
* #return true, if is mac
*/
public static final boolean isMac() {
return (OS.indexOf("mac") >= 0);
}
}
Using SystemUtils – Apache Commons Lang
public String getOperatingSystemSystemUtils() {
String os = SystemUtils.OS_NAME;
// System.out.println("Using SystemUtils: " + os);
return os;
}

Kryo serialization caused fatal java runtime error with custom objects

I am trying to serialize hashmaps and collections of two custom classes (containing more hashmaps and collections).
Class1: NodeStorage.java
#NotNull
private final String id;
#Nullable
private String type;
#Nullable
private HashMap<String, String> properties;
Class2: RelationshipStorage.java
#NotNull
private final String id;
#Nullable
private String type;
#Nullable
private HashMap<String, String> properties;
#NotNull
private final NodeStorage startNode;
#NotNull
private final NodeStorage endNode;
The collections to serialize:
private HashMap<NodeStorage, NodeStorage> readsSetNode;
private HashMap<NodeStorage, NodeStorage> updateSetNode;
private ArrayList<NodeStorage> deleteSetNode;
private ArrayList<NodeStorage> createSetNode;
private HashMap<RelationshipStorage, RelationshipStorage> readsSetRelationship;
private HashMap<RelationshipStorage, RelationshipStorage> updateSetRelationship;
private ArrayList<RelationshipStorage> deleteSetRelationship;
private ArrayList<RelationshipStorage> createSetRelationship;
What I tried until now:
kryo.register(NodeStorage.class, 1);
kryo.register(RelationshipStorage.class, 2);
kryo.register(HashMap.class, mapSerializer);
mapSerializer.setKeyClass(NodeStorage.class, kryo.getSerializer(NodeStorage.class));
mapSerializer.setKeyClass(RelationshipStorage.class, kryo.getSerializer(RelationshipStorage.class));
mapSerializer.setValuesCanBeNull(false);
mapSerializer.setKeysCanBeNull(false);
listSerializer.setElementClass(NodeStorage.class, kryo.getSerializer(NodeStorage.class));
listSerializer.setElementClass(RelationshipStorage.class, kryo.getSerializer(RelationshipStorage.class));
listSerializer.setElementsCanBeNull(false);
public byte[] serialize()
{
ByteArrayOutputStream stream = new ByteArrayOutputStream();
Output output = new Output(stream);
mapSerializer.write(kryo, output, readsSetNode);
byte[] bytes = output.toBytes();
output.close();
return bytes;
}
I tried it with kryo.writeclassandobject but it didn't work as well. I get:
> > #
> # A fatal error has been detected by the Java Runtime Environment:
> #
> # SIGSEGV (0xb) at pc=0x00007f92f7f6efe0, pid=4637, tid=0x00007f92f94fd700
> #
> # JRE version: OpenJDK Runtime Environment (8.0_102-b14) (build 1.8.0_102-b14)
> # Java VM: OpenJDK 64-Bit Server VM (25.102-b14 mixed mode linux-amd64 compressed oops)
> # Problematic frame:
> # V [libjvm.so+0x787fe0]
> #
> # Core dump written
Full code at: https://github.com/Raycoms/thesis
Declaration:
private Kryo kryo = new Kryo();
MapSerializer mapSerializer = new MapSerializer();
CollectionSerializer listSerializer = new CollectionSerializer();
Kryo is not thread-safe, and practically any Java program has multiple threads going. For example, your log file shows that you have 25 threads running at the time of the crash. Even though your Kryo instance is private, the error is down deep in the bowels of Kryo, and there may be thread or JVM interactions there that are not under your control.
Try the thread-pool approach, quoted below, detailed in the Kryo readme:
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.pool.*;
KryoFactory factory = new KryoFactory() {
public Kryo create () {
Kryo kryo = new Kryo();
// configure kryo instance, customize settings
return kryo;
}
};
// Build pool with SoftReferences enabled (optional)
KryoPool pool = new KryoPool.Builder(factory).softReferences().build();
Then, in your serialize() function:
Kryo kryo = pool.borrow();
<... the rest of your code in serialize() before the return...>
pool.release(kryo);
return bytes;
As the README points out, you can alternatively use a callback:
// or use a callback to work with kryo - no need to borrow/release,
// that's done by `run`.
String value = pool.run(new KryoCallback() {
public String execute(Kryo kryo) {
return kryo.readObject(input, String.class);
}
});
OP's final code
Posted here from the OP's comment so that others can read it more easily. The OP was not able to use mapSerializer in the end, but was able to serialize using this code:
private byte[] serialize() {
KryoPool pool = new KryoPool.Builder(factory).softReferences().build();
Kryo kryo = pool.borrow();
Output output = new Output(0, 1024);
kryo.writeClassAndObject(output, readsSetNode);
byte[] bytes = output.toBytes();
output.close();
pool.release(kryo);
return bytes;
}
Note As #MartinGrotzke pointed out, per this issue, if you use kryo.register(class, id), make sure id>=10.

Get titles of Non-Java Windows in Java

Window.getWindows();
Doesn't get all the open windows, just the java ones. Is there a way to get all the windows the operating system has open. I'm making a java taskbar.
There is no solution using core Java, but the problem can be solved using JNI or for an easier time, JNA. As noted in the comments, no solution (that I know of) will be platform independent.
For instance, this demo program uses JNA to enumerate all Windows with title in a Windows platform, but will also include windows that are not top-level windows and even windows that aren't visible:
import java.util.ArrayList;
import java.util.List;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.win32.StdCallLibrary;
public class EnumAllWindowNames {
static interface User32 extends StdCallLibrary {
User32 INSTANCE = (User32) Native.loadLibrary("user32", User32.class);
interface WNDENUMPROC extends StdCallCallback {
boolean callback(Pointer hWnd, Pointer arg);
}
boolean EnumWindows(WNDENUMPROC lpEnumFunc, Pointer userData);
int GetWindowTextA(Pointer hWnd, byte[] lpString, int nMaxCount);
Pointer GetWindow(Pointer hWnd, int uCmd);
}
public static List<String> getAllWindowNames() {
final List<String> windowNames = new ArrayList<String>();
final User32 user32 = User32.INSTANCE;
user32 .EnumWindows(new User32.WNDENUMPROC() {
#Override
public boolean callback(Pointer hWnd, Pointer arg) {
byte[] windowText = new byte[512];
user32.GetWindowTextA(hWnd, windowText, 512);
String wText = Native.toString(windowText).trim();
if (!wText.isEmpty()) {
windowNames.add(wText);
}
return true;
}
}, null);
return windowNames;
}
public static void main(String[] args) {
List<String> winNameList = getAllWindowNames();
for (String winName : winNameList) {
System.out.println(winName);
}
}
}
Addition to #Hovercraft Full Of Eels, if you also have windows which are titled with unreadable characters and get "?" printed for those characters, try to:
convert byte[] to string by using different encoding (as I understood)
see below:
String wText = Native.toString(windowText, "windows-1254").trim();
Possible encodings instead of "windows-1254 (which worked well for Turkish characters)":
UTF-8
UTF-16
ASCII
ISO-8859-1
...
You can now call into native Windows APIs to read the titles of non-java windows with pure-Java code using Foreign Memory API and code bindings generated by a compatible jextract.
This example works with the preview code in JDK19 release and is the equivalent to Hovercraft Full Of Eels answer:
/**
* Run with Windows JDK19 and preview enabled:
* jdk-19\bin\java --enable-native-access=ALL-UNNAMED --enable-preview -cp your.jar EnumWindowsDemo
*/
public class EnumWindowsDemo {
// Wide to Java String
private static String toJavaString(MemorySegment wide, int len) {
final CharBuffer cb = wide.asByteBuffer().order(ByteOrder.nativeOrder()).asCharBuffer();
return cb.limit(len).toString();
}
public static void main(String[] args) {
// TODO: You could use someParam to detect specific hWnd inside the callback
long someParam = 0x1234;
final int isRunning = User32_h.TRUE();
final int maxChars = 1024;
System.out.format("EnumWindows Panama 19 Demo Java %s%n", System.getProperty("java.runtime.version"));
HashMap<Long,String> windowNames = new HashMap<>();
try(MemorySession arena = MemorySession.openConfined()) {
// JDK20 try(Arena arena = Arena.openConfined()) {
MemorySegment lpString = arena.allocateArray(JAVA_CHAR, maxChars);
// Windows callback WNDENUMPROC(MemorySegment hwnd, long lParam)
WNDENUMPROC callback = (hWnd, lParam) -> {
int used = User32_h.GetWindowTextW(hWnd, lpString, maxChars);
if (used > 0) {
String text = toJavaString(lpString, used);
System.out.format("hWnd:%x lParam:%x => `%s`%n", hWnd.toRawLongValue(), lParam, text);
// JDK20: System.out.format("hWnd:%x lParam:%x => `%s`%n", hWnd.address(), lParam, text);
if (used >= maxChars) {
System.out.format("*** Oops: increase maxChars > %d, or call GetWindowTextLength first ***%n", used);
}
windowNames.put(hWnd.toRawLongValue(), text);
// JDK20: windowNames.put(hWnd.address(), text);
}
return isRunning;
};
final MemorySegment ewProc = WNDENUMPROC.allocate(callback, arena);
// JDK20: final MemorySegment ewProc = WNDENUMPROC.allocate(callback, arena.scope());
// Note that someParam is passed to the callback as lParam value
int rc = User32_h.EnumWindows(ewProc, someParam);
System.out.format("EnumWindows rc:%s%n", rc == 0 ? "FAIL":"OK");
if (rc == 0) {
throw new RuntimeException("EnumWindows failed rc="+rc);
}
}
System.out.format("Found windowNames: %s%n", windowNames.toString());
}
}
The standalone jextract is used to generate bindings for calling native code. You need to create a header User32.h referencing suitable Windows API headers:
echo #include ^<shlobj_core.h^> > User32.h
Run jextract to generate bindings for a suitable set of Windows APIs:
set JEXTRACT_ARGS=--source -luser32 User32.h -t somepackage --output java
set JEXTRACT_SYMBOLS=--include-macro TRUE --include-function EnumWindows --include-function GetWindowTextW --include-function GetWindowTextLengthW --include-typedef WNDENUMPROC
jextract %JEXTRACT_ARGS% %JEXTRACT_SYMBOLS%
You can also save symbols to a file for easy editing, and re-generate by replacing use of %JEXTRACT_SYMBOLS% above by #your.symbols.
See also:
Holger's answer Windows wide string to Java String conversion.
EnumWindows
GetWindowTextW

Categories

Resources