Franz Wong
Posted on September 23, 2024
Introduction
Here is a simple example about how to use JNA with MacOS API. I am not going to explain this, because I am still learning, but I keep code minimal to make it easier to understand and get the basic idea about how to do it for other purposes.
Most of the code is from Intellij-Community (Apache License).
Dependency
All we need is jna-platform
.
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.13.0</version>
</dependency>
Code
There are totally 4 source code files. ID
is copied from IntelliJ without modification. MyFoundation
and MyFoundationLibrary
are trimmed from Foundation
and FoundationLibrary
respectively.
Application.java
package org.example;
import com.sun.jna.Pointer;
import static org.example.MyFoundation.*;
public class Application {
public static void main(String[] args) throws Exception {
ID processInfoCls = getObjcClass("NSProcessInfo");
Pointer processInfoSel = createSelector("processInfo");
Pointer processNameSel = createSelector("processName");
ID processInfo = invoke(processInfoCls, processInfoSel);
ID processNameNSString = invoke(processInfo, processNameSel);
String processName = toStringViaUTF8(processNameNSString);
System.out.println(processName);
}
}
ID.java
package org.example;
import com.sun.jna.NativeLong;
public final class ID extends NativeLong {
public ID() {
}
public ID(long peer) {
super(peer);
}
public static final ID NIL = new ID(0L);
public boolean booleanValue() {
return intValue() != 0;
}
}
MyFoundationLibrary.java
public interface MyFoundationLibrary extends Library {
int kCFStringEncodingUTF8 = 0x08000100;
ID objc_getClass(String className);
Pointer sel_registerName(String selectorName);
int CFStringGetLength(ID theString);
byte CFStringGetCString(ID theString, byte[] buffer, int bufferSize, int encoding);
}
MyFoundation.java
package org.example;
import com.sun.jna.*;
import java.lang.reflect.Proxy;
import java.util.Collections;
public class MyFoundation {
private static final MyFoundationLibrary myFoundationLibrary;
private static final Function myObjcMsgSend;
static {
myFoundationLibrary = Native.load("Foundation", MyFoundationLibrary.class, Collections.singletonMap("jna.encoding", "UTF8"));
NativeLibrary nativeLibrary = ((Library.Handler) Proxy.getInvocationHandler(myFoundationLibrary)).getNativeLibrary();
myObjcMsgSend = nativeLibrary.getFunction("objc_msgSend");
}
public static ID getObjcClass(String className) {
return myFoundationLibrary.objc_getClass(className);
}
public static Pointer createSelector(String s) {
return myFoundationLibrary.sel_registerName(s);
}
private static Object [] prepInvoke(ID id, Pointer selector, Object[] args) {
Object[] invokArgs = new Object[args.length + 2];
invokArgs[0] = id;
invokArgs[1] = selector;
System.arraycopy(args, 0, invokArgs, 2, args.length);
return invokArgs;
}
public static ID invoke(final ID id, final Pointer selector, Object... args) {
// objc_msgSend is called with the calling convention of the target method
// on x86_64 this does not make a difference, but arm64 uses a different calling convention for varargs
// it is therefore important to not call objc_msgSend as a vararg function
return new ID(myObjcMsgSend.invokeLong(prepInvoke(id, selector, args)));
}
public static String toStringViaUTF8(ID cfString) {
if (ID.NIL.equals(cfString)) return null;
int lengthInChars = myFoundationLibrary.CFStringGetLength(cfString);
int potentialLengthInBytes = 3 * lengthInChars + 1; // UTF8 fully escaped 16 bit chars, plus nul
byte[] buffer = new byte[potentialLengthInBytes];
byte ok = myFoundationLibrary.CFStringGetCString(cfString, buffer, buffer.length, MyFoundationLibrary.kCFStringEncodingUTF8);
if (ok == 0) throw new RuntimeException("Could not convert string");
return Native.toString(buffer);
}
}
💖 💪 🙅 🚩
Franz Wong
Posted on September 23, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.