JNA Struct Value as a C Function Argument

30 views
Skip to first unread message

Lachlan Maloney

unread,
Mar 15, 2024, 7:17:27 PMMar 15
to Java Native Access

Hello, 


I am trying to write a JNA mapping for the SimpleBLE Library on macOS. In order to call the native c functions I need to, I have created JNA struct mappings of the structs here. I have also confirmed that the SimpleBLE library itself works properly, with a small c application.


##################################################################################

E.g. Structs: 

public static class uuid_t extends Structure {

private static final List<String> FIELDS = Arrays.asList("value");

public byte[] value = new byte[37]; // SIMPLEBLE_UUID_STR_LEN

@Override

protected List<String> getFieldOrder(){

return FIELDS;

}

}

public static class descriptor_t extends Structure {

private static final List<String> FIELDS = Arrays.asList("uuid");

public uuid_t uuid;

@Override

protected List<String> getFieldOrder(){

return FIELDS;

}

}

public class characteristic_t extends Structure {

private static final List<String> FIELDS = Arrays.asList(

"uuid",

"can_read",

"can_write_request",

"can_write_command",

"can_notify",

"can_indicate",

"descriptor_count",

"descriptors"

);

public uuid_t uuid;

public byte can_read;

public byte can_write_request;

public byte can_write_command;

public byte can_notify;

public byte can_indicate;

public long descriptor_count;

public descriptor_t[] descriptors = new descriptor_t[16];

@Override

protected List<String> getFieldOrder(){

return FIELDS;

}

}

