Wrapping Nested C Structs

57 views
Skip to first unread message

Kaitlyn

unread,
Apr 20, 2022, 3:59:19 PM4/20/22
to Java Native Access
Hello!

I am using JNA to wrap a C method that takes in nested structs. I believe that I am not properly storing the values in the Java Structures because I am seeing odd behavior with values shifting by one once the structs are passed to the C code.

C code:
gnc_pose_t diff_pose(gnc_pose_t pose1, gnc_pose_t pose2) {
// Print pose1
printf("Pos 1 = %1.3f %1.3f %1.3f\n",pose1.state.pos[0],pose1.state.pos[1],pose1.state.pos[2]);
printf("Vel 1 = %1.3f %1.3f %1.3f\n",pose1.state.vel[0],pose1.state.vel[1],pose1.state.vel[2]);
printf("Accel 1 = %1.3f %1.3f %1.3f\n",pose1.state.accel[0],pose1.state.accel[1],pose1.state.accel[2]);

// [print the rest of pose1 and pose2]
// [C code using pose1 and pose2]
}

C header file contains the following:
typedef double F64;

C header file contains the following:
typedef struct{
F64 pos[3];
F64 vel[3];
F64 accel[3];
} gnc_state_t;

typedef struct{
F64 quat[4];
F64 rate[3];
F64 accel[3];
} gnc_attitude_t;

typedef struct{
gnc_state_t state;
gnc_attitude_t attitude;
F64 time;
} gnc_pose_t;

extern gnc_pose_t diff_pose(gnc_pose_t pose1, gnc_pose_t pose2);



Java wrapper:
@FieldOrder({"pos", "vel", "accel"})
public static class gnc_state_t extends Structure {
public static class ByValue extends gnc_state_t implements Structure.ByValue {}
public double[] pos = new double[3];
public double[] vel = new double[3];
public double[] accel = new double[3];
}

@FieldOrder({"quat", "rate", "accel"})
public static class gnc_attitude_t extends Structure {
public static class ByValue extends gnc_attitude_t implements Structure.ByValue {}
public double[] quat = new double[4];
public double[] rate = new double[3];
public double[] accel = new double[3];
}

@FieldOrder({"state", "attitude", "time"})
public static class gnc_pose_t extends Structure {
public static class ByValue extends gnc_pose_t implements Structure.ByValue {}
public gnc_state_t.ByValue state;
public gnc_attitude_t.ByValue attitude;
public double time;
}

public interface GncMath_structs extends Library {
GncMath_structs INSTANCE = Native.load(sharedLibraryFilepath, GncMath_structs.class);
gnc_pose_t.ByValue diff_pose(gnc_pose_t.ByValue pose1, gnc_pose_t.ByValue pose2);
}



Java test code:
final double[] pos1 = {1, 2, 3};
final double[] vel1 = {4, 5, 6};
final double[] lin_accel1 = {7, 8, 9};
final double[] quat1 = {10, 11, 12, 13};
final double[] rate1 = {14, 15, 16};
final double[] ang_accel1 = {17, 18, 19};
final double time1 = 20;

final double[] pos2 = {21, 22, 23};
final double[] vel2 = {24, 25, 26};
final double[] lin_accel2 = {27, 28, 29};
final double[] quat2 = {30, 31, 32, 33};
final double[] rate2 = {34, 35, 36};
final double[] ang_accel2 = {37, 38, 39};
final double time2 = 40;

// assign vectors to structs
final JnaGncMath.gnc_state_t.ByValue state1 = new JnaGncMath.gnc_state_t.ByValue();
state1.pos = pos1;
state1.vel = vel1;
state1.accel = lin_accel1;

final JnaGncMath.gnc_state_t.ByValue state2 = new JnaGncMath.gnc_state_t.ByValue();
state2.pos = pos2;
state2.vel = vel2;
state2.accel = lin_accel2;

final JnaGncMath.gnc_attitude_t.ByValue attitude1 = new JnaGncMath.gnc_attitude_t.ByValue();
attitude1.quat = quat1;
attitude1.rate = rate1;
attitude1.accel = ang_accel1;

final JnaGncMath.gnc_attitude_t.ByValue attitude2 = new JnaGncMath.gnc_attitude_t.ByValue();
attitude2.quat = quat2;
attitude2.rate = rate2;
attitude2.accel = ang_accel2;

final JnaGncMath.gnc_pose_t.ByValue pose1 = new JnaGncMath.gnc_pose_t.ByValue();
pose1.state = state1;
pose1.attitude = attitude1;
pose1.time = time1;

final JnaGncMath.gnc_pose_t.ByValue pose2 = new JnaGncMath.gnc_pose_t.ByValue();
pose2.state = state2;
pose2.attitude = attitude2;
pose2.time = time2;

// call JNA method
final JnaGncMath.gnc_pose_t.ByValue pose3 = JnaGncMath.GncMath_structs.INSTANCE.diff_pose(pose1, pose2);



