2

I'm trying to use JNA to bridge between Leap Motion's C API (LeapC) and Java since Leap Motion has deprecated their official Java bindings. However, I'm unfamiliar with C and I haven't used JNA before. So far I have managed to get some things to work (like connecting to my Leap Motion controller and polling it for status events), but I'm stuck when it comes to receiving the actual tracking data.

The relevant struct from LeapC is defined like so in LeapC.h:

/** \ingroup Structs
 * A snapshot, or frame of data, containing the tracking data for a single moment in time.
 * The LEAP_FRAME struct is the container for all the tracking data.
 * @since 3.0.0
 */
typedef struct _LEAP_TRACKING_EVENT {
  LEAP_FRAME_HEADER info;

  int64_t tracking_frame_id;

  /** The number of hands tracked in this frame, i.e. the number of elements in
   * the pHands array.
   */
  uint32_t nHands;

  /**
   * A pointer to the array of hands tracked in this frame.
   */
  LEAP_HAND* pHands;

  /**
   * Current tracking frame rate in hertz.
   */
  float framerate;
} LEAP_TRACKING_EVENT;

My attempts to implement it in JNA have only been partially successful: The info, tracking_frame_id and nHands fields are read properly and contain the expected information. pHands refuses to work.

Attempt 1

First, I tried treating pHands as a pointer to the start of the LEAP_HAND array:

@FieldOrder({ "info", "tracking_frame_id", "nHands", "pHands", "framerate" })
public static class LEAP_TRACKING_EVENT extends Structure
{
    public LEAP_FRAME_HEADER info;
    public long tracking_frame_id;
    public int nHands;
    public Pointer pHands;
    public float framerate;
}

Attempting to read anything from the address the pointer points to, whether a full struct (new LEAP_HAND(pHands)) or even just a single byte (pHands.getByte()) always gives an Invalid Memory Access error from JNA.

Based on reading various other StackOverflow questions and answers it looks like I should be doing something like this.

Attempt 2

Secondly, I tried treating pHands as a straight up array:

@FieldOrder({ "info", "tracking_frame_id", "nHands", "pHands", "framerate" })
public static class LEAP_TRACKING_EVENT extends Structure
{
    public LEAP_FRAME_HEADER info;
    public long tracking_frame_id;
    public int nHands;
    public LEAP_HAND[] pHands = new LEAP_HAND[1]; //Size-limited to 1 here while testing.
    public float framerate;
}

Given a situation where exactly 1 hand is present (as defined in the JNA code above), the array is populated by an instance of LEAP_HAND but it contains garbage data (mostly zeroes). Curiously, the value of pHands[0].palm.position.z does change when I move my hand along the x-axis, so it appears to be partially overlapping the correct memory section here but is not aligned correctly.

I must be doing something wrong with how I'm approaching reading this array. Anyone have an idea of what I'm missing?


Edit: Working C code from one of the SDK's samples

They call LeapPollConnection like so: result = LeapPollConnection(connectionHandle, timeout, &msg);. If msg contains a tracking event they then call handleTrackingEvent(msg.tracking_event);. This function is defined as follows:

//File: ExampleConnection.c
static void handleTrackingEvent(const LEAP_TRACKING_EVENT *tracking_event){
  if(ConnectionCallbacks.on_frame){
    ConnectionCallbacks.on_frame(tracking_event);
  }
}

ConnectionCallbacks.on_frame is bound to the following OnFrame function:

//File: CallbackSample.c

/** Callback for when a frame of tracking data is available. */
static void OnFrame(const LEAP_TRACKING_EVENT *frame){
  printf("Frame %lli with %i hands.\n", (long long int)frame->info.frame_id, frame->nHands);

  for(uint32_t h = 0; h < frame->nHands; h++){
    LEAP_HAND* hand = &frame->pHands[h];
    printf("    Hand id %i is a %s hand with position (%f, %f, %f).\n",
                hand->id,
                (hand->type == eLeapHandType_Left ? "left" : "right"),
                hand->palm.position.x,
                hand->palm.position.y,
                hand->palm.position.z);
  }
}

Edit 2: All (relevant) code so far:

First, the code for polling and fetching events.

// Define an object to receive an event message into.
LEAP_CONNECTION_MESSAGE.ByReference messageRef = new LEAP_CONNECTION_MESSAGE.ByReference();

