cknelle
cknelle

Reputation: 159

JNA Structure.getFieldOrder() does not match declared field names

I have the following exception defining a JNA structure inside a Structure :

Exception in thread "main" java.lang.Error: Structure.getFieldOrder() on class com.MyInterface$mine$ByReference returns names ([color, data, hello, rice, str, wild]) which do not match declared field names

See my Cpp Structures:

typedef struct s_mine
{
    e_color           color;    //e_color is enum type made of int values
    his               data;        
    int               str;        
    unsigned int      wild;         
    unsigned int      hello;        
    float             rice; 
} mine;

typedef struct s_his
{
    unsigned char * data; 
    unsigned int    size; 
} his;

See my sample below i.e MyInterface.java:

public interface MyInterface extends Library {

    public static class his extends Structure {
        public static class ByReference extends his implements Structure.ByReference {} // Need the stucture address as it a parameter of a particular wrapped method
        public static class ByValue extends his implements Structure.ByValue {}         // Need the structure value inside "mine" Structure
        public Pointer data;
        public int size;
        @Override
        protected List<String> getFieldOrder() {
            return Arrays.asList(new String[] { "data", "size"});
        }
    }
    
    public class mine extends Structure {
        public static class ByReference extends mine implements Structure.ByReference {}
        int color; 
        his.ByValue data;
        int str; 
        int wild; 
        int hello; 
        float rice;

        @Override
        protected List<String> getFieldOrder() {
            return Arrays.asList(new String[] {"color","data","str","wild","hello","rice"});
        }
    }
}

The main class using MyInterface contains the following line on which I have the exception regarding the order of "mine" Structure's fields:

final MyInterface.mine.ByReference mine_ref = new MyInterface.mine.ByReference();   

Thus I debug the code in order to step into JNA following Structure.class into the following getFields method (in which I get the exception (second one below):

    protected List<Field> getFields(boolean force) {
        List<Field> flist = getFieldList();
        Set<String> names = new HashSet<String>();
        for (Field f : flist) {
            names.add(f.getName());
        }

        List<String> fieldOrder = fieldOrder();
        if (fieldOrder.size() != flist.size() && flist.size() > 1) {
            if (force) {
                throw new Error("Structure.getFieldOrder() on " + getClass()
                                + (fieldOrder.size() < flist.size()
                                    ? " does not provide enough"
                                    : " provides too many")
                                + " names [" + fieldOrder.size()
                                + "] ("
                                + sort(fieldOrder)
                                + ") to match declared fields [" + flist.size()
                                + "] ("
                                + sort(names)
                                + ")");
            }
            return null;
        }

        Set<String> orderedNames = new HashSet<String>(fieldOrder);
        if (!orderedNames.equals(names)) {
            throw new Error("Structure.getFieldOrder() on " + getClass()
                            + " returns names ("
                            + sort(fieldOrder)
                            + ") which do not match declared field names ("
                            + sort(names) + ")");
        }

        sortFields(flist, fieldOrder);
        return flist;
    }

This is what I get exploring the following fields value as the exception is raised in getFields method

fieldOrder = [color, data, str, wild, hello, rice]
orderedNames = [str, color, data, hello, rice, wild]

The exception is raised because names is empty. Anyway, as HashSet java.util class does not guarantee that the order will remain constant over time, I definitely think that there is bug in JNA Structure.java. Because my fieldOrder's order is sorted in a random order by new HashSet<String>(fieldOrder) instruction and thus different from orderedNames's order.

  1. Does anyone share my point of view or did I miss something regarding the way to declare and use my JNA structures?

  2. If there is no bug (i.e HashSet is used on purpose...) did I make something wrong declaring my structures? How can I fix the issue?

Upvotes: 1

Views: 3198

Answers (1)

Daniel Widdis
Daniel Widdis

Reputation: 9130

Your error is in the mine structure: You need to declare the fields as public. JNA uses reflection and relies on the public modifier for fields that are intended to be mapped to native; otherwise they're just considered helper fields in the class.

Otherwise, the mappings would work as you have them, but consider the following improvements:

  • You don't need to use the ByValue version of the structure when used as a nested structure. That's the default. The only time you need to take special action with a nested structure is if it's ByReference.
    • Similarly, ByReference is the default when used as a function argument. Only if the function requires a ByValue argument does it require special treatment.
  • The use of getFieldOrder() override was replaced in JNA 5.x with the @FieldOrder annotation. While what you have works, your code is more readable if you just put that annotation before your structure, e.g.,
@FieldOrder({"data", "size"})
class his extends Structure { 
  // etc... 
}
  • Interface variables/classes/etc. are by default public, static, and final so use of those modifiers is redundant.

Upvotes: 3

Related Questions