cknelle
cknelle

Reputation: 159

Invalid memory access with Structure containing char*

I'm trying to map a Structure with contains some char * fields using JNA. I get the following exception :

Exception in thread "main" java.lang.Error: Invalid memory access

Here is my C code :

The signature of the function called

extern "C" MATHLIBRARY_API ERR ErrorTest(); 

The C sample implementation I did as an example :

ERR sngErrorTest() {
    ERR err;
    char message[] = "My message";
    char filename[] = "My file Name";

    err.errorCode = OK;
    err.errorDetails.fileName = NULL;
    err.errorDetails.lineNumber = 1;
    err.errorDetails.message = message;
    err.errorDetails.fileName = filename;
    return err;
}

The C types definition :

typedef enum {
    OK = 0, 
    ERR_ONE,
    ERR_TWO,
    ERR_THREE,
    ERR_FOUR,
    ERR_FIVE,
    ERR_SIX,
} ERR_CODE;

typedef struct {
    char *fileName; 
    char *message; 
    int lineNumber; 
} ERR_DETAILS;

typedef struct {
    ERR_CODE errorCode; 
    ERR_DETAILS errorDetails; 
} ERR;

Here is my java code :

public interface IFunctions extends Library {
    public static class ERR_DETAILS extends Structure {
        public static class ByValue extends ERR_DETAILS implements Structure.ByValue {}
        public static class ByReference extends ERR_DETAILS implements Structure.ByReference {}
        public Pointer fileName; 
        public Pointer message; 
        public int lineNumber; 
        @Override
        protected List<String> getFieldOrder() {
            return Arrays.asList(new String[] { "fileName", "message","lineNumber"});
        }
    }
    public static class ERR_CODE {
        public static int OK = 0; 
        public static int ERR_ONE = 1;
        public static int ERR_TWO = 2;
        public static int ERR_THREE = 3;
        public static int ERR_FOUR = 4;
        public static int ERR_FIVE = 5;
        public static int ERR_SIX = 6;

    }
    
    public static class ERR extends Structure {
        public static class ByValue extends ERR implements Structure.ByValue {}
        public static class ByReference extends ERR implements Structure.ByReference {}
        public ERR_CODE errorCode;
        public ERR_DETAILS errorDetails;
        @Override
        protected List<String> getFieldOrder() {
            return Arrays.asList(new String[] { "errorCode", "errorDetails"});
        }
    }
    
    public ERR ErrorTest();
    }

Here is my java test main class :

ERR err = IFunctions.ErrorTest();
  1. I replaced Pointer by String regarding char * types but I get the same issue.
  2. Once I retrieved fileName item as a Pointer, how does I get the value which is refered by fileName? Is it err.errorDetails.fileName.getString(0); ?

Thus I'm trying to isolate each type of the structure and I created as a sample the following C function :

signature :

    extern "C" MATHLIBRARY_API ERR_CODE ErrorCode();

C implementation :

ERR_CODE ErrorCode() {
    ERR_CODE err_code;
    err_code = OK;
    return err_code;
}  

Java declaration

public interface IFunctions extends Library {
....
public ERR_CODE ErrorCode();
....
}

Call of my jna function :

ERR_CODE errCode = IFunctions.ErrorCode();

In that case I get the following error : Exception in thread "main" java.lang.IllegalArgumentException: Unsupported return type class ERR_CODE in function ErrorCode

Did I forget somethink regarding the enum type definition?

Upvotes: 2

Views: 209

Answers (2)

cknelle
cknelle

Reputation: 159

Finally I get the response to my issue.

In my JNA wrapper I had to explicitly defined that my function returns a structure by Value and not a structure By reference as it is done by default by JNA. As described in JNA API regarding Structure class : "When used as a function parameter or return value, this class corresponds to struct*. When used as a field within another Structure, it corresponds to struct. The tagging interfaces Structure.ByReference and Structure.ByValue may be used to alter the default behavior."

Thus the solution is as follow :

public interface IFunctions extends Library {
...
public ERR.ByValue ErrorTest();
}

Nothing else to change. Thanx for your help.

Upvotes: 1

Daniel Widdis
Daniel Widdis

Reputation: 9131

The problem is in your mapping of the ERR_CODE enum.

JNA does not have a direct mapping for the enum type, as those types are usually 32-bit integers. However, you have mapped it as a plain java class (extends Object) which doesn't have any JNA mapping.

The conventional way to map enum in JNA is to wrap the int values in an interface. (This is simply for self-documenting code. In reality all you really need is the int value.)

So you should change your ERR_CODE mapping to:

interface ERR_CODE {
    int OK = 0; 
    int ERR_ONE = 1;
    int ERR_TWO = 2;
    int ERR_THREE = 3;
    int ERR_FOUR = 4;
    int ERR_FIVE = 5;
    int ERR_SIX = 6;
}

Note the public and static modifiers are redundant for interface members.

Then you should also change your ERR structure mapping to:

@FieldOrder({"errorCode", "errorDetails"})
class ERR extends Structure {
    public int errorCode; // ERR_CODE
    public ERR_DETAILS errorDetails;
}

Note I just mapped the enum as an int, using the comment field to denote the specific type (enum) the int represents.

There is a potential alignment issue here, which can be platform dependent. Normally the largest byte size is used for alignment, and because the ERR_DETAILS mapping includes 8-byte pointers in it, there could be misalignment between the 4-byte int and the byte boundary for the next element (e.g., errorDetails may start at an offset of 0x8 rather than 0x4. To fix this you may need to set the alignment type in the structure, changing the mapping to:

@FieldOrder({"errorCode", "errorDetails"})
class ERR extends Structure {
    public int errorCode; // ERR_CODE
    public ERR_DETAILS errorDetails;

    public ERR() {
        super();
        setAlignType(ALIGN_NONE);
    }
}

Note also, I have also removed the unnecessary ByValue and ByReference subclasses. You rarely need to define those -- inside a structure, ByValue is the default and in method arguments ByReference is the default, and you only need to define these in rare cases where the default is not used.

I have also used the JNA 5.x @FieldOrder annotation rather than overriding the method.

You can use String in the structure mappings for char * instead of Pointer, although the Pointer and getString() are the correct way to handle those if you choose to keep that mapping:

@FieldOrder({"fileName", "message", "lineNumber"})
class ERR_DETAILS extends Structure {
    public String fileName; 
    public String message; 
    public int lineNumber; 
}

Upvotes: 1

Related Questions