-
Notifications
You must be signed in to change notification settings - Fork 0
Foreign Function Interface
Karstian Lee edited this page Dec 31, 2023
·
1 revision
UXGL provides a set of simple and clear API ("UXGL FFI") to easily integrate native code. Let's begin with the basis, UXGL "Handle Mapping"!.
package org.example.desktop.foreign;
import unrefined.app.Logger;
import unrefined.runtime.DesktopRuntime;
import unrefined.util.foreign.Foreign;
import unrefined.util.foreign.Symbol;
public class ForeignFunction {
public static void main(String[] args) throws NoSuchMethodException {
DesktopRuntime.setup(args); // Initialize the UXGL runtime environment
// Type mapping:
// Java void -> void
// Java boolean -> uint8_t
// Java byte -> int8_t
// Java char -> uint16_t
// Java short -> int16_t
// Java int -> int32_t
// Java long -> int64_t
// Java float -> float
// Java double -> double
// Java unrefined.nio.Pointer -> pointer
// Note that unrefined.nio.Pointer is used just as a "marker class",
// for pointer types, the method parameter type in Java side should be long.class
// Varargs supported for the downcall handle, but the upcall stub not
Foreign foreign = Foreign.getInstance(); // Get the platform-dependent FFI factory
Symbol upcallStub = foreign.upcallStub(ForeignFunction.class.getDeclaredMethod("helloFFI"),
void.class); // Create a upcall stub, which a closure/callback will call Java method from a corresponding C function pointer
Symbol downcallHandle = foreign.downcallHandle(upcallStub.address(), void.class); // Create a downcall handle, which calls C function from Java
downcallHandle.invokeVoid(); // Java -> downcall -> C -> upcall -> Java
//upcallStub.invokeVoid(); // In fact, you can directly invoke the upcall stub without create a new downcall handle
}
public static void helloFFI() {
Logger.defaultInstance().info("UXGL FFI", "C call Java from C from Java");
}
}
With some limitations, you can dynamically call some native functions as fast as hand-writing JNI. (Okay, a bit slower than JNI indeed) Works similar with JNA Direct Mapping, that's why I named it UXGL "Direct Mapping"!
package org.example.desktop.foreign;
import unrefined.app.Logger;
import unrefined.runtime.DesktopRuntime;
import unrefined.util.foreign.Foreign;
public class FastForeignCall {
public static void main(String[] args) {
DesktopRuntime.setup(args); // Initialize the UXGL runtime environment
Foreign foreign = Foreign.getInstance(); // Get the platform-dependent FFI factory
foreign.register(FastForeignCall.class); // Register all the native functions in specified class
final double x = 1;
long time;
time = System.currentTimeMillis();
for (int i = 0; i < 10000000; i ++) {
Math.sin(x);
}
time = System.currentTimeMillis() - time;
Logger.defaultInstance().info("UXGL FFI", "java.lang.Math#sin (10m call) time: " + time);
time = System.currentTimeMillis();
for (int i = 0; i < 10000000; i ++) {
sin(x);
}
time = System.currentTimeMillis() - time;
Logger.defaultInstance().info("UXGL FFI", "<cmath> sin (UXGL FFI Direct Mapping, 10m call) time: " + time);
}
// Only Java primitive types allowed! The pointer type and varargs are unsupported!
// Java void -> void
// Java boolean -> uint8_t
// Java byte -> int8_t
// Java char -> uint16_t
// Java short -> int16_t
// Java int -> int32_t
// Java long -> int64_t
// Java float -> float
// Java double -> double
public static native double sin(double x);
}
UXGL "Library Mapping"! Dynamic proxy objects, working just like JNA! Note that type mapping as the same of UXGL "Handle Mapping", different from JNA.
package org.example.desktop.foreign;
import unrefined.nio.Pointer;
import unrefined.runtime.DesktopRuntime;
import unrefined.util.UnexpectedError;
import unrefined.util.foreign.Foreign;
import unrefined.util.foreign.Library;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
public class LibraryMapping {
public interface CLibrary extends Library {
void printf(long format, int... args); // Varargs supported!
}
public static void main(String[] args) {
DesktopRuntime.setup(args); // Initialize the UXGL runtime environment
Foreign foreign = Foreign.getInstance(); // Get the platform-dependent FFI factory
Random random = ThreadLocalRandom.current();
int a = random.nextInt();
int b = random.nextInt();
CLibrary c = foreign.downcallProxy(CLibrary.class);
try (Pointer format = Pointer.allocateDirect("SUM (%d, %d) = %d")) {
c.printf(format.address(), a, b, a + b);
}
catch (IOException e) {
throw new UnexpectedError(e);
}
}
}
Symbols are internally managed by UXGL. Load the library, get the symbol, and do whatever you want!
package org.example.desktop.foreign;
import unrefined.app.Logger;
import unrefined.runtime.DesktopRuntime;
import unrefined.util.StringCompat;
import unrefined.util.foreign.Foreign;
import unrefined.util.foreign.Redirect;
import unrefined.util.foreign.Symbol;
import java.io.IOException;
public class SymbolManagement {
public static final boolean IS_WINDOWS = StringCompat.stripLeading(System.getProperty("os.name")).startsWith("Windows");
public static final boolean IS_LINUX = System.getProperty("os.name").equalsIgnoreCase("linux");
public static void main(String[] args) throws IOException {
DesktopRuntime.setup(args); // Initialize the UXGL runtime environment
Foreign foreign = Foreign.getInstance(); // Get the platform-dependent FFI factory
foreign.loadLibrary(foreign.mapLibraryName(IS_WINDOWS ? "Kernel32" : (IS_LINUX ? "libc.so.6" : "c")),
Foreign.Loader.LINKER); // Load the library for symbols we want.
// The symbols whether ClassLoader-managed or linker-managed depend on the loader type.
// Also, Loader#CLASS follows java.library.path, Loader#LINKER follows native linker, to search for libraries.
Symbol getpid = foreign.downcallHandle(foreign.getSymbolAddress(IS_WINDOWS ? "GetCurrentProcessId" : "getpid"),
IS_WINDOWS ? int.class : long.class);
foreign.register(IS_WINDOWS ? Windows.class : POSIX.class);
final long pid = IS_WINDOWS ? Windows.pidInt() : POSIX.pidLong();
foreign.unregister(SymbolManagement.class); // We're able to cache the pid value, so it's okay to unregister
Logger.defaultInstance().info("UXGL FFI", "PID: " + pid);
Logger.defaultInstance().info("UXGL FFI", "Direct Mapping result == Handle Mapping result: " + (getpid.invokeNativeLong() == pid));
}
private static final class Windows {
@Redirect("GetCurrentProcessId") // Redirect to the real symbol name
public static native int pidInt();
}
private static final class POSIX {
@Redirect("getpid")
public static native long pidLong(); // Like above
}
}
You can also retrieve the global variable as well as get the function symbol!
package org.example.desktop.foreign;
import unrefined.nio.Allocator;
import unrefined.nio.Pointer;
import unrefined.runtime.DesktopRuntime;
import unrefined.util.UnexpectedError;
import unrefined.util.foreign.Foreign;
import unrefined.util.foreign.Library;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
public class GlobalVariables {
public interface CLibrary extends Library {
void fprintf(long fp, long format, int... args); // Varargs supported!
}
public static void main(String[] args) {
DesktopRuntime.setup(args); // Initialize the UXGL runtime environment
Foreign foreign = Foreign.getInstance(); // Get the platform-dependent FFI factory
long stdout = Allocator.defaultInstance().getAddress(foreign.getSymbolAddress("stdout")); // Get stdout FILE*
Random random = ThreadLocalRandom.current();
int a = random.nextInt();
int b = random.nextInt();
CLibrary c = foreign.downcallProxy(CLibrary.class);
try (Pointer format = Pointer.allocateDirect("SUM (%d, %d) = %d")) {
c.fprintf(stdout, format.address(), a, b, a + b);
}
catch (IOException e) {
throw new UnexpectedError(e);
}
}
}