Values printed from the C code when the above Java test code is run:
Pose1:
Pos 1 = 1.000 2.000 3.000
Vel 1 = 4.000 5.000 6.000
Accel 1 = 7.000 8.000 9.000
Quat 1 = 10.000000000 11.000000000 12.000000000 13.000000000
Rate 1 = 14.000 15.000 16.000
Ang Accel 1 = 17.000 18.000 19.000
Time 1 = 21.000
Pose2:
Pos 2 = 22.000 23.000 24.000
Vel 2 = 25.000 26.000 27.000
Accel 2 = 28.000 29.000 30.000
Quat 2 = 31.000000000 32.000000000 33.000000000 34.000000000
Rate 2 = 35.000 36.000 37.000
Ang Accel 2 = 38.000 39.000 0.000
Time 2 = 0.000


The above values are the printed struct components as soon as the C code receives the structs. These values do not match what the structs were initialized with in the Java code. I have created a chart comparing the values in the structures in Java and in the C code:

Screen Shot 2022-04-20 at 12.55.25 PM.png

I would appreciate any pointers on how to properly wrap these structs so their values are preserved when passed to the C code.

Thank you!

Matthias Bläsing

unread,
Apr 24, 2022, 9:01:39 AM4/24/22
to jna-...@googlegroups.com
Hi,

Am Mittwoch, dem 20.04.2022 um 12:59 -0700 schrieb Kaitlyn:
>
> I am using JNA to wrap a C method that takes in nested structs. I
> believe that I am not properly storing the values in the Java
> Structures because I am seeing odd behavior with values shifting by
> one once the structs are passed to the C code.
>
> [DemoCode]
>
> The above values are the printed struct components as soon as the C
> code receives the structs. These values do not match what the structs
> were initialized with in the Java code. I have created a chart
> comparing the values in the structures in Java and in the C code:
>
> Screen Shot 2022-04-20 at 12.55.25 PM.png
>
> I would appreciate any pointers on how to properly wrap these structs
> so their values are preserved when passed to the C code.

for anyone interested I pushed a runnable sample here:

https://github.com/matthiasblaesing/JNA-Demos/tree/master/NestedStructures

Instructions how to run it ar in the README.md in the project
directory.

From a first impression it looks as if the last part of the structure
is not correctly passed:

Native: Pos 1 = 1,000 2,000 3,000
Native: Vel 1 = 4,000 5,000 6,000
Native: Accel 1 = 7,000 8,000 9,000

Native: Quat 1 = 10,000 11,000 12,000 13,000
Native: Rate 1 = 14,000 15,000 16,000
Native: AttAccel 1 = 17,000 18,000 19,000

Native: Time: 20,000
Native: Time2: 21,000
Native: Time3: 0,000

The zeroed member is always the last one.

Needs some investigation.

Greetings

Matthias

Kaitlyn

unread,
May 4, 2022, 5:27:26 PM5/4/22
to Java Native Access
Hi,

Thanks so much for the note! I'm wondering if there have been any updates on this issue.

Thanks!
Kaitlyn

Matthias Bläsing

unread,
May 10, 2022, 4:03:47 PM5/10/22
to jna-...@googlegroups.com
Hi Kaitlyn,

I think I know what is going on (at this point more a hunch):

libffi needs a description for each structure it handles. This
descriptions contains one entry per structure element. JNA creates the
descriptors (c.s.j.Structure.FFIType) per Structure and holds them in a
map. The map uses the class as key.

This is fine until you reach arrays. The arrays are represented as
structures and their component type is used as key. This is fine until
you have differently sized arrays. With the map there can only be a
single FFIType per array and this will break. It depends on
initialization order, which definition is used, but it will be broken
for one of the structures.

This might also be a potential cause for memory corruption
(speculation!).

Greetings

Matthias
> --
> You received this message because you are subscribed to the Google
> Groups "Java Native Access" group.
> To unsubscribe from this group and stop receiving emails from it,
> send an email to jna-users+...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/jna-users/9b923bd8-027d-4ae5-8712-e38a9b91463en%40googlegroups.com
> .

Matthias Bläsing

unread,
May 10, 2022, 4:36:54 PM5/10/22
to jna-...@googlegroups.com
Hi again,

as with most technical problems, once the core is identified, the
solution is not that difficult. A first draft can be found here:

https://github.com/java-native-access/jna/pull/1438

It fixed my testcase, which was broken before the change. The hard part
is polishing this and adding test(s) to verify, that it works.

To be clear: That PR is work in progress, I will force push there.

Greetings

Matthias

Matthias Bläsing

unread,
May 11, 2022, 4:06:23 PM5/11/22
to jna-...@googlegroups.com
Hi,

so it is done. Please have a look and if possible test the fix.

Greetings

Matthias
Reply all
Reply to author
Forward
0 new messages