com.sun.jna.Structure Maven / Gradle / Ivy
Show all versions of platform Show documentation
/* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package com.sun.jna;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.Buffer;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.zip.Adler32;
/**
* Represents a native structure with a Java peer 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 {@link ByReference} and {@link ByValue} may be used
* to alter the default behavior. Structures may have variable size, but only
* by providing an array field (e.g. byte[]).
*
* See the overview for supported
* type mappings for struct fields.
*
* Structure alignment and type mappings are derived by default from the
* enclosing interface definition (if any) by using
* {@link Native#getStructureAlignment} and {@link Native#getTypeMapper}.
* Alternatively you can explicitly provide alignment, field order, or type
* mapping by calling the respective functions in your subclass's
* constructor.
*
* Structure fields corresponding to native struct fields must be
* public. They may additionally have the following modifiers:
*
* volatile
JNA will not write the field unless specifically
* instructed to do so via {@link #writeField(String)}. This allows you to
* prevent inadvertently overwriting memory that may be updated in real time
* on another (possibly native) thread.
* final
JNA will overwrite the field via {@link #read()},
* but otherwise the field is not modifiable from Java. Take care when using
* this option, since the compiler will usually assume all accesses
* to the field (for a given Structure instance) have the same value. This
* modifier is invalid to use on J2ME.
*
* NOTE: Strings are used to represent native C strings because usage of
* char *
is generally more common than wchar_t *
.
*
* NOTE: This class assumes that fields are returned in {@link Class#getFields}
* in the same or reverse order as declared. If your VM returns them in
* no particular order, you need to explicitly indicate the order with {@link
* #setFieldOrder} in your subclass constructor.
*
* NOTE: In general, instances of this class are not synchronized.
*
*
* @author Todd Fast, [email protected]
* @author [email protected]
*/
public abstract class Structure {
/** Tagging interface to indicate the value of an instance of the
* Structure
type is to be used in function invocations rather
* than its address. The default behavior is to treat
* Structure
function parameters and return values as by
* reference, meaning the address of the structure is used.
*/
public interface ByValue { }
/** Tagging interface to indicate the address of an instance of the
* Structure type is to be used within a Structure
definition
* rather than nesting the full Structure contents. The default behavior
* is to inline Structure
fields.
*/
public interface ByReference { }
private static class MemberOrder {
private static final String[] FIELDS = {
"first", "second", "middle", "penultimate", "last",
};
public int first;
public int second;
public int middle;
public int penultimate;
public int last;
}
private static final boolean REVERSE_FIELDS;
private static final boolean REQUIRES_FIELD_ORDER;
static final boolean isPPC;
static final boolean isSPARC;
static final boolean isARM;
static {
// Check for predictable field order; IBM and JRockit store fields in
// reverse order; Excelsior JET requires explicit order
Field[] fields = MemberOrder.class.getFields();
List names = new ArrayList();
for(int i=0;i < fields.length;i++) {
names.add(fields[i].getName());
}
List expected = Arrays.asList(MemberOrder.FIELDS);
List reversed = new ArrayList(expected);
Collections.reverse(reversed);
REVERSE_FIELDS = names.equals(reversed);
REQUIRES_FIELD_ORDER = !(names.equals(expected) || REVERSE_FIELDS);
String arch = System.getProperty("os.arch").toLowerCase();
isPPC = "ppc".equals(arch) || "powerpc".equals(arch);
isSPARC = "sparc".equals(arch);
isARM = "arm".equals(arch);
}
/** Use the platform default alignment. */
public static final int ALIGN_DEFAULT = 0;
/** No alignment, place all fields on nearest 1-byte boundary */
public static final int ALIGN_NONE = 1;
/** validated for 32-bit x86 linux/gcc; align field size, max 4 bytes */
public static final int ALIGN_GNUC = 2;
/** validated for w32/msvc; align on field size */
public static final int ALIGN_MSVC = 3;
/** Align to a 2-byte boundary. */
//public static final int ALIGN_2 = 4;
/** Align to a 4-byte boundary. */
//public static final int ALIGN_4 = 5;
/** Align to an 8-byte boundary. */
//public static final int ALIGN_8 = 6;
static final int MAX_GNUC_ALIGNMENT =
isSPARC || ((isPPC || isARM) && Platform.isLinux())
? 8 : Native.LONG_SIZE;
protected static final int CALCULATE_SIZE = -1;
static final Map layoutInfo = new WeakHashMap();
// This field is accessed by native code
private Pointer memory;
private int size = CALCULATE_SIZE;
private int alignType;
private int structAlignment;
private Map structFields;
// Keep track of java strings which have been converted to C strings
private final Map nativeStrings = new HashMap();
private TypeMapper typeMapper;
// This field is accessed by native code
private long typeInfo;
private List fieldOrder;
private boolean autoRead = true;
private boolean autoWrite = true;
private Structure[] array;
protected Structure() {
this((Pointer)null);
}
protected Structure(TypeMapper mapper) {
this((Pointer)null, ALIGN_DEFAULT, mapper);
}
/** Create a structure cast onto preallocated memory. */
protected Structure(Pointer p) {
this(p, ALIGN_DEFAULT);
}
protected Structure(Pointer p, int alignType) {
this(p, alignType, null);
}
protected Structure(Pointer p, int alignType, TypeMapper mapper) {
setAlignType(alignType);
setTypeMapper(mapper);
if (p != null) {
useMemory(p);
}
else {
allocateMemory(CALCULATE_SIZE);
}
}
/** Return all fields in this structure (ordered). This represents the
* layout of the structure, and will be shared among Structures of the
* same class except when the Structure can have a variable size.
* NOTE: {@link #ensureAllocated()} must be called prior to
* calling this method.
*/
Map fields() {
return structFields;
}
/** Return the type mapper in effect for this Structure. */
TypeMapper getTypeMapper() {
return typeMapper;
}
/** Change the type mapping for this structure. May cause the structure
* to be resized and any existing memory to be reallocated.
* If null
, the default mapper for the
* defining class will be used.
*/
protected void setTypeMapper(TypeMapper mapper) {
if (mapper == null) {
Class declaring = getClass().getDeclaringClass();
if (declaring != null) {
mapper = Native.getTypeMapper(declaring);
}
}
this.typeMapper = mapper;
this.size = CALCULATE_SIZE;
if (this.memory instanceof AutoAllocated) {
this.memory = null;
}
}
/** Change the alignment of this structure. Re-allocates memory if
* necessary. If alignment is {@link #ALIGN_DEFAULT}, the default
* alignment for the defining class will be used.
*/
protected void setAlignType(int alignType) {
if (alignType == ALIGN_DEFAULT) {
Class declaring = getClass().getDeclaringClass();
if (declaring != null)
alignType = Native.getStructureAlignment(declaring);
if (alignType == ALIGN_DEFAULT) {
if (Platform.isWindows())
alignType = ALIGN_MSVC;
else
alignType = ALIGN_GNUC;
}
}
this.alignType = alignType;
this.size = CALCULATE_SIZE;
if (this.memory instanceof AutoAllocated) {
this.memory = null;
}
}
protected Memory autoAllocate(int size) {
return new AutoAllocated(size);
}
/** Set the memory used by this structure. This method is used to
* indicate the given structure is nested within another or otherwise
* overlaid on some other memory block and thus does not own its own
* memory.
*/
protected void useMemory(Pointer m) {
useMemory(m, 0);
}
/** Set the memory used by this structure. This method is used to
* indicate the given structure is nested within another or otherwise
* overlaid on some other memory block and thus does not own its own
* memory.
*/
protected void useMemory(Pointer m, int offset) {
// Invoking size() here is important when this method is invoked
// from the ctor, to ensure fields are properly scanned and allocated
try {
// Set the structure's memory field temporarily to avoid
// auto-allocating memory in the call to size()
this.memory = m.share(offset);
if (size == CALCULATE_SIZE) {
size = calculateSize(false);
}
if (size != CALCULATE_SIZE) {
this.memory = m.share(offset, size);
}
this.array = null;
}
catch(IndexOutOfBoundsException e) {
throw new IllegalArgumentException("Structure exceeds provided memory bounds");
}
}
/** Ensure this memory has its size and layout calculated and its
memory allocated. */
protected void ensureAllocated() {
ensureAllocated(false);
}
/** Ensure this memory has its size and layout calculated and its
memory allocated.
@param avoidFFIType used when computing FFI type information
to avoid recursion
*/
private void ensureAllocated(boolean avoidFFIType) {
if (memory == null) {
allocateMemory(avoidFFIType);
}
else if (size == CALCULATE_SIZE) {
this.size = calculateSize(true, avoidFFIType);
}
}
/** Attempt to allocate memory if sufficient information is available.
* Returns whether the operation was successful.
*/
protected void allocateMemory() {
allocateMemory(false);
}
private void allocateMemory(boolean avoidFFIType) {
allocateMemory(calculateSize(true, avoidFFIType));
}
/** Provided for derived classes to indicate a different
* size than the default. Returns whether the operation was successful.
* Will leave memory untouched if it is non-null and not allocated
* by this class.
*/
protected void allocateMemory(int size) {
if (size == CALCULATE_SIZE) {
// Analyze the struct, but don't worry if we can't yet do it
size = calculateSize(false);
}
else if (size <= 0) {
throw new IllegalArgumentException("Structure size must be greater than zero: " + size);
}
// May need to defer size calculation if derived class not fully
// initialized
if (size != CALCULATE_SIZE) {
if (this.memory == null
|| this.memory instanceof AutoAllocated) {
this.memory = autoAllocate(size);
}
this.size = size;
}
}
public int size() {
ensureAllocated();
if (size == CALCULATE_SIZE) {
size = calculateSize(true);
}
return size;
}
public void clear() {
memory.clear(size());
}
/** Return a {@link Pointer} object to this structure. Note that if you
* use the structure's pointer as a function argument, you are responsible
* for calling {@link #write()} prior to the call and {@link #read()}
* after the call. These calls are normally handled automatically by the
* {@link Function} object when it encounters a {@link Structure} argument
* or return value.
*/
public Pointer getPointer() {
ensureAllocated();
return memory;
}
//////////////////////////////////////////////////////////////////////////
// Data synchronization methods
//////////////////////////////////////////////////////////////////////////
// Keep track of ByReference reads to avoid creating multiple structures
// mapped to the same address
private static final ThreadLocal reads = new ThreadLocal() {
protected synchronized Object initialValue() {
return new HashMap();
}
};
// Keep track of what is currently being read/written to avoid redundant
// reads (avoids problems with circular references).
private static final ThreadLocal busy = new ThreadLocal() {
/** Avoid using a hash-based implementation since the hash code
for a Structure is not immutable.
*/
class StructureSet extends AbstractCollection implements Set {
private Structure[] elements;
private int count;
private void ensureCapacity(int size) {
if (elements == null) {
elements = new Structure[size*3/2];
}
else if (elements.length < size) {
Structure[] e = new Structure[size*3/2];
System.arraycopy(elements, 0, e, 0, elements.length);
elements = e;
}
}
public int size() { return count; }
public boolean contains(Object o) {
return indexOf(o) != -1;
}
public boolean add(Object o) {
if (!contains(o)) {
ensureCapacity(count+1);
elements[count++] = (Structure)o;
}
return true;
}
private int indexOf(Object o) {
Structure s1 = (Structure)o;
for (int i=0;i < count;i++) {
Structure s2 = elements[i];
if (s1 == s2
|| (s1.getClass() == s2.getClass()
&& s1.size() == s2.size()
&& s1.getPointer().equals(s2.getPointer()))) {
return i;
}
}
return -1;
}
public boolean remove(Object o) {
int idx = indexOf(o);
if (idx != -1) {
if (--count > 0) {
elements[idx] = elements[count];
elements[count] = null;
}
return true;
}
return false;
}
/** Simple implementation so that toString() doesn't break.
Provides an iterator over a snapshot of this Set.
*/
public Iterator iterator() {
Structure[] e = new Structure[count];
if (count > 0) {
System.arraycopy(elements, 0, e, 0, count);
}
return Arrays.asList(e).iterator();
}
}
protected synchronized Object initialValue() {
return new StructureSet();
}
};
static Set busy() {
return (Set)busy.get();
}
static Map reading() {
return (Map)reads.get();
}
/**
* Reads the fields of the struct from native memory
*/
public void read() {
// convenience: allocate memory and/or calculate size if it hasn't
// been already; this allows structures to do field-based
// initialization of arrays and not have to explicitly call
// allocateMemory in a ctor
ensureAllocated();
// Avoid redundant reads
if (busy().contains(this)) {
return;
}
busy().add(this);
if (this instanceof Structure.ByReference) {
reading().put(getPointer(), this);
}
try {
for (Iterator i=fields().values().iterator();i.hasNext();) {
readField((StructField)i.next());
}
}
finally {
busy().remove(this);
if (reading().get(getPointer()) == this) {
reading().remove(getPointer());
}
}
}
/** Returns the calculated offset of the given field. */
protected int fieldOffset(String name) {
ensureAllocated();
StructField f = (StructField)fields().get(name);
if (f == null)
throw new IllegalArgumentException("No such field: " + name);
return f.offset;
}
/** Force a read of the given field from native memory. The Java field
* will be updated from the current contents of native memory.
* @return the new field value, after updating
* @throws IllegalArgumentException if no field exists with the given name
*/
public Object readField(String name) {
ensureAllocated();
StructField f = (StructField)fields().get(name);
if (f == null)
throw new IllegalArgumentException("No such field: " + name);
return readField(f);
}
/** Obtain the value currently in the Java field. Does not read from
* memory.
*/
Object getField(StructField structField) {
try {
return structField.field.get(this);
}
catch (Exception e) {
throw new Error("Exception reading field '"
+ structField.name + "' in " + getClass()
+ ": " + e);
}
}
void setField(StructField structField, Object value) {
setField(structField, value, false);
}
void setField(StructField structField, Object value, boolean overrideFinal) {
try {
structField.field.set(this, value);
}
catch(IllegalAccessException e) {
int modifiers = structField.field.getModifiers();
if (Modifier.isFinal(modifiers)) {
if (overrideFinal) {
// WARNING: setAccessible(true) on J2ME does *not* allow overwriting of
// a final field.
throw new UnsupportedOperationException("This VM does not support Structures with final fields (field '" + structField.name + "' within " + getClass() + ")");
}
throw new UnsupportedOperationException("Attempt to write to read-only field '" + structField.name + "' within " + getClass());
}
throw new Error("Unexpectedly unable to write to field '"
+ structField.name + "' within " + getClass()
+ ": " + e);
}
}
/** Only keep the original structure if its native address is unchanged.
* Otherwise replace it with a new object.
* @param type Structure subclass
* @param s Original Structure object
* @param address the native struct *
* @return Updated Structure.ByReference
object
*/
static Structure updateStructureByReference(Class type, Structure s, Pointer address) {
if (address == null) {
s = null;
}
else {
if (s == null || !address.equals(s.getPointer())) {
Structure s1 = (Structure)reading().get(address);
if (s1 != null && type.equals(s1.getClass())) {
s = s1;
}
else {
s = newInstance(type);
s.useMemory(address);
}
}
s.autoRead();
}
return s;
}
/** Read the given field and return its value. The Java field will be
* updated from the contents of native memory.
*/
// TODO: make overridable method with calculated native type, offset, etc
Object readField(StructField structField) {
// Get the offset of the field
int offset = structField.offset;
// Determine the type of the field
Class fieldType = structField.type;
FromNativeConverter readConverter = structField.readConverter;
if (readConverter != null) {
fieldType = readConverter.nativeType();
}
// Get the current value only for types which might need to be preserved
Object currentValue = (Structure.class.isAssignableFrom(fieldType)
|| Callback.class.isAssignableFrom(fieldType)
|| (Platform.HAS_BUFFERS && Buffer.class.isAssignableFrom(fieldType))
|| Pointer.class.isAssignableFrom(fieldType)
|| NativeMapped.class.isAssignableFrom(fieldType)
|| fieldType.isArray())
? getField(structField) : null;
Object result = memory.getValue(offset, fieldType, currentValue);
if (readConverter != null) {
result = readConverter.fromNative(result, structField.context);
}
// Update the value on the field
setField(structField, result, true);
return result;
}
/**
* Writes the fields of the struct to native memory
*/
public void write() {
// convenience: allocate memory if it hasn't been already; this
// allows structures to do field-based initialization of arrays and not
// have to explicitly call allocateMemory in a ctor
ensureAllocated();
// Update native FFI type information, if needed
if (this instanceof ByValue) {
getTypeInfo();
}
// Avoid redundant writes
if (busy().contains(this)) {
return;
}
busy().add(this);
try {
// Write all fields, except those marked 'volatile'
for (Iterator i=fields().values().iterator();i.hasNext();) {
StructField sf = (StructField)i.next();
if (!sf.isVolatile) {
writeField(sf);
}
}
}
finally {
busy().remove(this);
}
}
/** Write the given field to native memory. The current value in the Java
* field will be translated into native memory.
* @throws IllegalArgumentException if no field exists with the given name
*/
public void writeField(String name) {
ensureAllocated();
StructField f = (StructField)fields().get(name);
if (f == null)
throw new IllegalArgumentException("No such field: " + name);
writeField(f);
}
/** Write the given field value to the field and native memory. The
* given value will be written both to the Java field and the
* corresponding native memory.
* @throws IllegalArgumentException if no field exists with the given name
*/
public void writeField(String name, Object value) {
ensureAllocated();
StructField f = (StructField)fields().get(name);
if (f == null)
throw new IllegalArgumentException("No such field: " + name);
setField(f, value);
writeField(f);
}
void writeField(StructField structField) {
if (structField.isReadOnly)
return;
// Get the offset of the field
int offset = structField.offset;
// Get the value from the field
Object value = getField(structField);
// Determine the type of the field
Class fieldType = structField.type;
ToNativeConverter converter = structField.writeConverter;
if (converter != null) {
value = converter.toNative(value, new StructureWriteContext(this, structField.field));
fieldType = converter.nativeType();
}
// Java strings get converted to C strings, where a Pointer is used
if (String.class == fieldType
|| WString.class == fieldType) {
// Allocate a new string in memory
boolean wide = fieldType == WString.class;
if (value != null) {
NativeString nativeString = new NativeString(value.toString(), wide);
// Keep track of allocated C strings to avoid
// premature garbage collection of the memory.
nativeStrings.put(structField.name, nativeString);
value = nativeString.getPointer();
}
else {
value = null;
nativeStrings.remove(structField.name);
}
}
try {
memory.setValue(offset, value, fieldType);
}
catch(IllegalArgumentException e) {
String msg = "Structure field \"" + structField.name
+ "\" was declared as " + structField.type
+ (structField.type == fieldType
? "" : " (native type " + fieldType + ")")
+ ", which is not supported within a Structure";
throw new IllegalArgumentException(msg);
}
}
private boolean hasFieldOrder() {
synchronized(this) {
return fieldOrder != null;
}
}
protected List getFieldOrder() {
synchronized(this) {
if (fieldOrder == null) {
fieldOrder = new ArrayList();
}
return fieldOrder;
}
}
/** Provided for VMs where the field order as returned by {@link
* Class#getFields()} is not predictable.
*/
protected void setFieldOrder(String[] fields) {
getFieldOrder().addAll(Arrays.asList(fields));
// Force recalculation of size/field layout, since
// differing field order may result in different padding/alignment
this.size = CALCULATE_SIZE;
if (this.memory instanceof AutoAllocated) {
this.memory = null;
}
}
/** Sort the structure fields according to the given array of names. */
protected void sortFields(List fields, List names) {
for (int i=0;i < names.size();i++) {
String name = (String)names.get(i);
for (int f=0;f < fields.size();f++) {
Field field = (Field)fields.get(f);
if (name.equals(field.getName())) {
Collections.swap(fields, i, f);
break;
}
}
}
}
protected List getFields(boolean force) {
// Restrict to valid fields
List flist = new ArrayList();
for (Class cls = getClass();
!cls.equals(Structure.class);
cls = cls.getSuperclass()) {
List classFields = new ArrayList();
Field[] fields = cls.getDeclaredFields();
for (int i=0;i < fields.length;i++) {
int modifiers = fields[i].getModifiers();
if (Modifier.isStatic(modifiers)
|| !Modifier.isPublic(modifiers))
continue;
classFields.add(fields[i]);
}
if (REVERSE_FIELDS) {
Collections.reverse(classFields);
}
flist.addAll(0, classFields);
}
if (REQUIRES_FIELD_ORDER || hasFieldOrder()) {
List fieldOrder = getFieldOrder();
if (fieldOrder.size() < flist.size()) {
if (force) {
throw new Error("This VM does not store fields in a predictable order; you must use Structure.setFieldOrder to explicitly indicate the field order: " + System.getProperty("java.vendor") + ", " + System.getProperty("java.version"));
}
return null;
}
sortFields(flist, fieldOrder);
}
return flist;
}
/** Compare this Structure's known field order against the given. */
private synchronized boolean fieldOrderMatch(List fieldOrder) {
return this.fieldOrder == fieldOrder
|| (this.fieldOrder != null && this.fieldOrder.equals(fieldOrder));
}
/** Calculate the amount of native memory required for this structure.
* May return {@link #CALCULATE_SIZE} if the size can not yet be
* determined (usually due to fields in the derived class not yet
* being initialized).
*
* If the force
parameter is true
will throw
* an {@link IllegalStateException} if the size can not be determined.
* @throws IllegalStateException an array field is not initialized
* @throws IllegalArgumentException when an unsupported field type is
* encountered
*/
private int calculateSize(boolean force) {
return calculateSize(force, false);
}
int calculateSize(boolean force, boolean avoidFFIType) {
LayoutInfo info;
boolean needsInit = true;
synchronized(layoutInfo) {
info = (LayoutInfo)layoutInfo.get(getClass());
}
if (info == null
|| this.alignType != info.alignType
|| this.typeMapper != info.typeMapper
|| !fieldOrderMatch(info.fieldOrder)) {
info = deriveLayout(force, avoidFFIType);
needsInit = false;
}
if (info != null) {
this.structAlignment = info.alignment;
this.structFields = info.fields;
info.alignType = this.alignType;
info.typeMapper = this.typeMapper;
info.fieldOrder = this.fieldOrder;
if (!info.variable) {
synchronized(layoutInfo) {
layoutInfo.put(getClass(), info);
}
}
if (needsInit) {
initializeFields();
}
return info.size;
}
return CALCULATE_SIZE;
}
/** Keep track of structure layout information. Alignment type, type
mapper, and explicit field order will affect this information.
*/
private class LayoutInfo {
int size = CALCULATE_SIZE;
int alignment = 1;
Map fields = Collections.synchronizedMap(new LinkedHashMap());
int alignType = ALIGN_DEFAULT;
TypeMapper typeMapper;
List fieldOrder;
boolean variable;
}
/** Calculates the size, alignment, and field layout of this structure.
Also initializes any null-valued inline Structures.
*/
private LayoutInfo deriveLayout(boolean force, boolean avoidFFIType) {
LayoutInfo info = new LayoutInfo();
int calculatedSize = 0;
List fields = getFields(force);
if (fields == null) {
return null;
}
boolean firstField = true;
for (Iterator i=fields.iterator();i.hasNext();firstField=false) {
Field field = (Field)i.next();
int modifiers = field.getModifiers();
Class type = field.getType();
if (type.isArray()) {
info.variable = true;
}
StructField structField = new StructField();
structField.isVolatile = Modifier.isVolatile(modifiers);
structField.isReadOnly = Modifier.isFinal(modifiers);
if (structField.isReadOnly) {
if (!Platform.RO_FIELDS) {
throw new IllegalArgumentException("This VM does not support read-only fields (field '"
+ field.getName() + "' within " + getClass() + ")");
}
// In J2SE VMs, this allows overriding the value of final fields
field.setAccessible(true);
}
structField.field = field;
structField.name = field.getName();
structField.type = type;
// Check for illegal field types
if (Callback.class.isAssignableFrom(type) && !type.isInterface()) {
throw new IllegalArgumentException("Structure Callback field '"
+ field.getName()
+ "' must be an interface");
}
if (type.isArray()
&& Structure.class.equals(type.getComponentType())) {
String msg = "Nested Structure arrays must use a "
+ "derived Structure type so that the size of "
+ "the elements can be determined";
throw new IllegalArgumentException(msg);
}
int fieldAlignment = 1;
if (!Modifier.isPublic(field.getModifiers())) {
continue;
}
Object value = getField(structField);
if (value == null && type.isArray()) {
if (force) {
throw new IllegalStateException("Array fields must be initialized");
}
// can't calculate size yet, defer until later
return null;
}
Class nativeType = type;
if (NativeMapped.class.isAssignableFrom(type)) {
NativeMappedConverter tc = NativeMappedConverter.getInstance(type);
nativeType = tc.nativeType();
structField.writeConverter = tc;
structField.readConverter = tc;
structField.context = new StructureReadContext(this, field);
}
else if (typeMapper != null) {
ToNativeConverter writeConverter = typeMapper.getToNativeConverter(type);
FromNativeConverter readConverter = typeMapper.getFromNativeConverter(type);
if (writeConverter != null && readConverter != null) {
value = writeConverter.toNative(value,
new StructureWriteContext(this, structField.field));
nativeType = value != null ? value.getClass() : Pointer.class;
structField.writeConverter = writeConverter;
structField.readConverter = readConverter;
structField.context = new StructureReadContext(this, field);
}
else if (writeConverter != null || readConverter != null) {
String msg = "Structures require bidirectional type conversion for " + type;
throw new IllegalArgumentException(msg);
}
}
if (value == null) {
value = initializeField(structField, type);
}
try {
structField.size = getNativeSize(nativeType, value);
fieldAlignment = getNativeAlignment(nativeType, value, firstField);
}
catch(IllegalArgumentException e) {
// Might simply not yet have a type mapper set yet
if (!force && typeMapper == null) {
return null;
}
String msg = "Invalid Structure field in " + getClass() + ", field name '" + structField.name + "', " + structField.type + ": " + e.getMessage();
throw new IllegalArgumentException(msg);
}
// Align fields as appropriate
info.alignment = Math.max(info.alignment, fieldAlignment);
if ((calculatedSize % fieldAlignment) != 0) {
calculatedSize += fieldAlignment - (calculatedSize % fieldAlignment);
}
structField.offset = calculatedSize;
calculatedSize += structField.size;
// Save the field in our list
info.fields.put(structField.name, structField);
}
if (calculatedSize > 0) {
int size = calculateAlignedSize(calculatedSize, info.alignment);
// Update native FFI type information, if needed
if (this instanceof ByValue && !avoidFFIType) {
getTypeInfo();
}
if (this.memory != null
&& !(this.memory instanceof AutoAllocated)) {
// Ensure we've set bounds on the memory used
this.memory = this.memory.share(0, size);
}
info.size = size;
return info;
}
throw new IllegalArgumentException("Structure " + getClass()
+ " has unknown size (ensure "
+ "all fields are public)");
}
/** Initialize any null-valued fields that should have a non-null default
value. */
private void initializeFields() {
for (Iterator i=fields().values().iterator();i.hasNext();) {
StructField f = (StructField)i.next();
initializeField(f, f.type);
}
}
private Object initializeField(StructField structField, Class type) {
Object value = null;
if (Structure.class.isAssignableFrom(type)
&& !(ByReference.class.isAssignableFrom(type))) {
try {
value = newInstance(type);
setField(structField, value);
}
catch(IllegalArgumentException e) {
String msg = "Can't determine size of nested structure: "
+ e.getMessage();
throw new IllegalArgumentException(msg);
}
}
else if (NativeMapped.class.isAssignableFrom(type)) {
NativeMappedConverter tc = NativeMappedConverter.getInstance(type);
value = tc.defaultValue();
setField(structField, value);
}
return value;
}
int calculateAlignedSize(int calculatedSize) {
return calculateAlignedSize(calculatedSize, structAlignment);
}
private int calculateAlignedSize(int calculatedSize, int alignment) {
// Structure size must be an integral multiple of its alignment,
// add padding if necessary.
if (alignType != ALIGN_NONE) {
if ((calculatedSize % alignment) != 0) {
calculatedSize += alignment - (calculatedSize % alignment);
}
}
return calculatedSize;
}
protected int getStructAlignment() {
if (size == CALCULATE_SIZE) {
// calculate size, but don't allocate memory
calculateSize(true);
}
return structAlignment;
}
/** Overridable in subclasses. */
// TODO: write getNaturalAlignment(stack/alloc) + getEmbeddedAlignment(structs)
// TODO: move this into a native call which detects default alignment
// automatically
protected int getNativeAlignment(Class type, Object value, boolean isFirstElement) {
int alignment = 1;
if (NativeMapped.class.isAssignableFrom(type)) {
NativeMappedConverter tc = NativeMappedConverter.getInstance(type);
type = tc.nativeType();
value = tc.toNative(value, new ToNativeContext());
}
int size = Native.getNativeSize(type, value);
if (type.isPrimitive() || Long.class == type || Integer.class == type
|| Short.class == type || Character.class == type
|| Byte.class == type || Boolean.class == type
|| Float.class == type || Double.class == type) {
alignment = size;
}
else if (Pointer.class == type
|| (Platform.HAS_BUFFERS && Buffer.class.isAssignableFrom(type))
|| Callback.class.isAssignableFrom(type)
|| WString.class == type
|| String.class == type) {
alignment = Pointer.SIZE;
}
else if (Structure.class.isAssignableFrom(type)) {
if (ByReference.class.isAssignableFrom(type)) {
alignment = Pointer.SIZE;
}
else {
if (value == null)
value = newInstance(type);
alignment = ((Structure)value).getStructAlignment();
}
}
else if (type.isArray()) {
alignment = getNativeAlignment(type.getComponentType(), null, isFirstElement);
}
else {
throw new IllegalArgumentException("Type " + type + " has unknown "
+ "native alignment");
}
if (alignType == ALIGN_NONE) {
alignment = 1;
}
else if (alignType == ALIGN_MSVC) {
alignment = Math.min(8, alignment);
}
else if (alignType == ALIGN_GNUC) {
// NOTE this is published ABI for 32-bit gcc/linux/x86, osx/x86,
// and osx/ppc. osx/ppc special-cases the first element
if (!isFirstElement || !(Platform.isMac() && isPPC)) {
alignment = Math.min(MAX_GNUC_ALIGNMENT, alignment);
}
}
return alignment;
}
public String toString() {
return toString(Boolean.getBoolean("jna.dump_memory"));
}
public String toString(boolean debug) {
return toString(0, true, true);
}
private String format(Class type) {
String s = type.getName();
int dot = s.lastIndexOf(".");
return s.substring(dot + 1);
}
private String toString(int indent, boolean showContents, boolean dumpMemory) {
ensureAllocated();
String LS = System.getProperty("line.separator");
String name = format(getClass()) + "(" + getPointer() + ")";
if (!(getPointer() instanceof Memory)) {
name += " (" + size() + " bytes)";
}
String prefix = "";
for (int idx=0;idx < indent;idx++) {
prefix += " ";
}
String contents = LS;
if (!showContents) {
contents = "...}";
}
else for (Iterator i=fields().values().iterator();i.hasNext();) {
StructField sf = (StructField)i.next();
Object value = getField(sf);
String type = format(sf.type);
String index = "";
contents += prefix;
if (sf.type.isArray() && value != null) {
type = format(sf.type.getComponentType());
index = "[" + Array.getLength(value) + "]";
}
contents += " " + type + " "
+ sf.name + index + "@" + Integer.toHexString(sf.offset);
if (value instanceof Structure) {
value = ((Structure)value).toString(indent + 1, !(value instanceof Structure.ByReference), dumpMemory);
}
contents += "=";
if (value instanceof Long) {
contents += Long.toHexString(((Long)value).longValue());
}
else if (value instanceof Integer) {
contents += Integer.toHexString(((Integer)value).intValue());
}
else if (value instanceof Short) {
contents += Integer.toHexString(((Short)value).shortValue());
}
else if (value instanceof Byte) {
contents += Integer.toHexString(((Byte)value).byteValue());
}
else {
contents += String.valueOf(value).trim();
}
contents += LS;
if (!i.hasNext())
contents += prefix + "}";
}
if (indent == 0 && dumpMemory) {
final int BYTES_PER_ROW = 4;
contents += LS + "memory dump" + LS;
byte[] buf = getPointer().getByteArray(0, size());
for (int i=0;i < buf.length;i++) {
if ((i % BYTES_PER_ROW) == 0) contents += "[";
if (buf[i] >=0 && buf[i] < 16)
contents += "0";
contents += Integer.toHexString(buf[i] & 0xFF);
if ((i % BYTES_PER_ROW) == BYTES_PER_ROW-1 && i < buf.length-1)
contents += "]" + LS;
}
contents += "]";
}
return name + " {" + contents;
}
/** Returns a view of this structure's memory as an array of structures.
* Note that this Structure
must have a public, no-arg
* constructor. If the structure is currently using auto-allocated
* {@link Memory} backing, the memory will be resized to fit the entire
* array.
*/
public Structure[] toArray(Structure[] array) {
ensureAllocated();
if (this.memory instanceof AutoAllocated) {
// reallocate if necessary
Memory m = (Memory)this.memory;
int requiredSize = array.length * size();
if (m.size() < requiredSize) {
useMemory(autoAllocate(requiredSize));
}
}
array[0] = this;
int size = size();
for (int i=1;i < array.length;i++) {
array[i] = Structure.newInstance(getClass());
array[i].useMemory(memory.share(i*size, size));
array[i].read();
}
if (!(this instanceof ByValue)) {
// keep track for later auto-read/writes
this.array = array;
}
return array;
}
/** Returns a view of this structure's memory as an array of structures.
* Note that this Structure
must have a public, no-arg
* constructor. If the structure is currently using auto-allocated
* {@link Memory} backing, the memory will be resized to fit the entire
* array.
*/
public Structure[] toArray(int size) {
return toArray((Structure[])Array.newInstance(getClass(), size));
}
private Class baseClass() {
if ((this instanceof Structure.ByReference
|| this instanceof Structure.ByValue)
&& Structure.class.isAssignableFrom(getClass().getSuperclass())) {
return getClass().getSuperclass();
}
return getClass();
}
/** This structure is equal to another based on the same data type
* and memory contents.
*/
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Structure)) {
return false;
}
if (o.getClass() != getClass()
&& ((Structure)o).baseClass() != baseClass()) {
return false;
}
Structure s = (Structure)o;
if (s.getPointer().equals(getPointer())) {
return true;
}
if (s.size() == size()) {
clear(); write();
byte[] buf = getPointer().getByteArray(0, size());
s.clear(); s.write();
byte[] sbuf = s.getPointer().getByteArray(0, s.size());
return Arrays.equals(buf, sbuf);
}
return false;
}
/** Since {@link #equals} depends on the contents of memory, use that
* as the basis for the hash code.
*/
public int hashCode() {
clear(); write();
Adler32 code = new Adler32();
code.update(getPointer().getByteArray(0, size()));
return (int)code.getValue();
}
protected void cacheTypeInfo(Pointer p) {
typeInfo = p.peer;
}
/** Override to supply native type information for the given field. */
protected Pointer getFieldTypeInfo(StructField f) {
Class type = f.type;
Object value = getField(f);
if (typeMapper != null) {
ToNativeConverter nc = typeMapper.getToNativeConverter(type);
if (nc != null) {
type = nc.nativeType();
value = nc.toNative(value, new ToNativeContext());
}
}
return FFIType.get(value, type);
}
/** Obtain native type information for this structure. */
Pointer getTypeInfo() {
Pointer p = getTypeInfo(this);
cacheTypeInfo(p);
return p;
}
/** Set whether the structure is automatically synchronized to native memory
before and after a native function call. Convenience method for
boolean auto = ...;
setAutoRead(auto);
setAutoWrite(auto);
*/
public void setAutoSynch(boolean auto) {
setAutoRead(auto);
setAutoWrite(auto);
}
/** Set whether the structure is read from native memory prior to
a native function call.
*/
public void setAutoRead(boolean auto) {
this.autoRead = auto;
}
/** Returns whether the structure is read from native memory prior to
a native function call.
*/
public boolean getAutoRead() {
return this.autoRead;
}
/** Set whether the structure is written to native memory after a native
function call.
*/
public void setAutoWrite(boolean auto) {
this.autoWrite = auto;
}
/** Returns whether the structure is written to native memory after a native
function call.
*/
public boolean getAutoWrite() {
return this.autoWrite;
}
/** Exposed for testing purposes only. */
static Pointer getTypeInfo(Object obj) {
return FFIType.get(obj);
}
/** Create a new Structure instance of the given type
* @param type
* @return the new instance
* @throws IllegalArgumentException if the instantiation fails
*/
public static Structure newInstance(Class type) throws IllegalArgumentException {
try {
Structure s = (Structure)type.newInstance();
if (s instanceof ByValue) {
s.allocateMemory();
}
return s;
}
catch(InstantiationException e) {
String msg = "Can't instantiate " + type + " (" + e + ")";
throw new IllegalArgumentException(msg);
}
catch(IllegalAccessException e) {
String msg = "Instantiation of " + type
+ " not allowed, is it public? (" + e + ")";
throw new IllegalArgumentException(msg);
}
}
class StructField extends Object {
public String name;
public Class type;
public Field field;
public int size = -1;
public int offset = -1;
public boolean isVolatile;
public boolean isReadOnly;
public FromNativeConverter readConverter;
public ToNativeConverter writeConverter;
public FromNativeContext context;
public String toString() {
return name + "@" + offset + "[" + size + "] (" + type + ")";
}
}
/** This class auto-generates an ffi_type structure appropriate for a given
* structure for use by libffi. The lifecycle of this structure is easier
* to manage on the Java side than in native code.
*/
static class FFIType extends Structure {
public static class size_t extends IntegerType {
public size_t() { this(0); }
public size_t(long value) { super(Native.POINTER_SIZE, value); }
}
private static Map typeInfoMap = new WeakHashMap();
// Native.initIDs initializes these fields to their appropriate
// pointer values. These are in a separate class from FFIType so that
// they may be initialized prior to loading the FFIType class
private static class FFITypes {
private static Pointer ffi_type_void;
private static Pointer ffi_type_float;
private static Pointer ffi_type_double;
private static Pointer ffi_type_longdouble;
private static Pointer ffi_type_uint8;
private static Pointer ffi_type_sint8;
private static Pointer ffi_type_uint16;
private static Pointer ffi_type_sint16;
private static Pointer ffi_type_uint32;
private static Pointer ffi_type_sint32;
private static Pointer ffi_type_uint64;
private static Pointer ffi_type_sint64;
private static Pointer ffi_type_pointer;
}
static {
if (Native.POINTER_SIZE == 0)
throw new Error("Native library not initialized");
if (FFITypes.ffi_type_void == null)
throw new Error("FFI types not initialized");
typeInfoMap.put(void.class, FFITypes.ffi_type_void);
typeInfoMap.put(Void.class, FFITypes.ffi_type_void);
typeInfoMap.put(float.class, FFITypes.ffi_type_float);
typeInfoMap.put(Float.class, FFITypes.ffi_type_float);
typeInfoMap.put(double.class, FFITypes.ffi_type_double);
typeInfoMap.put(Double.class, FFITypes.ffi_type_double);
typeInfoMap.put(long.class, FFITypes.ffi_type_sint64);
typeInfoMap.put(Long.class, FFITypes.ffi_type_sint64);
typeInfoMap.put(int.class, FFITypes.ffi_type_sint32);
typeInfoMap.put(Integer.class, FFITypes.ffi_type_sint32);
typeInfoMap.put(short.class, FFITypes.ffi_type_sint16);
typeInfoMap.put(Short.class, FFITypes.ffi_type_sint16);
Pointer ctype = Native.WCHAR_SIZE == 2
? FFITypes.ffi_type_uint16 : FFITypes.ffi_type_uint32;
typeInfoMap.put(char.class, ctype);
typeInfoMap.put(Character.class, ctype);
typeInfoMap.put(byte.class, FFITypes.ffi_type_sint8);
typeInfoMap.put(Byte.class, FFITypes.ffi_type_sint8);
typeInfoMap.put(Pointer.class, FFITypes.ffi_type_pointer);
typeInfoMap.put(String.class, FFITypes.ffi_type_pointer);
typeInfoMap.put(WString.class, FFITypes.ffi_type_pointer);
typeInfoMap.put(boolean.class, FFITypes.ffi_type_uint32);
typeInfoMap.put(Boolean.class, FFITypes.ffi_type_uint32);
}
// From ffi.h
private static final int FFI_TYPE_STRUCT = 13;
// Structure fields
public size_t size;
public short alignment;
public short type = FFI_TYPE_STRUCT;
public Pointer elements;
private FFIType(Structure ref) {
Pointer[] els;
ref.ensureAllocated(true);
if (ref instanceof Union) {
StructField sf = ((Union)ref).biggestField;
els = new Pointer[] {
get(ref.getField(sf), sf.type), null,
};
}
else {
els = new Pointer[ref.fields().size() + 1];
int idx = 0;
for (Iterator i=ref.fields().values().iterator();i.hasNext();) {
StructField sf = (StructField)i.next();
els[idx++] = ref.getFieldTypeInfo(sf);
}
}
init(els);
}
// Represent fixed-size arrays as structures of N identical elements
private FFIType(Object array, Class type) {
int length = Array.getLength(array);
Pointer[] els = new Pointer[length+1];
Pointer p = get(null, type.getComponentType());
for (int i=0;i < length;i++) {
els[i] = p;
}
init(els);
}
private void init(Pointer[] els) {
elements = new Memory(Pointer.SIZE * els.length);
elements.write(0, els, 0, els.length);
write();
}
static Pointer get(Object obj) {
if (obj == null)
return FFITypes.ffi_type_pointer;
if (obj instanceof Class)
return get(null, (Class)obj);
return get(obj, obj.getClass());
}
private static Pointer get(Object obj, Class cls) {
TypeMapper mapper = Native.getTypeMapper(cls);
if (mapper != null) {
ToNativeConverter nc = mapper.getToNativeConverter(cls);
if (nc != null) {
cls = nc.nativeType();
}
}
synchronized(typeInfoMap) {
Object o = typeInfoMap.get(cls);
if (o instanceof Pointer) {
return (Pointer)o;
}
if (o instanceof FFIType) {
return ((FFIType)o).getPointer();
}
if ((Platform.HAS_BUFFERS && Buffer.class.isAssignableFrom(cls))
|| Callback.class.isAssignableFrom(cls)) {
typeInfoMap.put(cls, FFITypes.ffi_type_pointer);
return FFITypes.ffi_type_pointer;
}
if (Structure.class.isAssignableFrom(cls)) {
if (obj == null) obj = newInstance(cls);
if (ByReference.class.isAssignableFrom(cls)) {
typeInfoMap.put(cls, FFITypes.ffi_type_pointer);
return FFITypes.ffi_type_pointer;
}
FFIType type = new FFIType((Structure)obj);
typeInfoMap.put(cls, type);
return type.getPointer();
}
if (NativeMapped.class.isAssignableFrom(cls)) {
NativeMappedConverter c = NativeMappedConverter.getInstance(cls);
return get(c.toNative(obj, new ToNativeContext()), c.nativeType());
}
if (cls.isArray()) {
FFIType type = new FFIType(obj, cls);
// Store it in the map to prevent premature GC of type info
typeInfoMap.put(obj, type);
return type.getPointer();
}
throw new IllegalArgumentException("Unsupported Structure field type " + cls);
}
}
}
private class AutoAllocated extends Memory {
public AutoAllocated(int size) {
super(size);
// Always clear new structure memory
super.clear();
}
}
private static void structureArrayCheck(Structure[] ss) {
Pointer base = ss[0].getPointer();
int size = ss[0].size();
for (int si=1;si < ss.length;si++) {
if (ss[si].getPointer().peer != base.peer + size*si) {
String msg = "Structure array elements must use"
+ " contiguous memory (bad backing address at Structure array index " + si + ")";
throw new IllegalArgumentException(msg);
}
}
}
public static void autoRead(Structure[] ss) {
structureArrayCheck(ss);
if (ss[0].array == ss) {
ss[0].autoRead();
}
else {
for (int si=0;si < ss.length;si++) {
ss[si].autoRead();
}
}
}
public void autoRead() {
if (getAutoRead()) {
read();
if (array != null) {
for (int i=1;i < array.length;i++) {
array[i].autoRead();
}
}
}
}
public static void autoWrite(Structure[] ss) {
structureArrayCheck(ss);
if (ss[0].array == ss) {
ss[0].autoWrite();
}
else {
for (int si=0;si < ss.length;si++) {
ss[si].autoWrite();
}
}
}
public void autoWrite() {
if (getAutoWrite()) {
write();
if (array != null) {
for (int i=1;i < array.length;i++) {
array[i].autoWrite();
}
}
}
}
protected int getNativeSize(Class nativeType, Object value) {
return Native.getNativeSize(nativeType, value);
}
}