javaarrayspointersjnaleap-motion

How to JNA-map a pointer-to-array? I keep getting Invalid Memory Access or garbage data


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).


Solution

  • 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.