Reputation: 159
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();
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
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
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