Java Foreign API: How to get string from C_POINTER?

207 views Asked by At

I want to use libimobiledevice by Java Foreign API, This is my code, written by Kotlin:

class Device {
    private val arena: Arena = Arena.openConfined()
    private val udidAddress: MemorySegment = arena.allocate(C_POINTER)
    private val deviceAddress: MemorySegment = arena.allocate(C_POINTER)

    init {
        if (idevice_new_with_options(deviceAddress, NULL(), IDEVICE_LOOKUP_USBMUX()) != IDEVICE_E_SUCCESS()) {
            throw Exception("ERROR: No device found!")
        }
        if (idevice_get_udid(deviceAddress, udidAddress) != IDEVICE_E_SUCCESS()) {
            idevice_free(deviceAddress)
            throw Exception("ERROR: Get UDID failed")
        } else {
            println("connected")
            println(MemorySegment.ofAddress(udidAddress.address()).getUtf8String(0))
        }
    }
}

fun main() {
    Device()
}

But it got error while getting udid:

Exception in thread "main" java.lang.IndexOutOfBoundsException: Out of bound access on segment MemorySegment{ array: Optional.empty address:140202431639472 limit: 0 }; new offset = 0; new length = 1
    at java.base/jdk.internal.foreign.AbstractMemorySegmentImpl.outOfBoundException(AbstractMemorySegmentImpl.java:371)
    at java.base/jdk.internal.foreign.AbstractMemorySegmentImpl.apply(AbstractMemorySegmentImpl.java:357)
    at java.base/jdk.internal.foreign.AbstractMemorySegmentImpl.apply(AbstractMemorySegmentImpl.java:70)
    at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:98)
    at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:124)
    at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:448)
    at java.base/jdk.internal.foreign.AbstractMemorySegmentImpl.checkBounds(AbstractMemorySegmentImpl.java:346)
    at java.base/jdk.internal.foreign.AbstractMemorySegmentImpl.checkAccess(AbstractMemorySegmentImpl.java:311)
    at java.base/java.lang.invoke.VarHandleSegmentAsBytes.checkAddress(VarHandleSegmentAsBytes.java:81)
    at java.base/java.lang.invoke.VarHandleSegmentAsBytes.get(VarHandleSegmentAsBytes.java:108)
    at java.base/java.lang.foreign.MemorySegment.get(MemorySegment.java:1388)
    at java.base/jdk.internal.foreign.abi.SharedUtils.strlen(SharedUtils.java:199)
    at java.base/jdk.internal.foreign.abi.SharedUtils.toJavaStringInternal(SharedUtils.java:190)
    at java.base/java.lang.foreign.MemorySegment.getUtf8String(MemorySegment.java:890)
    at Device.<init>(Device.kt:21)
    at DeviceKt.main(Device.kt:27)
    at DeviceKt.main(Device.kt)

The C language code i want:

static char *udid = NULL;

/* Device Handle */
idevice_t device = NULL;

/* Try to connect to first USB device */
if (idevice_new_with_options(&device, NULL, IDEVICE_LOOKUP_USBMUX) != IDEVICE_E_SUCCESS) {
  printf("ERROR: No device found!\n");
  return -1;
}

/* Retrieve the udid of the connected device */
if (idevice_get_udid(device, &udid) != IDEVICE_E_SUCCESS) {
  printf("ERROR: Unable to get the device UDID.\n");
  idevice_free(device);
  return -1;
}

/* Outputs device identifier */
printf("Connected with UDID: %s\n", udid);

how to solve this problem? (sorry, this is my first time using stackoverflow)

It should return a udid, but I got an error, the code I tried is above.

3

There are 3 answers

1
Jorn Vernee On

There's no equivalent of the & operator in the Panama API (Simply because Java values don't have a stable native address). So, step 1 is to rewrite your C code to not use it. We get something like this:

static char *udid = NULL;

/* Device Handle */
idevice_t device = NULL;

/* Try to connect to first USB device */
idevice_t *device_ptr = malloc(sizeof *device_ptr);
if (idevice_new_with_options(device_ptr , NULL, IDEVICE_LOOKUP_USBMUX) != IDEVICE_E_SUCCESS) {
  printf("ERROR: No device found!\n");
  return -1;
}
device = *device_ptr;

/* Retrieve the udid of the connected device */
char **udid_ptr = malloc(sizeof *udid_ptr);
if (idevice_get_udid(device, udid_ptr) != IDEVICE_E_SUCCESS) {
  printf("ERROR: Unable to get the device UDID.\n");
  idevice_free(device);
  return -1;
}
udid = *uid_ptr;

/* Outputs device identifier */
printf("Connected with UDID: %s\n", udid);

free(device_ptr);
free(udid_ptr);

Now it should be easier to see how this should be translated into Kotlin (I hope this is correctly eyeballed, as I'm not super familiar with Kotlin):

class Device {
    private val uid: MemorySegment
    private val device: MemorySegment

    init {
        Arena.openConfined().use { arena ->
            // you could even have just one of these and re-use it
            val uidAddress: MemorySmegment = arena.allocate(C_POINTER)
            val deviceAddress: MemorySegment = arena.allocate(C_POINTER)

            if (idevice_new_with_options(deviceAddress, NULL(), IDEVICE_LOOKUP_USBMUX()) != IDEVICE_E_SUCCESS()) {
                throw Exception("ERROR: No device found!")
            }
            device = deviceAddress.get(C_POINTER, 0)

            if (idevice_get_udid(device, uidAddress) != IDEVICE_E_SUCCESS()) {
                idevice_free(device)
                throw Exception("ERROR: Get UDID failed")
            }
            uid = udidAddress.get(C_POINTER, 0)

            println("connected")
            println(uid.getUtf8String(0))
        }
    }
}

fun main() {
    Device()
}
0
purofle On

It worked when I write like this:

class Device {
    private var device: MemorySegment
    private var udid: MemorySegment

    init {
        Arena.openConfined().use { arena ->
            val udidAddress: MemorySegment = arena.allocate(C_POINTER)
            val deviceAddress: MemorySegment = arena.allocate(C_POINTER)

            if (idevice_new_with_options(deviceAddress, NULL(), IDEVICE_LOOKUP_USBMUX()) != IDEVICE_E_SUCCESS()) {
            throw Exception("ERROR: No device found!")
            }
            device = deviceAddress.get(C_POINTER, 0)

            if (idevice_get_udid(deviceAddress, udidAddress) != IDEVICE_E_SUCCESS()) {
                idevice_free(deviceAddress)
                throw Exception("ERROR: Get UDID failed")
            }
            
            udid = udidAddress.get(C_POINTER, 0).get(C_POINTER, 0)
            println("connected")
            println(udid.getUtf8String(0))
        }
    }
}
0
WhiteWood City On

I just tried these code, it works the same

var device = MemorySegment.ofAddress(pDevice.get(ValueLayout.JAVA_LONG,0));
//You may also need this unsafe operation to define the new segment size. add option --enable-access-native=modulename to close the warning info. but its optional. Current segment size is 0.
//device.reinterpret(Long.MAX_VALUE); 

or

var device = pDevice.get(C_POINTER, 0);//bytesize is Long.MAX_VALUE