while (true)
{
    // Poll LeapC for an event. A status code for success is returned,
    // and messageRef is populated with the event message.
    LeapC.INSTANCE.LeapPollConnection(leapConnection.getValue(), 500, messageRef);

    // If the event is a tracking event, get the event data.
    if (messageRef.type == eLeapEventType.Tracking.getShortValue())
    {
        LEAP_TRACKING_EVENT event = messageRef.union.tracking_event;
    }

    // Sleep a moment before polling again.
    try
    {
        Thread.sleep(100);
    }
    catch (InterruptedException e)
    {
    }
}

And here are the implementations of PollLeapConnection and LEAP_CONNECTION_MESSAGE.

LeapPollConnection (LeapC API ref)

// Original C signature:
//   LeapPollConnection(LEAP_CONNECTION hConnection, uint32_t timeout, LEAP_CONNECTION_MESSAGE* evt);

public eLeapRS LeapPollConnection(Pointer hConnection, int timeout,
        LEAP_CONNECTION_MESSAGE.ByReference message);

LEAP_CONNECTION_MESSAGE (LeapC API ref)

// Original C signature:
//   typedef struct _LEAP_CONNECTION_MESSAGE {
//     /**
//      * The size of this message struct. @since 3.0.0
//      */
//     uint32_t size;
//   
//     /**
//      * The message type. @since 3.0.0
//      */
//     eLeapEventType type;
//   
//     /**
//      * A pointer to the event data for the current type of message. @since 3.0.0
//      */
//     union {
//       /** An untyped pointer. @since 3.0.0 */
//       const void* pointer;
//       /** A connection message. @since 3.0.0 */
//       const LEAP_CONNECTION_EVENT* connection_event;
//       /** A connection lost. @since 3.0.0 */
//       const LEAP_CONNECTION_LOST_EVENT* connection_lost_event;
//       /** A device detected message. @since 3.0.0 */
//       const LEAP_DEVICE_EVENT* device_event;
//       /** A device's status has changed.  @since 3.1.3 */
//       const LEAP_DEVICE_STATUS_CHANGE_EVENT* device_status_change_event;
//       /** A policy message. @since 3.0.0 */
//       const LEAP_POLICY_EVENT* policy_event;
//       /** A device failure message. @since 3.0.0 */
//       const LEAP_DEVICE_FAILURE_EVENT* device_failure_event;
//       /** A tracking message. @since 3.0.0 */
//       const LEAP_TRACKING_EVENT* tracking_event;
//       /** A log message. @since 3.0.0 */
//       const LEAP_LOG_EVENT* log_event;
//       /** A log messages. @since 4.0.0 */
//       const LEAP_LOG_EVENTS* log_events;
//       /** A get config value message. @since 3.0.0 */
//       const LEAP_CONFIG_RESPONSE_EVENT* config_response_event;
//       /** A set config value message. @since 3.0.0 */
//       const LEAP_CONFIG_CHANGE_EVENT* config_change_event;
//       const LEAP_DROPPED_FRAME_EVENT* dropped_frame_event;
//       /** A streaming image message. @since 4.0.0 */
//       const LEAP_IMAGE_EVENT* image_event;
//       /** A point mapping message. @since 4.0.0 */
//       const LEAP_POINT_MAPPING_CHANGE_EVENT* point_mapping_change_event;
//       const LEAP_HEAD_POSE_EVENT* head_pose_event;
//     };
//   } LEAP_CONNECTION_MESSAGE;

@FieldOrder({ "size", "type", "union" })
public static class LEAP_CONNECTION_MESSAGE extends Structure
{
    public static class EventUnion extends Union
    {
        public Pointer pointer;
        // Pointer is used for all event types I haven't mapped yet.
        public Pointer connection_event;
        public Pointer connection_lost_event;
        public Pointer device_event;
        public Pointer device_status_change_event;
        public Pointer policy_event;
        public Pointer device_failure_event;
        public LEAP_TRACKING_EVENT.ByReference tracking_event;
        public Pointer log_event;
        public Pointer log_events;
        public Pointer config_response_event;
        public Pointer config_change_event;
        public Pointer dropped_frame_event;
        public Pointer image_event;
        public Pointer point_mapping_change_event;
        public Pointer head_pose_event;
    }


    public int size;
    public short type;
    public EventUnion union;

    private eLeapEventType typeE;

