diff --git a/jni/jffi/MemoryIO.c b/jni/jffi/MemoryIO.c
index 29c78e80..6ebfca28 100644
--- a/jni/jffi/MemoryIO.c
+++ b/jni/jffi/MemoryIO.c
@@ -144,6 +144,29 @@ getArrayChecked(JNIEnv* env, jlong address, jobject obj, jint offset, jint lengt
},);
}
+static jsize
+UTFStrLen(const char* str, jsize maxlen, jint nullTerminatorWidth)
+{
+ jsize matchingBytesCount = 0;
+ const char* char_ptr = str;
+ const char* end_char_ptr = str + maxlen;
+ while (char_ptr < end_char_ptr) {
+ if (*char_ptr == '\0') {
+ matchingBytesCount++;
+ char_ptr++;
+ } else {
+ char_ptr += nullTerminatorWidth - matchingBytesCount;//jump to start of next character
+ matchingBytesCount = 0;
+ continue;
+ }
+ if (matchingBytesCount == nullTerminatorWidth) {
+ char_ptr -= nullTerminatorWidth;//trim to the last byte just before null terminator
+ return char_ptr - str;
+ }
+ }
+ return maxlen;
+}
+
#define UNSAFE(J, N) GET(J, N) PUT(J, N) COPY(J, N)
UNSAFE(Byte, jbyte);
@@ -347,6 +370,23 @@ Java_com_kenai_jffi_Foreign_getZeroTerminatedByteArray__JI(JNIEnv* env, jobject
return bytes;
}
+/*
+ * Class: com_kenai_jffi_Foreign
+ * Method: getZeroTerminatedByteArray
+ * Signature: (JII)[B
+ */
+JNIEXPORT jbyteArray JNICALL
+Java_com_kenai_jffi_Foreign_getZeroTerminatedByteArray__JII(JNIEnv* env, jobject self, jlong address, jint maxlen, jint nullTerminatorWidth)
+{
+ const char *str = (const char*) j2p(address);
+ jsize len = UTFStrLen(str, maxlen, nullTerminatorWidth);
+ jbyteArray bytes = (*env)->NewByteArray(env, len);
+ (*env)->SetByteArrayRegion(env, bytes, 0, len, (jbyte *) str);
+
+ return bytes;
+}
+
+
JNIEXPORT jbyteArray JNICALL
Java_com_kenai_jffi_Foreign_getZeroTerminatedByteArrayChecked__JI(JNIEnv* env, jobject self, jlong address, jint maxlen)
{
@@ -361,6 +401,24 @@ Java_com_kenai_jffi_Foreign_getZeroTerminatedByteArrayChecked__JI(JNIEnv* env, j
return bytes;
}
+/*
+ * Class: com_kenai_jffi_Foreign
+ * Method: getZeroTerminatedByteArrayChecked
+ * Signature: (JII)[B
+ */
+JNIEXPORT jbyteArray JNICALL
+Java_com_kenai_jffi_Foreign_getZeroTerminatedByteArrayChecked__JII(JNIEnv* env, jobject self, jlong address, jint maxlen, jint nullTerminatorWidth)
+{
+ const char *str = (const char*) j2p(address);
+ jsize len;
+
+ PROT(len = UTFStrLen(str, maxlen, nullTerminatorWidth), NULL);
+ jbyteArray bytes = (*env)->NewByteArray(env, len);
+ (*env)->SetByteArrayRegion(env, bytes, 0, len, (jbyte *) str);
+
+ return bytes;
+}
+
/*
* Class: com_kenai_jffi_Foreign
* Method: putZeroTerminatedByteArray
@@ -374,6 +432,24 @@ Java_com_kenai_jffi_Foreign_putZeroTerminatedByteArray(JNIEnv *env, jobject self
*((char *) (uintptr_t) address + length) = '\0';
}
+/*
+ * Class: com_kenai_jffi_Foreign
+ * Method: putZeroTerminatedByteArray
+ * Signature: (J[BIII)V
+ */
+JNIEXPORT void JNICALL
+Java_com_kenai_jffi_Foreign_putZeroTerminatedByteArray__J_3BIII(JNIEnv *env, jobject self,
+ jlong address, jbyteArray data, jint offset, jint length, jint nullTerminatorWidth)
+{
+ (*env)->GetByteArrayRegion(env, data, offset, length, (jbyte *)j2p(address));
+ char *str = (char*) j2p(address);
+ jint i;
+ for(i = 0; i < nullTerminatorWidth; i++){
+ str[address + length + i] = '\0';
+ }
+}
+
+
JNIEXPORT void JNICALL
Java_com_kenai_jffi_Foreign_putZeroTerminatedByteArrayChecked(JNIEnv *env, jobject self,
jlong address, jbyteArray data, jint offset, jint length)
@@ -383,6 +459,22 @@ Java_com_kenai_jffi_Foreign_putZeroTerminatedByteArrayChecked(JNIEnv *env, jobje
(*env)->GetByteArrayRegion(env, data, offset, length, (jbyte *)j2p(address));
}
+/*
+ * Class: com_kenai_jffi_Foreign
+ * Method: putZeroTerminatedByteArrayChecked
+ * Signature: (J[BIII)V
+ */
+JNIEXPORT void JNICALL
+Java_com_kenai_jffi_Foreign_putZeroTerminatedByteArrayChecked__J_3BIII(JNIEnv *env, jobject self,
+ jlong address, jbyteArray data, jint offset, jint length, jint nullTerminatorWidth)
+{
+ char* cp = (char *) (uintptr_t) address;
+ jint i;
+ PROT({ *cp = 0; for(i = 0; i < nullTerminatorWidth; i++) cp[address + length + i] = '\0';},);
+ (*env)->GetByteArrayRegion(env, data, offset, length, (jbyte *)j2p(address));
+}
+
+
/*
* Class: com_kenai_jffi_Foreign
* Method: allocateMemory
diff --git a/src/main/java/com/kenai/jffi/Foreign.java b/src/main/java/com/kenai/jffi/Foreign.java
index 6706aacb..20110f78 100644
--- a/src/main/java/com/kenai/jffi/Foreign.java
+++ b/src/main/java/com/kenai/jffi/Foreign.java
@@ -1425,6 +1425,20 @@ static native void invokePointerParameterArray(long callContext, long functionCo
*/
static native byte[] getZeroTerminatedByteArray(long address, int maxlen);
+ /**
+ * Copies a zero (nul) terminated by array from native memory.
+ *
+ * This method will search for a varying size null terminator, starting from address
+ * and stop once the terminator is encountered. The returned byte array does not
+ * contain the terminating zero bytes.
+ *
+ * @param address The address to copy the array from
+ * @param maxlen The maximum number of bytes to search for the nul terminator
+ * @param nullTerminatorWidth size of null terminator in bytes
+ * @return A byte array containing the bytes copied from native memory.
+ */
+ static native byte[] getZeroTerminatedByteArray(long address, int maxlen, int nullTerminatorWidth);
+
/**
* Copies a java byte array to native memory and appends a NUL terminating byte.
*
@@ -1436,6 +1450,19 @@ static native void invokePointerParameterArray(long callContext, long functionCo
* @param length The number of bytes to copy to native memory
*/
static native void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length);
+
+ /**
+ * Copies a java byte array to native memory and appends a varying width null terminator.
+ *
+ * Note A total of length + nullTerminatorWidth bytes is written to native memory.
+ *
+ * @param address The address to copy to.
+ * @param data The byte array to copy to native memory
+ * @param offset The offset within the byte array to begin copying from
+ * @param length The number of bytes to copy to native memory
+ * @param nullTerminatorWidth size of null terminator in bytes
+ */
+ static native void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length, int nullTerminatorWidth);
/**
* Reads an 8 bit integer from a native memory location.
@@ -1757,7 +1784,7 @@ static native void invokePointerParameterArray(long callContext, long functionCo
static native byte[] getZeroTerminatedByteArrayChecked(long address);
/**
- * Copies a zero Checked(nul) terminated by array from native memory.
+ * Copies a zero Checked(nul) terminated byte array from native memory.
*
* This method will search for a zero byte, starting from address
* and stop once a zero byte is encountered. The returned byte array does not
@@ -1769,6 +1796,20 @@ static native void invokePointerParameterArray(long callContext, long functionCo
*/
static native byte[] getZeroTerminatedByteArrayChecked(long address, int maxlen);
+ /**
+ * Copies a zero Checked(nul) terminated byte array from native memory.
+ *
+ * This method will search for a varying length null terminator, starting from address
+ * and stop once the terminator is encountered. The returned byte array does not
+ * contain the terminating zero bytes.
+ *
+ * @param address The address to copy the array from
+ * @param maxlen The maximum number of bytes to search for the nul terminator
+ * @param nullTerminatorWidth size of null terminator in bytes
+ * @return A byte array containing the bytes copied from native memory.
+ */
+ static native byte[] getZeroTerminatedByteArrayChecked(long address, int maxlen, int nullTerminatorWidth);
+
/**
* Copies a java byte array to native memory and appends a NUL terminating byte.
*
@@ -1781,6 +1822,19 @@ static native void invokePointerParameterArray(long callContext, long functionCo
*/
static native void putZeroTerminatedByteArrayChecked(long address, byte[] data, int offset, int length);
+ /**
+ * Copies a java byte array to native memory and appends a varying width null terminator.
+ *
+ * Note A total of length + nullTerminatorWidth bytes is written to native memory.
+ *
+ * @param address The address to copy to.
+ * @param data The byte array to copy to native memory
+ * @param offset The offset within the byte array to begin copying from
+ * @param length The number of bytes to copy to native memory
+ * @param nullTerminatorWidth size of null terminator in bytes
+ */
+ static native void putZeroTerminatedByteArrayChecked(long address, byte[] data, int offset, int length, int nullTerminatorWidth);
+
/**
* Creates a new Direct ByteBuffer for a native memory region.
*
diff --git a/src/main/java/com/kenai/jffi/MemoryIO.java b/src/main/java/com/kenai/jffi/MemoryIO.java
index ddab5e87..729f40df 100644
--- a/src/main/java/com/kenai/jffi/MemoryIO.java
+++ b/src/main/java/com/kenai/jffi/MemoryIO.java
@@ -522,6 +522,20 @@ public final void freeMemory(long address) {
*/
public abstract byte[] getZeroTerminatedByteArray(long address, int maxlen);
+ /**
+ * Reads a byte array from native memory, stopping when a null terminator is found,
+ * or the maximum length is reached.
+ *
+ * This can be used to read non single byte terminator strings from native memory.
+ *
+ * @param address The address to read the data from.
+ * @param maxlen The limit of the memory area to scan for null terminator.
+ * @param nullTerminatorWidth size of null terminator in bytes
+ * @return The byte array containing a copy of the native data. Any zero
+ * byte is stripped from the end.
+ */
+ public abstract byte[] getZeroTerminatedByteArray(long address, int maxlen, int nullTerminatorWidth);
+
@Deprecated
public final byte[] getZeroTerminatedByteArray(long address, long maxlen) {
return getZeroTerminatedByteArray(address, (int) maxlen);
@@ -539,6 +553,19 @@ public final byte[] getZeroTerminatedByteArray(long address, long maxlen) {
*/
public abstract void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length);
+ /**
+ * Copies a java byte array to native memory and appends a varying width NUL terminator.
+ *
+ * Note A total of length + 1 bytes is written to native memory.
+ *
+ * @param address The address to copy to.
+ * @param data The byte array to copy to native memory
+ * @param offset The offset within the byte array to begin copying from
+ * @param length The number of bytes to copy to native memory
+ * @param nullTerminatorWidth size of null terminator in bytes
+ */
+ public abstract void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length, int nullTerminatorWidth);
+
/**
* Finds the location of a byte value in a native memory region.
*
@@ -693,9 +720,15 @@ public final byte[] getZeroTerminatedByteArray(long address) {
public final byte[] getZeroTerminatedByteArray(long address, int maxlen) {
return Foreign.getZeroTerminatedByteArray(address, maxlen);
}
+ public final byte[] getZeroTerminatedByteArray(long address, int maxlen, int nullTerminatorWidth) {
+ return Foreign.getZeroTerminatedByteArray(address, maxlen, nullTerminatorWidth);
+ }
public final void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length) {
Foreign.putZeroTerminatedByteArray(address, data, offset, length);
}
+ public final void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length, int nullTerminatorWidth) {
+ Foreign.putZeroTerminatedByteArray(address, data, offset, length, nullTerminatorWidth);
+ }
}
@@ -822,9 +855,15 @@ public final byte[] getZeroTerminatedByteArray(long address) {
public final byte[] getZeroTerminatedByteArray(long address, int maxlen) {
return Foreign.getZeroTerminatedByteArrayChecked(address, maxlen);
}
+ public final byte[] getZeroTerminatedByteArray(long address, int maxlen, int nullTerminatorWidth) {
+ return Foreign.getZeroTerminatedByteArrayChecked(address, maxlen, nullTerminatorWidth);
+ }
public final void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length) {
Foreign.putZeroTerminatedByteArrayChecked(address, data, offset, length);
}
+ public final void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length, int nullTerminatorWidth) {
+ Foreign.putZeroTerminatedByteArrayChecked(address, data, offset, length, nullTerminatorWidth);
+ }
}
/**
diff --git a/src/main/java/com/kenai/jffi/UnsafeMemoryIO.java b/src/main/java/com/kenai/jffi/UnsafeMemoryIO.java
index 7b23d039..52be7f49 100644
--- a/src/main/java/com/kenai/jffi/UnsafeMemoryIO.java
+++ b/src/main/java/com/kenai/jffi/UnsafeMemoryIO.java
@@ -120,9 +120,15 @@ public final byte[] getZeroTerminatedByteArray(long address) {
public final byte[] getZeroTerminatedByteArray(long address, int maxlen) {
return Foreign.getZeroTerminatedByteArray(address, maxlen);
}
+ public final byte[] getZeroTerminatedByteArray(long address, int maxlen, int nullTerminatorWidth) {
+ return Foreign.getZeroTerminatedByteArray(address, maxlen, nullTerminatorWidth);
+ }
public final void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length) {
Foreign.putZeroTerminatedByteArray(address, data, offset, length);
}
+ public final void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length, int nullTerminatorWidth) {
+ Foreign.putZeroTerminatedByteArray(address, data, offset, length, nullTerminatorWidth);
+ }
/**
* A 32 bit optimized implementation of MemoryIO using sun.misc.Unsafe
diff --git a/src/test/java/com/kenai/jffi/MemoryTest.java b/src/test/java/com/kenai/jffi/MemoryTest.java
index 93f78c0e..b0fe4f43 100644
--- a/src/test/java/com/kenai/jffi/MemoryTest.java
+++ b/src/test/java/com/kenai/jffi/MemoryTest.java
@@ -7,6 +7,7 @@
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
+import java.nio.charset.StandardCharsets;
public class MemoryTest {
@@ -54,7 +55,14 @@ public void tearDown() {
byte[] string = MemoryIO.getInstance().getZeroTerminatedByteArray(memory, 4);
assertArrayEquals(MAGIC, string);
}
-
+ @Test public void zeroTerminatedArrayWithVaryingTerminatorWidth() {
+ byte[] MAGIC = new String("goodbye").getBytes(StandardCharsets.UTF_16LE);
+ byte[] nullTerminator = new String("\0").getBytes(StandardCharsets.UTF_16LE);
+ long memory = MemoryIO.getInstance().allocateMemory(MAGIC.length + nullTerminator.length, true);
+ MemoryIO.getInstance().putZeroTerminatedByteArray(memory, MAGIC, 0, MAGIC.length, nullTerminator.length);
+ assertArrayEquals("String not written to native memory", MAGIC,
+ MemoryIO.getInstance().getZeroTerminatedByteArray(memory, MAGIC.length + nullTerminator.length, nullTerminator.length));
+ }
@Test public void putZeroTerminatedByteArray() {
final byte[] DIRTY = { 'd', 'i', 'r', 't', 'y' };
final byte[] MAGIC = { 't', 'e', 's', 't' };