JNA: изменить кодировку String только для одной внешней собственной библиотеки - PullRequest
1 голос
/ 13 июля 2020

У нас есть приложение JAVA, которое загружает и использует множество внешних библиотек. Кодировка по умолчанию операционной системы (Windows) - «windows -1252» (или «cp-1252»). Но есть одна внешняя библиотека, которая хочет, чтобы все строки (входящие и исходящие) были в «utf-8». Как я могу это сделать? Как изменить тип кодировки String только для одной библиотеки JNA?

Ответы [ 3 ]

2 голосов
/ 13 июля 2020

Обычный шаблон JNA следующий:

public interface DemoLibrary extends Library {

    DemoLibrary INSTANCE = Native.load("demoLibrary", DemoLibrary.class);

    // abstract method declarations as interface to native library
}

Однако Native # load перегружается несколько раз для поддержки настройки привязок. Соответствующая перегрузка: Native # load (String, Class, Map ) . Третий аргумент может использоваться для передачи параметров загрузчику собственной библиотеки. Параметры можно найти в com.sun.jna.Library Интерфейс.

Соответствующий параметр здесь - Library.OPTION_STRING_ENCODING. Эта опция передается в загруженный экземпляр NativeLibrary и будет использоваться в качестве кодировки по умолчанию для этого класса.

Пример выше становится тогда

public interface DemoLibrary extends Library {

    DemoLibrary INSTANCE = Native.load("demoLibrary", DemoLibrary.class,
        Collections.singletonMap(Library.OPTION_STRING_ENCODING, "UTF-8"));

}

Если вам нужно настроить больше ( typemapper, соглашение о вызовах) вам нужно будет создать карту параметров, например, в блоке инициализатора stati c.

1 голос
/ 13 июля 2020

Ответ Маттиаса Бласинга - гораздо лучшее решение для этого конкретного c варианта использования. Пожалуйста, сначала прочтите его, если вам нужна только кодировка символов.

Мой исходный ответ приведен ниже и является более общим для более широкого круга приложений.

Простой способ справиться с этим - не отображать напрямую String полей / аргументов вообще. Просто отправьте и получите байтовые массивы из библиотеки и создайте вспомогательную функцию для преобразования между строками и байтовыми массивами. Как вы отметили, вы можете записать эти байты в выделенный блок Memory и передать указатель.

Если вам нужно более постоянное решение, делающее то же самое за кулисами, вы можете использовать TypeMapper для этой конкретной библиотеки.

W32APITypeMapper - хороший справочник, с переменной stringConverter, показывающей, как в юникоде она отображает String на широкую строку WString (UTF16 ).

Создайте свой собственный UTF8TypeMapper (или аналогичный) и используйте функции набора символов / кодирования Java для преобразования ваших строк в последовательность байтов UTF-8.

Это не проверено, но должно быть близко к тому, что вам нужно. Вы можете сделать немного больше абстракции, чтобы создать новый тип UTF8String, который обрабатывает детали.

public class UTF8TypeMapper extends DefaultTypeMapper {

    public UTF8TypeMapper() {
        TypeConverter stringConverter = new TypeConverter() {
            @Override
            public Object toNative(Object value, ToNativeContext context) {
                if (value == null)
                    return null;

                String str = (String) value;
                byte[] bytes = str.getBytes(StandardCharsets.UTF_8);

                // Allocate an extra byte for null terminator
                Memory m = new Memory(bytes.length + 1);
                // write the string's bytes
                m.write(0, bytes, 0, bytes.length);
                // write the terminating null
                m.setByte((long) bytes.length, (byte) 0); 
                return m;
            }

            @Override
            public Object fromNative(Object value, FromNativeContext context) {
                if (value == null)
                    return null;
                Pointer p = (Pointer) value;
                // handles the null terminator
                return p.getString(0, StandardCharsets.UTF_8.name());
            }

            @Override
            public Class<?> nativeType() {
                return Pointer.class;
            }
        };
        addTypeConverter(String.class, stringConverter);
    }
}

Затем добавьте преобразователь типов в параметры при загрузке библиотеки:

private static final Map<String, ?> UTF8_OPTIONS =
        Collections.singletonMap(Library.OPTION_TYPE_MAPPER, new UTF8TypeMapper());

TheUTF8Lib INSTANCE = Native.load("TheUTF8Lib", TheUTF8Lib.class, UTF8_OPTIONS);
0 голосов
/ 13 июля 2020

Единственный способ, который я нашел, - это использовать класс Function JNA (см. https://java-native-access.github.io/jna/5.2.0/javadoc/com/sun/jna/Function.html) следующим образом:

public void setIp(String ip) {
    Function fSetIp = Function.getFunction("myLib", "setIp", Function.C_CONVENTION, "utf-8");

    Object[] args = {ip};

    fSetIp.invoke(args);
}

Но я должен реализовать это для каждой функции Я хочу позвонить. Не уверен, что есть способ лучше / проще. Если да: ответьте, пожалуйста, на мой вопрос.

...