    @Override
    public void read()
    {
        super.read();

        // Convert the short in type to an enum constant.
        typeE = eLeapEventType.None.getForValue(type);

        if (typeE == null)
        {
            typeE = eLeapEventType.None;
        }


        switch (typeE)
        {
            case ConfigChange :
                union.setType("config_change_event");
                break;
            case ConfigResponse :
                union.setType("config_response_event");
                break;
            case Connection :
                union.setType("connection_event");
                break;
            case ConnectionLost :
                union.setType("connection_lost_event");
                break;
            case Device :
                union.setType("device_event");
                break;
            case DeviceFailure :
                union.setType("device_failure_event");
                break;
            case DeviceLost :
                union.setType("device_event");
                break;
            case DeviceStatusChange :
                union.setType("device_status_change_event");
                break;
            case DroppedFrame :
                union.setType("dropped_frame_event");
                break;
            case HeadPose :
                union.setType("head_pose_event");
                break;
            case Image :
                union.setType("image_event");
                break;
            case ImageComplete :
                break;
            case ImageRequestError :
                break;
            case LogEvent :
                union.setType("log_event");
                break;
            case LogEvents :
                union.setType("log_events");
                break;
            case None :
                union.setType("pointer");
                break;
            case PointMappingChange :
                union.setType("point_mapping_change_event");
                break;
            case Policy :
                union.setType("policy_event");
                break;
            case Tracking :
                union.setType("tracking_event");
                break;
            default :
                System.out.println("Unknown message type: " + typeE);
                break;
        }

        union.read();
    }


    public static class ByReference extends LEAP_CONNECTION_MESSAGE
            implements Structure.ByReference
    {
    }
}

And finally, the full code for my current LEAP_TRACKING_EVENT

(LeapC API ref)

// Original C signature: See the top of this post.

@FieldOrder({ "info", "tracking_frame_id", "nHands", "pHands", "framerate" })
public static class LEAP_TRACKING_EVENT extends Structure
{
    public LEAP_FRAME_HEADER info;
    public long tracking_frame_id;
    public int nHands;
    public Pointer pHands;
    public float framerate;

    // Field to store all LEAP_HAND objects pointed to by pHands.
    private LEAP_HAND[] hands;


    @Override
    public void read()
    {
        super.read();

        // Print frame ID and hand count
        System.out.println("======================");
        System.out.println("ID: " + tracking_frame_id);
        System.out.println("Hands: " + nHands);

        if (nHands > 0)
        {
            // Attempt to read LEAP_HAND data and print info about hand #0.
            System.out.println("===");
            LEAP_HAND hand = new LEAP_HAND(pHands);
            hands = (LEAP_HAND[]) hand.toArray(nHands);

            String log = String.format(
                    "Hand 0| id: %d, type: %d, pos: [%.02f, %.02f, %.02f]%n",
                    hands[0].id, hands[0].type,
                    hands[0].palm.position.union.struct.x,
                    hands[0].palm.position.union.struct.y,
                    hands[0].palm.position.union.struct.z);

            System.out.println(log);
        }
        System.out.println("======================");
    }


    public static class ByReference extends LEAP_TRACKING_EVENT
            implements Structure.ByReference
    {
    }
}

new LEAP_HAND(pHands) simply passes the pointer to the super constructor and then calls read() on itself (the code for this is still available in the gist linked below, of course).


Edit 3: Invalid Memory Access stack trace

Exception in thread "main" java.lang.Error: Invalid memory access
    at com.sun.jna.Native.getInt(Native Method)
    at com.sun.jna.Pointer.getInt(Pointer.java:580)
    at com.sun.jna.Pointer.getValue(Pointer.java:382)
    at com.sun.jna.Structure.readField(Structure.java:732)
    at com.sun.jna.Structure.read(Structure.java:591)
    at komposten.leapmouse.LeapC$LEAP_HAND.read(LeapC.java:322)
    at komposten.leapmouse.LeapC$LEAP_HAND.<init>(LeapC.java:316)
    at komposten.leapmouse.LeapC$LEAP_TRACKING_EVENT.read(LeapC.java:236)
    at com.sun.jna.Structure.autoRead(Structure.java:2203)
    at com.sun.jna.Structure.conditionalAutoRead(Structure.java:561)
    at com.sun.jna.Structure.updateStructureByReference(Structure.java:690)
    at com.sun.jna.Pointer.getValue(Pointer.java:367)
    at com.sun.jna.Structure.readField(Structure.java:732)
    at com.sun.jna.Union.readField(Union.java:223)
    at com.sun.jna.Structure.read(Structure.java:591)
    at komposten.leapmouse.LeapC$LEAP_CONNECTION_MESSAGE.read(LeapC.java:196)
    at com.sun.jna.Structure.autoRead(Structure.java:2203)
    at com.sun.jna.Function.invoke(Function.java:381)
    at com.sun.jna.Library$Handler.invoke(Library.java:265)
    at com.sun.jna.Native$3.invoke(Native.java:1202)
    at com.sun.proxy.$Proxy0.LeapPollConnection(Unknown Source)
    at komposten.leapmouse.LeapTest.<init>(LeapTest.java:35)
    at komposten.leapmouse.LeapTest.main(LeapTest.java:88)