public class service_t extends Structure {

private static final List<String> FIELDS = Arrays.asList(

"uuid",

"data_length",

"data",

"characteristic_count",

"characteristics"

);

public uuid_t uuid;

public long data_length;

public byte[] data = new byte[27];

public long characteristic_count;

public characteristic_t[] characteristics = new characteristic_t[16];

@Override

protected List<String> getFieldOrder(){

return FIELDS;

}

##################################################################################


When calling the "simpleble_peripheral_write_command" function with service_uuid and characteristic_uuid structs as defined above (and actually set by the SimpleBLE dylib call to "simpleble_peripheral_services_get" function), the struct arguments seem to take up more space in memory than what the SimpleBLE dylib expects. I have concluded this by attaching an lldb debugger (output screenshot below) which shows the value of the arguments when write_command is called. 


This is how I am setting my structs and calling write_command:

// Data to send

byte[] data = new byte[]{0x8a, 0x03, 0xff};

// service_t struct

libsimpleble.service_t service_t = new libsimpleble.service_t();

// Populate the service_t struct with services and characteristics

// index 0 = 65333333-A115-11E2-9E9A-0800200CA100 (service I want to use)

boolean success = !simpleble.getPeripheralServices(peripheral, 0, service_t);

// [1] = 65333333-A115-11E2-9E9A-0800200CA102 (characteristic I want to use)

libsimpleble.characteristic_t characteristic_t = service_t.characteristics[1];

// setting the function arguments to the UUID struct of the service/characteristics (has one field "value" which is called by SimpleBLE dylib

libsimpleble.uuid_t service_uuid = service_t.uuid;

libsimpleble.uuid_t characteristic_uuid = characteristic_t.uuid;

simpleble.writeCommand(peripheral, service_uuid, characteristic_uuid, data, data.length);


And this is the debugger output:

Screenshot 2024-03-16 at 10.00.25 am.png


I think, the service_uuid struct is overflowing the memory allocated for service, characteristic, and data - data has been set to the value of the service_t.uuid.value which is why I think this is the case. 


I was hoping someone had some experience with this before and would be able to offer some suggestions. Apologies for the formatting!!


Thanks

Lachlan Maloney

unread,
Mar 15, 2024, 7:18:19 PMMar 15
to Java Native Access
Clearer debugger output: 

(lldb) fr v

(SimpleBLE::Safe::Peripheral *) handle = 0x0000600001c68720

(simpleble_uuid_t) service = (value = "\U00000006\0\0\0\0\0\0\0\xe8ng\f\U00000003\0\0\0\x90hg\f\U00000003\0\0\0)H\xc09\U00000001\0\0\0\U00000001\0\0\0\xcb")

(simpleble_uuid_t) characteristic = (value = "@\U00000011\0\xe6\a\0\0\0\0jg\f\U00000003\0\0\0 &\xcf9\U00000001\0\0\0\U00000006\0\0\0\0\0\0\0\xe8ng\f\U00000003")

(const uint8_t *) data = 0x00007fcab4896400 "65333333-a115-11e2-9e9a-0800200ca100"

(size_t) data_length = 140508589024992

(SimpleBLE::Safe::Peripheral *) peripheral = 0x00007fcb0400b0e0

(bool) success = false


Lachlan Maloney

unread,
Mar 16, 2024, 12:06:17 AMMar 16
to Java Native Access
Further debugging:
It seems that the argument order is getting jumbled somewhere along the way with JNA - if I reorder my Java call to the write_data parameter to be
simpleble.writeCommand(peripheral, data, data_length, service_uuid, characteristic_uuid);

then the data and data_length parameters are set correctly. This seems to be in contradiction with the native c export order of 
simpleble_peripheral_write_command(simpleble_peripheral_t handle, simpleble_uuid_t service, simpleble_uuid_t characteristic, const uint8_t* data, size_t data_length) ???

Lachlan Maloney

unread,
Mar 16, 2024, 2:08:02 AMMar 16
to Java Native Access
Okay I now think this has something to do with the calling convention being set at the very start when I am loading my external dylib - but have not idea how/what to change it to. Any assistance would be greatly appreciated!!

public interface libsimpleble extends Library {

libsimpleble INSTANCE = (libsimpleble)

Native.load(libName, libsimpleble.class);


I am running on a macOS and the plugin I am writing is for x86_64

Matthias Bläsing

unread,
Mar 16, 2024, 4:26:33 AMMar 16
to jna-...@googlegroups.com
Hi,

Am Freitag, dem 15.03.2024 um 16:17 -0700 schrieb Lachlan Maloney:


I am trying to write a JNA mapping for the SimpleBLE Library on macOS. In order to call the native c functions I need to, I have created JNA struct mappings of the structs here. I have also confirmed that the SimpleBLE library itself works properly, with a small c application.


[structure Bindings, incomplete Java code]


And this is the debugger output:

Screenshot 2024-03-16 at 10.00.25 am.png


I think, the service_uuid struct is overflowing the memory allocated for service, characteristic, and data - data has been set to the value of the service_t.uuid.value which is why I think this is the case. 


I was hoping someone had some experience with this before and would be able to offer some suggestions. Apologies for the formatting!!



Please always provide a runnable sample (for example on github) - I had to reformat and fix the code to get it into a workable format.

The most important definition is missing from the provided code: The signature of `writeCommand`. The C headers indicate, that the parameters of simpleble_peripheral_write_command must be provided by value. You are passing structures and JNA defaults to passing structures by reference.

I suggest to retry with this:

-------------------------------------------------------------

public interface libsimpleble extends Library {
    libsimpleble INSTANCE = Native.load("simpleble.so" /*Replace with real value*/, libsimpleble.class);

    int writeCommand(simpleble_peripheral_t handle, uuid_t.ByValue service, uuid_t.ByValue characteristics, byte[] data, LibC.size_t dataLength);

    public static class simpleble_peripheral_t extends PointerType {}

    @FieldOrder({"value"})
    public static class uuid_t extends Structure {
        public static class ByValue extends uuid_t implements Structure.ByValue {};

        public byte[] value = new byte[37]; // SIMPLEBLE_UUID_STR_LEN

    }

    @FieldOrder({"uuid"})
    public static class descriptor_t extends Structure {
        public static class ByValue extends descriptor_t implements Structure.ByValue {};

        public uuid_t uuid;

    }

    @FieldOrder({"uuid", "can_read", "can_write_request", "can_write_command", "can_notify", "can_indicate","descriptor_count","descriptors"})
    public static class characteristic_t extends Structure {
        public static class ByValue extends characteristic_t implements Structure.ByValue {};

        public uuid_t uuid;

        public byte can_read;

        public byte can_write_request;

        public byte can_write_command;

        public byte can_notify;

        public byte can_indicate;

        public LibC.size_t descriptor_count;

        public descriptor_t[] descriptors = new descriptor_t[16];
    }

    @FieldOrder({"uuid", "data_length", "data", "characteristic_count", "characteristics"})
    public static class service_t extends Structure {
        public static class ByValue extends service_t implements Structure.ByValue {};

