Syed Momin Naqvi
Syed Momin Naqvi

Reputation: 35

Is my C struct correctly implemented in JNA?

I'm trying to interface a cash counter device. I have to use a struct and pass it to the method parameter which is defined in the cash counter's API documentation. The struct is given below:

typedef struct 
{
    unsigned int  iCurrencyNum;   // Number of acceptable currencies(The current version only supports single currency)
    unsigned char  acCurrencyIndex [64];    // Index of acceptable currencies,Reference appendix SNBC note coding standard 
    unsigned char   cSortLevel;         // Recognition level 0-low 1- medium 2-high
    unsigned char     acReserved[255];  // Reserve
} tInitSetting;

In my java application, I created the following class to be passed to the API in the method parameters.

import com.sun.jna.Structure;

import java.util.Arrays;
import java.util.List;

public class TInitSetting extends Structure {

    public int iCurrencyNum;
    public byte[] acCurrencyIndex = new byte[64];
    public byte cSortLevel;
//    public byte[] acReserved = new byte[64];

    @Override
    protected List<String> getFieldOrder() {
        return Arrays.asList(new String[]{
                "iCurrencyNum",
                "acCurrencyIndex",
                "cSortLevel"
        });
    }

    public static class ByReference extends TInitSetting implements Structure.ByReference {}
    public static class ByValue extends TInitSetting implements Structure.ByValue {}
}

I run the method from the API and it works fine. The response is "Success" however the cash counter does not get initialized (which is what the method in the API does).

I suspect it's something to do with this class. Is this correct?

Upvotes: 1

Views: 305

Answers (1)

Daniel Widdis
Daniel Widdis

Reputation: 9091

The definition of "correctly" is dependent on the API, which you haven't shown.

However, I will comment on your structure implementation and what could possibly make it incorrect, in a more general sense that may help others mapping structures.

Type Mapping

Your choice of Java int for Native (unsigned) int and Java byte for Native (unsigned) char is correct, and you have correctly mapped the first three elements. This includes using the public modifier and having their order declared in the getFieldOrder() method.

Of note, since JNA 5 you can declare the field order in an annotation, so you could just put this annotation above your class to accomplish the same thing with less boilerplate:

@FieldOrder ({"iCurrencyNum", "acCurrencyIndex", "cSortLevel"})

Not Mapping All Fields

You have not mapped acReserved which would just be a byte[255] similar to your mapping of acCurrencyIndex. This is sometimes OK but highly dependent on the API. Is there a particular reason you chose to exclude it?

Assuming the function has a * tInitSetting argument, all you're really passing to that argument is a pointer to the beginning of the structure. It doesn't know where the structure ends, unless there is also an argument for the size. Many functions respect the size argument and write "up to size bytes" to the structure. Some functions use the size argument to determine a "version" of the structure and write different things based on that.

Ultimately you need to read the API documentation to know if it's acceptable to pass a smaller structure than it asks for.

ByReference / ByValue

These are generally not needed if using the default behavior (ByReference in function/method arguments where the native signature is * tInitSetting, and ByValue as a nested structure where the native structure includes the type tInitSetting (without the *).)

You haven't indicated how you're using the structure, but if you're using these tags, you may be using them improperly. In particular, if you are passing ByValue when it is expecting a pointer, it won't work.

autoRead

Your symptom of the function call succeeding but your structure not being updated could be a case of autoRead() not processing. The native function populates native memory with the structure results, but those still need to be copied over to Java using read().

This normally happens automatically when passing a structure by reference, but given you've added both ByReference and ByValue tags, it's possible the lack of update is a symptom of choosing the wrong one.

You could test by reading the structure value after manually calling read() on it.

Upvotes: 1

Related Questions