I have created a gist with the full code for LeapTest and LeapC, after some changes based on the answer and comments here on SO: LeapJna Gist. I can inline this code here if wanted, but a gist has line numbers and is easier to get an overview of (IMO).


If needed, here is my implementation of LEAP_HAND (as well as the full LEAP_TRACKING_EVENT code and other struct mappings used by those two).

komposten
  • 31
  • 1
  • 7
  • You may have mapped correctly with a `Pointer` in the structure but more important is how you, or the native code, allocates the memory. Which we can't tell just from your structure mapping. What method in the API are you using this with, and how does it describe who is responsible for allocating and freeing memory? You may need to allocate using `Memory arr = new Memory(size);` and passing the structure with the pointer to this allocated memory, unless the API tells you otherwise. You may have to call the method twice, once with a null pointer to get the `nHands` value to calculate `size`. – Daniel Widdis Dec 22 '19 at 17:35
  • @DanielWiddis I call the [LeapPollConnection](https://developer.leapmotion.com/documentation/v4/group___functions.html#ga2a8aecad339f0fd339ca22a3e7b389f6) function, which gives me a [LEAP_CONNECTION_MESSAGE](https://developer.leapmotion.com/documentation/v4/group___structs.html#struct_l_e_a_p___c_o_n_n_e_c_t_i_o_n___m_e_s_s_a_g_e). The message in turn contains a pointer to the LEAP_TRACKING_EVENT struct. – komposten Dec 22 '19 at 20:05
  • @DanielWiddis I'll add an edit to the question with an excerpt from one of the SDK's C code samples and how they extract the data. – komposten Dec 22 '19 at 20:11
  • I should also add that the Leap Motion API builds up a queue of events (including frame data), which is what I can then poll for information. So I would assume that the necessary memory has already been allocated so it can store the tracking data in the queue. – komposten Dec 23 '19 at 17:16
  • I think you're on the right track but need to understand a few things to implement. JNA won't read the contents of the pointed-to memory location since it's a variable sized array, so you have to write code to actually read that memory yourself once you know the size of it. The `Pointer.getX()` methods should help here. You do have to read that data from within the callback, though, because it looks like that's native allocated memory that only the callback will have access to. – Daniel Widdis Dec 23 '19 at 19:23
  • @DanielWiddis Based on some other SO answers I think I have figured out how to read the data from the pointer (by instancing the first `LEAP_HAND` from the pointer and then `toArray(nHands)`-ing it). However, since I can't access the memory at the pointer this is something I can't really do much with at the moment. As for the last part of your comment, what do you mean by "read that data from within the callback"? – komposten Dec 23 '19 at 20:45
  • The working example you gave uses a C callback, so you need to implement JNA callbacks to match. The memory pointer is not guaranteed to remain valid after the callback is done and the stack releases the memory. – Daniel Widdis Dec 24 '19 at 01:20
  • So in the callback you need to allocate some memory that you can control (e.g. `new Memory()`) and copy over from the native memory that the callback gives you access to. – Daniel Widdis Dec 24 '19 at 02:18
  • @DanielWiddis But I'm using `LeapPollConnection` directly, without callbacks (which I presume is possible in C as well; it's just that they chose to use callbacks in the example to make it easier to reuse `ExampleConnection.c` for all examples. – komposten Dec 24 '19 at 12:29
  • And regarding the memory, how would I read the native memory to copy it over when I can't access anything at the pointer's target? I have read a bit about `Memory` and I know I can get the size of the `Structure` I'm using to pass into `new Memory(size)`. But I'm at a loss as to how to copy the native memory into my Java-readable memory; there must be something I'm missing here. – komposten Dec 24 '19 at 12:33
  • I still don't see what method you are using to access it. In general, the JNA pattern is to pre-allocate the `Memory` object (using size determined from a method call using a null pointer) and then pass that `Memory` object to a method expecting a `Pointer`, allowing the native code to fill that allocated memory. The only time you don't do something like that is if the API tells you that the native code returns native-allocated memory that you are later responsible for releasing/freeing in some manner. I'm still not sure from the information you've provided which of these options it is. – Daniel Widdis Dec 24 '19 at 20:59
  • @DanielWiddis I've added a second edit to the question with all the relevant code I use so far. Hopefully that should clear up any questions you might have about what exactly it is I'm doing. – komposten Dec 25 '19 at 13:03
  • As you can see in the code above I call `LeapPollConnection()` and receive a pointer to a `LEAP_CONNECTION_MESSAGE` that contains a pointer to a `LEAP_TRACKING_EVENT`, which in turn contains my `pHands` pointer. I'm not sure where I would use `Memory` in the way you suggest since the tracking event pointer is nested so deep (and I can't change the `pHands` field to a Memory since JNA tries to stuff a Pointer instance into it: `Can not set com.sun.jna.Memory field komposten.leapmouse.LeapC$LEAP_TRACKING_EVENT.pHands to com.sun.jna.Pointer`). – komposten Dec 25 '19 at 13:06
  • If I had been using some method like `getHandTrackingData(uint32* nHands, LEAP_TRACKING_EVENT* pHands)` (made up example) then I think I understand how I would use Memory (call once to get a value to nHands, then call a second time with a Memory instance large enough to fit nHands LEAP_HAND structs). – komposten Dec 25 '19 at 13:11
  • It's a holiday so not spending too much time here today, but a thought. Looks like the API says the memory pointed to will be valid as long as you have the connection message object, so the code looks right to me, up through reading `messageRef.union.tracking_event`. At this point it has read that object but JNA does not read data from the `Pointer` inside there automatically. I think you need to update the overridden `read()` method in `LEAP_TRACKING_EVENT` to read data from that pointed-to `pHands` value. Now that I know the native memory is valid, that should be simple (continued...) – Daniel Widdis Dec 25 '19 at 22:53
  • I think the `hands = (LEAP_HAND[]) hand.toArray(nHands);` is the problematic statement. `hand` should probably work for the first element, but JNA doesn't know that the native memory is allocated here, and I think the `toArray()` call allocates its own memory in this case. YOu might need to add code immediately following the `toArray()` call to iterate over the native memory and populate each of the array's structures... that seems kludgy and I'm sure there's a more elegant solution. I'll respond more tomorrow. – Daniel Widdis Dec 25 '19 at 22:59

