B4J Code Snippet Native Linker (Java 19+ Only)

I have been trying out the replacement for JNA in Java 19.
The code needed to call a library function is far cleaner now, and requires less coding.

The following in-line java calls strlen from stdlib and prints the length of the string. (Not exciting code but shows how little you need to write now).
Just a taster for now.

B4X:
#Region Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 600
    ' ### needed for java 19 ###
    #VirtualMachineArgs: --enable-preview --enable-native-access=ALL-UNNAMED
    #PackagerProperty: VMArgs = --enable-preview
    ' ##########################
#End Region
' the usual app here that calls the inline java
...
#if java
import java.lang.foreign.*;
import java.lang.invoke.*;

public static void doIt() throws Throwable {
    // 1. Get a lookup object for commonly used libraries
    SymbolLookup stdlib = Linker.nativeLinker().defaultLookup();

    // 2. Get handle to the "strlen" function in the C standard library
    MethodHandle strlen = Linker.nativeLinker().downcallHandle(
        stdlib.lookup("strlen").orElseThrow(),
        FunctionDescriptor.of(ValueLayout.OfLong.JAVA_LONG, ValueLayout.OfAddress.ADDRESS));

    // 3. Convert Java String to C string and store it in off-heap memory
    MemorySegment str = SegmentAllocator.implicitAllocator().allocateUtf8String("B4X for coding!");

    // 4. Invoke the foreign function
    long len = (long) strlen.invoke(str);

    System.out.println("len = " + len);
  }

#End If

Looking forward to more experiments.

Note: It will not run in the IDE ( I had to modify some arguments that get passed to javac in order to compile the preview code).

ps. Sorry Erel if this is in the wrong part of the forum.
 

Daestrum

Expert
Licensed User
Longtime User
So after some more tries I managed to re-write the Joypad code I wrote a few years back.

It seems shorter to me, and will work from in-line java, don't think I ever got the JNA version to work, that's why I made the library.

The following code doesn't use all the buttons/triggers of the pad, but that can easily be added (by you) if you want to use it.

Note: again this won't run for you in the IDE.

B4X:
#Region Project Attributes 
    #MainFormWidth: 600
    #MainFormHeight: 600 
    ' ### needed for java 19 ###
    #VirtualMachineArgs: --enable-preview --enable-native-access=ALL-UNNAMED
    #PackagerProperty: VMArgs = --enable-preview
    ' ##########################    
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private xui As XUI 
    Private Button1 As B4XView
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("Layout1")
    MainForm.Show
End Sub

Sub Button1_Click
    'xui.MsgboxAsync("Hello World!", "B4X")
    (Me).As(JavaObject).RunMethod("doIt",Null)