        public uuid_t uuid;

        public LibC.size_t data_length;

        public byte[] data = new byte[27];

        public LibC.size_t characteristic_count;

        public characteristic_t[] characteristics = new characteristic_t[16];

    }
}

-------------------------------------------------------------

  • Field order definitions are moved to annotations to simplify code
  • cast is removed from Native#load as it is not necessary
  • ByValue subclasses are added to the structure definitions
  • simpleble_peripheral_t is defined as a PointerType (native definition is void*)
  • writeCommand is defined to take ByValue parameters

With that I would modify the calling code to:

-------------------------------------------------------------

public class Demo {
    public static void main(String[] args) {
        // Name needs to be adjusted

        simpleble_peripheral_t peripheral = null;

        // Data to send
        byte[] data = new byte[]{ (byte) 0x8a, (byte) 0x03, (byte) 0xff};

        // service_t struct
        service_t service_t = new libsimpleble.service_t();

        // Populate the service_t struct with services and characteristics
        // index 0 = 65333333-A115-11E2-9E9A-0800200CA100 (service I want to use)
        boolean success = !getPeripheralServices(peripheral, 0, service_t);

        // [1] = 65333333-A115-11E2-9E9A-0800200CA102 (characteristic I want to use)
        characteristic_t characteristic_t = service_t.characteristics[1];

        // setting the function arguments to the UUID struct of the service/characteristics (has one field "value" which is called by libsimpleble dylib
        uuid_t.ByValue service_uuid = new uuid_t.ByValue();
        System.arraycopy(service_t.uuid.value, 0, service_uuid.value, 0, service_t.uuid.value.length);

        uuid_t.ByValue characteristic_uuid = new uuid_t.ByValue();
        System.arraycopy(characteristic_t.uuid.value, 0, characteristic_uuid.value, 0, characteristic_t.uuid.value.length);

        libsimpleble.INSTANCE.writeCommand(peripheral, service_uuid, characteristic_uuid, data, new LibC.size_t(data.length));
    }
}

-------------------------------------------------------------

Maybe this helps

Matthias

Lachlan Maloney

unread,
Mar 16, 2024, 5:37:37 AMMar 16
to Java Native Access
Matthias, honestly I cannot thank you enough. It works!

Trying to solve this issue has been giving me a lot of troubles over the last 2 weeks, so I really really appreciate you taking the time to provide this information. I had previously been trying to pass the service_uuid and characteristic_uuid "ByValue", but I was not performing the System.arraycopy() to create the ByValue structures - was simply initialising them with the respective correct byte arrays manually.

I apologise for not providing runnable code, one of the reasons is that this Java project is actually a plugin for an open source Guitar Tab software "TuxGuitar". So it relies on a lot of setup/mvn installs to actually get up and running (and I don't have a standalone project which doesn't interface with TuxGuitar). But regardless, now I have a Java <-> SimpleBLE "bridge" working I will of course be putting this up as a repository for others to use.

The updated signature of "writeCommand" is as follows (basically just a wrapper for the SimpleBLE function: simpleble_peripheral_write_command)

public boolean writeCommand(Pointer peripheralHandle, byte[] data, libsimpleble.size_t length, libsimpleble.uuid_t.ByValue serviceUuid, libsimpleble.uuid_t.ByValue characteristicUuid) {
    return libsimpleble.INSTANCE.simpleble_peripheral_write_request(peripheralHandle, serviceUuid, characteristicUuid, data, length);
}

You'll notice that the argument ordering in the above differs from the cpp file: https://github.com/OpenBluetoothToolbox/SimpleBLE/blob/main/simpleble/src_c/peripheral.cpp which has the order of: handle, service, characteristic, data, data_length. I have no idea why it is getting mixed up, and could only confirm it (very painfully) by stepping through with a LLDB debugger and inspecting the stack/variables. I have a suspicion this is something to do with the calling convention..? But I'm using the standard "Library" which should be appropriate for MacOS?

Any other thoughts you had on the above would be much appreciated, but your suggestion has solved my main problem! As you can probably tell, Java is not my forte so again thank you very much. If you had a paypal or something I would love to buy you a beer as a token of appreciation but all good if you'd rather not share those details.

Cheers,
Lachlan
Reply all
Reply to author
Forward
0 new messages