2 Answers2

1

First, welcome to StackOverflow! Second, thanks for providing the additional details. You're missing a few implementation details for the way you've chosen to map the structures, but it's easier to simplify your implementation than add more code.

You're missing a super(p) in the pointer constructor for ByReference implementation for LEAP_CONNECTION_MESSAGE, so it may not read all the values correctly this way. However, it is unnecessary to use the ByReference implementation. JNA automatically uses the Structure's pointer when used in a function argument. Remove the ByReference and just instantiate with LEAP_CONNECTION_MESSAGE message = new LEAP_CONNECTION_MESSAGE();.

One other issue is trying to read the native memory into your structure array. Your initial assignment of LEAP_HAND hand = new LEAP_HAND(pHands); probably works to give you the first element of the array, but then using hands = (LEAP_HAND[]) hand.toArray(nHands); allocates new memory on the native side. You should instead create your own LEAP_HAND[] array, iterate an offset starting at 0 and incrementing by Native.getNativeSize(LEAP_HAND.class), and instantiate each element with new LEAP_HAND(pHands.share(offset)). (There are other ways to do this slightly more elegantly once you get it working.)

Another possible problematic point is your definition of the message type as public short type. The API specifies this type as an enum which is usually an int. Looking at your gist, you have several other enum mappings which you've mapped with things other than int (e.g., the type element of LEAP_HAND is a byte), which could misalign those structures as well. (However, based on your explanation and testing, you may be correct.)