End Sub
#if java
import java.lang.foreign.*;
import java.lang.invoke.*;
import java.lang.foreign.ValueLayout.*;

    // define the structure XINPUT_STATE    
    static GroupLayout XINPUT_STATE = MemoryLayout.structLayout(OfInt.JAVA_INT.withName("dwPacketNumber"),
            OfShort.JAVA_SHORT.withName("wButtons"), OfByte.JAVA_BYTE.withName("bLeftTrigger"),
            OfByte.JAVA_BYTE.withName("bRightTrigger"), OfShort.JAVA_SHORT.withName("sThumbLX"),
            OfShort.JAVA_SHORT.withName("sThumbLY"), OfShort.JAVA_SHORT.withName("sThumbRX"),
            OfShort.JAVA_SHORT.withName("sThumbRY")).withName("IState");
    
    // define structure ena
    static GroupLayout ena = MemoryLayout.structLayout(ValueLayout.OfBoolean.JAVA_BOOLEAN.withName("enabled"));
    
    // define the variables we will access in the structures
    static VarHandle buttons = XINPUT_STATE.varHandle(MemoryLayout.PathElement.groupElement("wButtons"));
    static VarHandle LTrig = XINPUT_STATE.varHandle(MemoryLayout.PathElement.groupElement("bLeftTrigger"));
    static VarHandle RTrig = XINPUT_STATE.varHandle(MemoryLayout.PathElement.groupElement("bRightTrigger"));
    static VarHandle LThumbX = XINPUT_STATE.varHandle(MemoryLayout.PathElement.groupElement("sThumbLX"));
    static VarHandle LThumbY = XINPUT_STATE.varHandle(MemoryLayout.PathElement.groupElement("sThumbLY"));
    static VarHandle RThumbX = XINPUT_STATE.varHandle(MemoryLayout.PathElement.groupElement("sThumbRX"));
    static VarHandle RThumbY = XINPUT_STATE.varHandle(MemoryLayout.PathElement.groupElement("sThumbRY"));

    static VarHandle PackNo = XINPUT_STATE.varHandle(MemoryLayout.PathElement.groupElement("dwPacketNumber"));

    static VarHandle enabledFlag = ena.varHandle(MemoryLayout.PathElement.groupElement("enabled"));

    public static void doIt() {
        try {
            // set up memorySession - can be inside or outside the JVM
            var memorySession = MemorySession.openConfined();
            
            // allocate so memory for the structure
            var xin = memorySession.allocate(XINPUT_STATE);

            // set up linkers & loaders
            Linker nativeLinker = Linker.nativeLinker();
            SymbolLookup stdlibLookup = nativeLinker.defaultLookup();
            SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
            
            // load System library XInput1_4
            System.loadLibrary("XInput1_4");
            
            //get method XInputEnable in XInput1_4.dll
            MethodHandle enable = nativeLinker.downcallHandle(
                    stdlibLookup.lookup("XInputEnable").or(() -> loaderLookup.lookup("XInputEnable")).orElseThrow(),
                    FunctionDescriptor.of(OfInt.JAVA_INT,OfAddress.ADDRESS /*OfBoolean.JAVA_BOOLEAN*/));

            //Enable readings on joystick/gamepad
            // allocate some memory and invoke foreign function
            var enab = memorySession.allocate(ena);
            // format is VarHandleName .set (memorySessionName , theValueYouWantToSet)
            enabledFlag.set(enab, true);
            enable.invoke(enab);
            //just a check
            //System.out.println("en flag : " + enabledFlag.get(enab));
            
            //get method XInputGetStae in XInput1_4.dll
            // descriptor is returnType, Args in order
            // so XInputGetState return an int (DWORD) , takes an int (DWORD) and takes a structure address (&XINPUT_STATE)
            MethodHandle readem = nativeLinker.downcallHandle(
                    stdlibLookup.lookup("XInputGetState").or(() -> loaderLookup.lookup("XInputGetState")).orElseThrow(),
                    FunctionDescriptor.of(OfInt.JAVA_INT,OfInt.JAVA_INT,OfAddress.ADDRESS));


            int res = (int) readem.invoke(0, xin.address());// pad number - address of struct

            // 0 is good anything else is reason for failure  like 1167 = no gamepads found
            System.out.println("Res : " + res);
            
            // get left trigger value
            Integer LT = (int) LTrig.get(xin);
            System.out.println("ltrig : " + (LT + 256)%256); // normalise to 0 - 255
            
            // get buttons pressed
            int b = (short) buttons.get(xin);
            System.out.println("Buttons : " + b); // buttons is bitwise field for buttons pressed see below
        
        } catch (Throwable t) {
            System.out.println(t.getMessage());   //t.printStackTrace();
        }
    }
#End If

'XINPUT_GAMEPAD_DPAD_UP            0x0001
'XINPUT_GAMEPAD_DPAD_DOWN        0x0002
'XINPUT_GAMEPAD_DPAD_LEFT        0x0004
'XINPUT_GAMEPAD_DPAD_RIGHT        0x0008
'XINPUT_GAMEPAD_START            0x0010
'XINPUT_GAMEPAD_BACK            0x0020
'XINPUT_GAMEPAD_LEFT_THUMB        0x0040
'XINPUT_GAMEPAD_RIGHT_THUMB        0x0080
'XINPUT_GAMEPAD_LEFT_SHOULDER    0x0100
'XINPUT_GAMEPAD_RIGHT_SHOULDER    0x0200
'XINPUT_GAMEPAD_A                0x1000
'XINPUT_GAMEPAD_B                0x2000
'XINPUT_GAMEPAD_X                0x4000
'XINPUT_GAMEPAD_Y                0x8000
 

Daestrum

Expert
Licensed User
Longtime User
Another simple example (using java 22 will work in IDE) uses JavaObject Library

Calls the OS to get the current PID via Foreign Function interface

B4X:
'Non-UI application (console / server application)
#Region Project Attributes 
    #CommandLineArgs:
    #MergeLibraries: True 
    #VirtualMachineArgs: --enable-native-access=ALL-UNNAMED
#End Region

Sub Process_Globals
    Dim thisPID As Int
End Sub

Sub AppStart (Args() As String)
    thisPID = getPID
    Log($"PID = ${thisPID}"$)
End Sub

Sub getPID As Object
    Return (Me).As(JavaObject).RunMethod("getMyPID",Null)
End Sub

#if java
import java.lang.invoke.*;
import java.lang.foreign.*;

public static Object getMyPID() throws Throwable {
    return Linker.nativeLinker().downcallHandle(
           Linker.nativeLinker().defaultLookup().find("_getpid").orElseThrow(),    
           FunctionDescriptor.of(ValueLayout.JAVA_INT))
           .invoke();
}
#End If
 

Daestrum

Expert
Licensed User
Longtime User
Quick update:
In the first example they changed one of the calls (well it was in preview so this happens)

this
allocateUtf8String(...)
becomes
allocateFrom(...) the explanation is utf8 is default so no need to specify it.
 
Top