From b347cd9bb0a7b8823a13a967c9b35f40f691b533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Tue, 31 Jan 2023 21:31:04 +0100 Subject: [PATCH] Fix usage of Library.OPTION_STRING_ENCODING property in functions --- CHANGES.md | 2 +- native/testlib.c | 31 +++++++ src/com/sun/jna/Function.java | 2 +- test/com/sun/jna/FunctionTest.java | 140 +++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c35e5db576..f8a74a6fbb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,7 +10,7 @@ Features Bug Fixes --------- - +* [#1501](https://github.com/java-native-access/jna/pull/1501): `Library.OPTION_STRING_ENCODING` is ignore for string arguments function calls - [@matthiasblaesing](https://github.com/matthiasblaesing). Release (5.13.0) ================ diff --git a/native/testlib.c b/native/testlib.c index d438ffd750..7f1b4c7292 100644 --- a/native/testlib.c +++ b/native/testlib.c @@ -31,6 +31,8 @@ extern "C" { #include #include #include +#include + #if !defined(_WIN32_WCE) #include #endif @@ -1078,6 +1080,35 @@ returnLastElementOfComponentsDSDAL(DemoStructureDifferentArrayLengths ts, int de return result; } +/** + * Copy the input char array to the output char array. The caller is responsible + * to allocate a correctly sized buffer. + */ +EXPORT size_t copyString(char* input, char* output) { + size_t len = strlen(input) + 1; + memcpy(output, input, len); + return len; +} + +/** + * Copy the input array of char arrays to the output char array. The caller is + * responsible to allocate a correctly sized buffer. + */ +EXPORT size_t copyStringArray(char** input, char* output) { + size_t len = 0; + for(int i = 0;; i++) { + char* currInput = input[i]; + if(currInput == NULL) { + break; + } + size_t localLen = strlen(currInput) + 1; + memcpy(output, currInput, localLen); + output += localLen; + len += localLen; + } + return len; +} + #ifdef __cplusplus } #endif diff --git a/src/com/sun/jna/Function.java b/src/com/sun/jna/Function.java index a56cb711e0..4374f44b81 100644 --- a/src/com/sun/jna/Function.java +++ b/src/com/sun/jna/Function.java @@ -561,7 +561,7 @@ private Object convertArgument(Object[] args, int index, // than in native code so that the values will be valid until // this method returns. // Convert String to native pointer (const) - return new NativeString((String)arg, false).getPointer(); + return new NativeString((String)arg, encoding).getPointer(); } else if (arg instanceof WString) { // Convert WString to native pointer (const) return new NativeString(arg.toString(), true).getPointer(); diff --git a/test/com/sun/jna/FunctionTest.java b/test/com/sun/jna/FunctionTest.java index c63315d78b..fc3333de87 100644 --- a/test/com/sun/jna/FunctionTest.java +++ b/test/com/sun/jna/FunctionTest.java @@ -23,7 +23,11 @@ */ package com.sun.jna; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.Collections; import junit.framework.TestCase; +import org.junit.Assert; /** Exercise the {@link Function} class. * @@ -32,6 +36,29 @@ //@SuppressWarnings("unused") public class FunctionTest extends TestCase { + private NativeLibrary libUTF8; + private NativeLibrary libLatin1; + private TestLibUTF8 libUTF8Direct; + private TestLibLatin1 libLatin1Direct; + private TestLib libUTF8Interface; + private TestLib libLatin1Interface; + + @Override + protected void setUp() { + libUTF8 = NativeLibrary.getInstance("testlib", + Collections.singletonMap(Library.OPTION_STRING_ENCODING, "UTF-8")); + libLatin1 = NativeLibrary.getInstance("testlib", + Collections.singletonMap(Library.OPTION_STRING_ENCODING, "ISO-8859-1")); + Native.register(TestLibUTF8.class, libUTF8); + Native.register(TestLibLatin1.class, libLatin1); + libUTF8Direct = new TestLibUTF8(); + libLatin1Direct = new TestLibLatin1(); + libUTF8Interface = Native.load("testlib", TestLib.class, + Collections.singletonMap(Library.OPTION_STRING_ENCODING, "UTF-8")); + libLatin1Interface = Native.load("testlib", TestLib.class, + Collections.singletonMap(Library.OPTION_STRING_ENCODING, "ISO-8859-1")); + } + public void testTooManyArgs() { NativeLibrary lib = NativeLibrary.getInstance(Platform.C_LIBRARY_NAME); Function f = lib.getFunction("printf"); @@ -58,8 +85,121 @@ public void testUnsupportedReturnType() { } } + public void testStringEncodingArgument() throws UnsupportedEncodingException { + // String with german umlauts + String input = "Hallo äöüß"; + byte[] result = new byte[32]; + Arrays.fill(result, (byte) 0); + libUTF8Interface.copyString(input, result); + Assert.assertArrayEquals(toByteArray(input, "UTF-8", 32), result); + Arrays.fill(result, (byte) 0); + libLatin1Interface.copyString(input, result); + Assert.assertArrayEquals(toByteArray(input, "ISO-8859-1", 32), result); + + // String array with german umlauts + String[] inputArray = new String[]{"1Hallo äöüß1", "2Hallo äöüß2"}; + result = new byte[64]; + Arrays.fill(result, (byte) 0); + libUTF8Interface.copyStringArray(inputArray, result); + Assert.assertArrayEquals(toByteArray(inputArray, "UTF-8", 64), result); + Arrays.fill(result, (byte) 0); + libLatin1Interface.copyStringArray(inputArray, result); + Assert.assertArrayEquals(toByteArray(inputArray, "ISO-8859-1", 64), result); + } + + public void testStringEncodingArgumentDirect() throws UnsupportedEncodingException { + // String with german umlauts + String input = "Hallo äöüß"; + byte[] result = new byte[32]; + Arrays.fill(result, (byte) 0); + libUTF8Direct.copyString(input, result); + Assert.assertArrayEquals(toByteArray(input, "UTF-8", 32), result); + Arrays.fill(result, (byte) 0); + libLatin1Direct.copyString(input, result); + Assert.assertArrayEquals(toByteArray(input, "ISO-8859-1", 32), result); + } + + public void testStringReturn() throws UnsupportedEncodingException { + // String with german umlauts + String input = "Hallo äöüß"; + + String result; + Memory mem = new Memory(32); + mem.clear(); + mem.write(0, input.getBytes("UTF-8"), 0, input.getBytes("UTF-8").length); + result = libUTF8Interface.returnStringArgument(mem); + assertEquals(input, result); + mem.clear(); + mem.write(0, input.getBytes("ISO-8859-1"), 0, input.getBytes("ISO-8859-1").length); + result = libLatin1Interface.returnStringArgument(mem); + assertEquals(input, result); + } + + public void testStringReturnDirect() throws UnsupportedEncodingException { + // String with german umlauts + String input = "Hallo äöüß"; + + String result; + Memory mem = new Memory(32); + mem.clear(); + mem.write(0, input.getBytes("UTF-8"), 0, input.getBytes("UTF-8").length); + result = libUTF8Direct.returnStringArgument(mem); + assertEquals(input, result); + mem.clear(); + mem.write(0, input.getBytes("ISO-8859-1"), 0, input.getBytes("ISO-8859-1").length); + result = libLatin1Direct.returnStringArgument(mem); + assertEquals(input, result); + } + + private byte[] toByteArray(String input, String encoding, int targetLength) throws UnsupportedEncodingException { + byte[] result = new byte[targetLength]; + byte[] encoded = input.getBytes(encoding); + System.arraycopy(encoded, 0, result, 0, encoded.length); + return result; + } + + private byte[] toByteArray(String[] input, String encoding, int targetLength) throws UnsupportedEncodingException { + byte[] result = new byte[targetLength]; + int offset = 0; + for(String currInput: input) { + byte[] encoded = currInput.getBytes(encoding); + System.arraycopy(encoded, 0, result, offset, encoded.length); + offset += encoded.length; + offset++; + } + return result; + } + public static void main(java.lang.String[] argList) { junit.textui.TestRunner.run(FunctionTest.class); } + private static class TestLibUTF8 implements Library { + native String returnStringArgument(Pointer input); + native SizeT copyString(String input, byte[] output); + } + + private static class TestLibLatin1 implements Library { + native String returnStringArgument(Pointer input); + native SizeT copyString(String input, byte[] output); + } + + private interface TestLib extends Library { + public String returnStringArgument(Pointer input); + public SizeT copyString(String input, byte[] output); + public SizeT copyStringArray(String[] input, byte[] output); + } + + private static class SizeT extends IntegerType { + public static final int SIZE = Native.SIZE_T_SIZE; + + public SizeT() { + this(0); + } + + public SizeT(long value) { + super(SIZE, value, true); + } + + } }