And as we eventually determined, there were a few other issues to hunt down, including structure alignment and endian-ness. Pointer.dump() as a debugging tool for the win!

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
  • Quite late here for me, but I did a quick test with `LEAP_CONNECTION_MESSAGE` instead of `LEAP_CONNETION_MESSAGE.ByRefernce`, but I still get the memory access error when I call `new LEAP_HAND(pHands)` and `read()` data into it. Same if I do something simple like `pHands.getByte()` (just as before). – komposten Dec 26 '19 at 22:20
  • AHA.... I see you've defined the event type as `public short type;` but the native type is `enum`. This *usually* maps to an `int`. Your earlier comments about it being "not aligned correctly" imply this may be the issue. – Daniel Widdis Dec 27 '19 at 02:52
  • I used `int` for it at first, but then I got no usable information in the `EventUnion` at all (I think all pointers were null pointers or something like that). My brother, who knows a little bit about C-family languages, suggested that it might use a `short` instead if the enum values fit into that. I changed to `short`, and suddenly the `EventUnion` contained information. And as I have already mentioned, the frame header, id and hand count are all read correctly in the tracking event. – komposten Dec 27 '19 at 18:15
  • Can you post the actual stack trace of the invalid memory access error? It's easier to figure out what's going on when I know the exact line of code and don't have to guess with mental compilation. – Daniel Widdis Dec 27 '19 at 18:27
  • I have added the stack trace to the bottom of the answer, along with a gist containing the most recent version of all my code (so that the line numbers map correctly from the stack trace). – komposten Dec 28 '19 at 15:04
  • Thanks for the trace. Silly questions: is `nHands` a sane, nonzero value? If so, based on everything so far I can't see why there's an error. The API states "Pointers in the retrieved event message structure will be valid until the associated connection or device is closed, or the next call to LeapPollConnection()." so the retrieved pointer should point to valid native memory if there's any data there. If there's no data (e.g., nHands=0) then that may be a null pointer. You might try `Pointer.dump()` to look at the raw memory at that location for debugging purposes. – Daniel Widdis Dec 28 '19 at 16:52
  • `nHands` is sane (0, 1 or 2 depending on if I show no, one or two hands), `info` and `tracking_frame_id` are also sane. `framerate` is not (usually 0 or a float less than 1, not the expected ~110), which I thought I had mentioned earlier but apparently I forgot to add that. ‍♂️ Will take a look at `Pointer.dump()`, both for `pHands` (probably gives an IMA) and the whole `LEAP_TRACKING_EVENT`. – komposten Dec 28 '19 at 17:26
  • If `framerate` is wrong that implies the `Pointer` might not be in the right location either... but the mappings there look correct. I'm wondering if the documentation is wrong stating that it's a pointer elsewhere, and the data may be inline in a variable sized structure. – Daniel Widdis Dec 28 '19 at 19:43
  • Can we not infer that from the C sample code I added a couple of edits ago? Based on the way they access the array or something (`LEAP_HAND* hand = &frame->pHands[h];`)? (And sorry for not mentioning `framerate` before. I completely forgot about it as I thought I had it in the post from the start...) – komposten Dec 28 '19 at 19:49
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/204996/discussion-between-daniel-widdis-and-komposten). – Daniel Widdis Dec 28 '19 at 19:52
1

After a lot of discussions with @DanielWiddis (thank you!) we finally found the issue. Using Pointer.dump(), as suggested by Daniel, I manually mapped the memory against the data that JNA loaded into the LEAP_TRACKING_EVENT class:

  memory   |   mapped to
-----------|--------------
[bceba2ed] | info.reserved
[f5010000] | info.reserved
[d7990600] | info.frame_id
[00000000] | info.frame_id
[adbaca0b] | info.timestamp
[04000000] | info.timestamp
[9d990600] | tracking_frame_id
[00000000] | tracking_frame_id
[01000000] | nHands
[80e7a2ed] |
[f5010000] | pHands
[6c4edd42] | pHands
[00000000] | framerate

Because of JNA's default alignment settings for structs 4 bytes where skipped between nHands and pHands, causing pHands to point to 0x42dd4e6c000001f5 instead of 0x000001f5eda2e780 (in this example). Thus pHands pointed to the completely wrong location.

To fix this I simply changed LEAP_TRACKING_DATA to use ALIGN_NONE instead of ALIGN_DEFAULT.

komposten
  • 31
  • 1
  • 7