diff --git a/src/main/java/jnr/ffi/Struct.java b/src/main/java/jnr/ffi/Struct.java index 9cbd012d..96c24182 100755 --- a/src/main/java/jnr/ffi/Struct.java +++ b/src/main/java/jnr/ffi/Struct.java @@ -30,10 +30,12 @@ import jnr.ffi.provider.ParameterFlags; import jnr.ffi.provider.jffi.ArrayMemoryIO; import jnr.ffi.util.EnumMapper; +import jnr.ffi.Platform; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; /** * Representation of C structures in java. @@ -2291,7 +2293,7 @@ public Enum(Class enumClass) { abstract public class String extends AbstractMember { protected final Charset charset; - protected final int length; + protected int length; protected String(int size, int align, int length, Charset cs) { super(size, align); @@ -2318,8 +2320,8 @@ public final java.lang.String toString() { } public class UTFString extends String { - public UTFString(int length, Charset cs) { - super(length * 8, 8, length, cs); // FIXME: This won't work for non-ASCII + public UTFString(int lengthInBytes, Charset cs) { + super(lengthInBytes * 8, 8, lengthInBytes, cs); // FIXME: This won't work for non-ASCII } protected jnr.ffi.Pointer getStringMemory() { @@ -2347,6 +2349,12 @@ public AsciiString(int size) { } } + public class WString extends UTFString { + public WString(int sizeInChars) { + super(sizeInChars * getWideCharWidthInBytes(), getCharset()); + } + } + public class UTFStringRef extends String { private jnr.ffi.Pointer valueHolder; @@ -2369,16 +2377,29 @@ public final java.lang.String get() { } public final void set(java.lang.String value) { - if (value != null) { - valueHolder = getRuntime().getMemoryManager().allocateDirect(length() * 4); - valueHolder.putString(0, value, length() * 4, charset); - getMemory().putPointer(offset(), valueHolder); - - } else { + if(value == null) { this.valueHolder = null; getMemory().putAddress(offset(), 0); + return; } + + value += "\0"; + byte[] bytes = value.getBytes(charset); + if(bytes.length > length || valueHolder == null) { + valueHolder = getRuntime().getMemoryManager().allocateDirect(bytes.length); + length = bytes.length; + getMemory().putPointer(offset(), valueHolder); + } + valueHolder.put(0, bytes, 0, bytes.length); } + + public void reAllocate(int sizeBytes) { + valueHolder = getRuntime().getMemoryManager().allocateDirect(sizeBytes); + length = sizeBytes; + getMemory().putPointer(offset(), valueHolder); + } + + //TODO: implement bool isNull() } public class UTF8StringRef extends UTFStringRef { @@ -2399,6 +2420,37 @@ public AsciiStringRef() { } } + public class WStringRef extends UTFStringRef { + public WStringRef(int sizeInChars) { + super(sizeInChars * getWideCharWidthInBytes(), getCharset()); + } + + public WStringRef() { + super(Integer.MAX_VALUE, getCharset()); + } + + public final void setMaxLength(int sizeInChars) { + length = sizeInChars * getWideCharWidthInBytes(); + } + + @Override + public void reAllocate(int sizeInChars){ + super.reAllocate(sizeInChars * getWideCharWidthInBytes()); + } + } + + private static Charset getCharset() { + if(Platform.getPlatform().getOS() == Platform.OS.WINDOWS) { + return StandardCharsets.UTF_16LE; + } else { + return Charset.forName("UTF-32LE");//unless -fshort-wchar is used for compiling native libs + } + } + + private static int getWideCharWidthInBytes() { + return Platform.getPlatform().getOS() == Platform.OS.WINDOWS ? 2 : 4; + } + /** * Specialized padding fields for structs. Use this instead of arrays of other * members for more efficient struct construction. diff --git a/src/main/java/jnr/ffi/provider/converters/StringUtil.java b/src/main/java/jnr/ffi/provider/converters/StringUtil.java index 0d5bc704..8025bc4b 100644 --- a/src/main/java/jnr/ffi/provider/converters/StringUtil.java +++ b/src/main/java/jnr/ffi/provider/converters/StringUtil.java @@ -35,7 +35,7 @@ import java.util.Arrays; import java.util.Collection; -final class StringUtil { +final public class StringUtil { private StringUtil() {} static CharsetEncoder getEncoder(Charset charset, ThreadLocal> localEncoder) { @@ -121,7 +121,7 @@ static void throwException(CoderResult result) { private static final Charset UTF16LE = Charset.forName("UTF-16LE"); private static final Charset UTF16BE = Charset.forName("UTF-16BE"); - static int terminatorWidth(Charset charset) { + public static int terminatorWidth(Charset charset) { if (charset.equals(UTF8) || charset.equals(USASCII) || charset.equals(ISO8859_1)) { return 1; diff --git a/src/main/java/jnr/ffi/provider/jffi/DirectMemoryIO.java b/src/main/java/jnr/ffi/provider/jffi/DirectMemoryIO.java index 1574b0ba..31a2453b 100644 --- a/src/main/java/jnr/ffi/provider/jffi/DirectMemoryIO.java +++ b/src/main/java/jnr/ffi/provider/jffi/DirectMemoryIO.java @@ -22,6 +22,7 @@ import jnr.ffi.Runtime; import jnr.ffi.provider.AbstractMemoryIO; import jnr.ffi.provider.DelegatingMemoryIO; +import jnr.ffi.provider.converters.StringUtil; import java.nio.ByteBuffer; import java.nio.charset.Charset; @@ -186,8 +187,42 @@ public String getString(long offset) { public String getString(long offset, int maxLength, Charset cs) { - final byte[] bytes = IO.getZeroTerminatedByteArray(address() + offset, maxLength); - return cs.decode(ByteBuffer.wrap(bytes)).toString(); + long baseAddress = address() + offset; + int nullTermSize = StringUtil.terminatorWidth(cs); + if(nullTermSize == 1) { + final byte[] bytes = IO.getZeroTerminatedByteArray(baseAddress, maxLength); + return cs.decode(ByteBuffer.wrap(bytes)).toString(); + } else { + if(address() == 0) { + return null; + } + + int nullTerminatedLen = 0; + int matchingBytesCount = 0; + int i = 0; + while(i < maxLength) { + if(IO.getByte(baseAddress+i) == 0) { + matchingBytesCount++; + i++; + } else { + matchingBytesCount = 0; + i += nullTermSize - (i%nullTermSize);//jump to start of next character + continue; + } + if(matchingBytesCount == nullTermSize) { + nullTerminatedLen = i-nullTermSize;//trim to the last byte just before null terminator + break; + } + } + + if(nullTerminatedLen == 0) { + return ""; + } + + byte[] bytes = new byte[nullTerminatedLen]; + IO.getByteArray(baseAddress, bytes, 0, nullTerminatedLen); + return new String(bytes, 0, bytes.length, cs); + } } public void putString(long offset, String string, int maxLength, Charset cs) { diff --git a/src/main/java/jnr/ffi/util/BufferUtil.java b/src/main/java/jnr/ffi/util/BufferUtil.java index 6165a65e..82444e70 100644 --- a/src/main/java/jnr/ffi/util/BufferUtil.java +++ b/src/main/java/jnr/ffi/util/BufferUtil.java @@ -26,6 +26,8 @@ import java.nio.charset.CharsetEncoder; import java.nio.charset.CodingErrorAction; +import jnr.ffi.provider.converters.StringUtil; + /** * */ @@ -64,7 +66,8 @@ public static CharSequence getCharSequence(ByteBuffer buf, Charset charset) { final ByteBuffer buffer = buf.slice(); // Find the NUL terminator and limit to that, so the // StringBuffer/StringBuilder does not have superfluous NUL chars - int end = indexOf(buffer, (byte) 0); + final byte[] nullCharBytes = new byte[StringUtil.terminatorWidth(charset)]; + int end = indexOf(buffer, nullCharBytes) - nullCharBytes.length; if (end < 0) { end = buffer.limit(); } @@ -141,6 +144,47 @@ public static int indexOf(ByteBuffer buf, byte value) { return -1; } + public static int indexOf(ByteBuffer buf, byte[] value) { + int matchingBytesCount = 0; + int offset = 0; + if (buf.hasArray()) { + byte[] array = buf.array(); + int begin = buf.arrayOffset() + buf.position(); + int end = buf.arrayOffset() + buf.limit(); + while(offset < end && offset > -1) { + if(array[begin + offset] == value[matchingBytesCount]) { + matchingBytesCount++; + offset++; + } else { + matchingBytesCount = 0; + offset += value.length - (offset%value.length);//jump to start of next character + continue; + } + + if(matchingBytesCount == value.length) { + return offset; + } + } + } else { + int begin = buf.position(); + while(offset < buf.limit()) { + if(buf.get(begin + offset) == value[matchingBytesCount]) { + matchingBytesCount++; + offset++; + } else { + matchingBytesCount = 0; + offset += value.length - (offset%value.length);//jump to start of next character + continue; + } + + if(matchingBytesCount == value.length) { + return offset; + } + } + } + return -1; + } + public static int indexOf(ByteBuffer buf, int offset, byte value) { if (buf.hasArray()) { byte[] array = buf.array();