JNA отображает Java логическое на -1 целое число? - PullRequest
9 голосов
/ 18 марта 2019

Я получаю удивительное предупреждение от собственной библиотеки, которую я использую при передаче значения boolean в структуре JNA:

value of pCreateInfo->clipped (-1) is neither VK_TRUE nor VK_FALSE

В этой библиотеке VK_TRUE и VK_FALSE определены #как 1 и 0. соответственно.

Сама структура не особенно сложна, и все остальное выглядит как работающее (родная библиотека, похоже, рассматривает логическое значение undefined как false), ноздесь это в любом случае:

public class VkSwapchainCreateInfoKHR extends Structure {
    public int sType;
    public Pointer pNext;
    public int flags;
    public Pointer surface;
    public int minImageCount;
    public int imageFormat;
    public int imageColorSpace;
    public VkExtent2D imageExtent;
    public int imageArrayLayers;
    public int imageUsage;
    public int imageSharingMode;
    public int queueFamilyIndexCount;
    public Pointer pQueueFamilyIndices;
    public int preTransform;
    public int compositeAlpha;
    public int presentMode;
    public boolean clipped;       // <--------- this is the field in question
    public Pointer oldSwapchain;
}

Если поле clipped имеет значение false, предупреждения нет, если это правда, тогда я получаю предупреждение - кажется, JNA отображает true в целое число -1?

Существует не так много собственных логических значений, используемых этой библиотекой, но я получаю то же самое поведение всякий раз, когда устанавливается значение true (и снова все остальное работает нормально).

В частности, если я изменяю clipped должно быть int и установить значение явно в 1 или 0, все работает!

Является ли -1 значением по умолчанию для логического выражения JNA true?

Если так, как быЯ перехожу к переписыванию типов карт?

Или я должен просто использовать int «вручную»?

Ответы [ 2 ]

5 голосов
/ 18 марта 2019

JNA сопоставляется с нативными библиотеками через libffi. В libffi нет типа bool, поэтому необходимо использовать другие сопоставления - сопоставление типов JNA по умолчанию выбирает сопоставление boolean с ffi_type_uint32. Это работает в структуре (структурах), потому что это соответствует 32-битному размеру отображения, но не определению: в C 0 равно false, а все ненулевое - true. Только если нативный тип также равен boolean, эта 0 / ненулевая интерпретация восстанавливает значение как false / true.

Поиск в Интернете с использованием ключевых слов FFI или JNI и boolean может обнаружить несколько примеров, таких как этот и этот , где непредсказуемые результаты возникают при доступе к библиотекам через FFI или JNI и не соответствуют требованию 0/1 для логических значений. Последний пример выглядит очень похоже на этот случай, когда истинная Java boolean интерпретируется как C int со значением, отличным от 1.

Где-то под колпаком между FFI и вашей библиотекой, и, возможно, в скомпилированном байтовом коде и / или зависимых от платформы / компилятора преобразованиях типов, вполне вероятно, что побитовое "не" применяется к 0x00000000, превращая его в 0xffffffff, что все еще «верно» в C.

Суть в том, что JNA по умолчанию отобразит Java булево false на 32-битное собственное значение 0, а Java true на 32-битное собственное значение, которое не равно 0, и это все это можно предположить. Если вашей библиотеке требуется, чтобы true имел целочисленное значение 1, либо используйте целочисленный тип, который вы можете специально установить, либо используйте настраиваемое сопоставление типов для boolean, которое устанавливает для int значение 0 или 1 для вас. JNA's W32APITypeMapper содержит пример этого преобразования в 1 или 0 для типа Windows BOOL.

В вашем случае, при условии, что вы отображаете структуру VkSwapchainCreateInfoKHR , определенную здесь , тип clipped - VkBool32:

typedef struct VkSwapchainCreateInfoKHR {
    VkStructureType                  sType;
    const void*                      pNext;
    VkSwapchainCreateFlagsKHR        flags;
    VkSurfaceKHR                     surface;
    uint32_t                         minImageCount;
    VkFormat                         imageFormat;
    VkColorSpaceKHR                  imageColorSpace;
    VkExtent2D                       imageExtent;
    uint32_t                         imageArrayLayers;
    VkImageUsageFlags                imageUsage;
    VkSharingMode                    imageSharingMode;
    uint32_t                         queueFamilyIndexCount;
    const uint32_t*                  pQueueFamilyIndices;
    VkSurfaceTransformFlagBitsKHR    preTransform;
    VkCompositeAlphaFlagBitsKHR      compositeAlpha;
    VkPresentModeKHR                 presentMode;
    VkBool32                         clipped;
    VkSwapchainKHR                   oldSwapchain;
} VkSwapchainCreateInfoKHR;

Где ...

typedef uint32_t VkBool32;

Таким образом, int является правильным отображением здесь - вам нужно сопоставить clipped с 32-разрядным целым числом Редактировать: Как вы указали в своем ответе, его легко добавить ваш собственный преобразователь типов для лучшей обработки этих int значений!

(Пока я рассматриваю сопоставления типов, вы можете найти IntByReference лучшее сопоставление, чем Pointer для поля pQueueFamilyIndices.) (Ваше сопоставление корректно для переменной длины int массив.)

2 голосов
/ 19 марта 2019

На самом деле, как выясняется, - это много логических значений в различных структурах нативных библиотек, на самом деле их несколько сотен! Было бы неплохо сохранить намерение логических полей, а не заменять их все на int только потому, что реализация навязывает это ограничение. Поэтому я провел некоторое время, изучая преобразование типов JNA ...

JNA поддерживает отображение пользовательских типов, используя TypeMapper, передаваемый в качестве дополнительного аргумента Native::load при создании собственной библиотеки. Пользовательские сопоставления типов определяются с использованием интерфейса конвертера Java-to / from-native TypeConverter.

Определение пользовательской логической оболочки, которая отображает Java boolean в / из C int с 1 = true и 0 = false, довольно просто:

public final class VulkanBoolean {
    static final TypeConverter MAPPER = new TypeConverter() {
        @Override
        public Class<?> nativeType() {
            return Integer.class;
        }

        @Override
        public Object toNative(Object value, ToNativeContext context) {
            if(value == null) {
                return VulkanBoolean.FALSE.toInteger();
            }
            else {
                final VulkanBoolean bool = (VulkanBoolean) value;
                return bool.toInteger();
            }
        }

        @Override
        public Object fromNative(Object nativeValue, FromNativeContext context) {
            if(nativeValue == null) {
                return VulkanBoolean.FALSE;
            }
            else {
                final int value = (int) nativeValue;
                return value == 1 ? VulkanBoolean.TRUE : VulkanBoolean.FALSE;
            }
        }
    };

    public static final VulkanBoolean TRUE = VulkanBoolean(true);
    public static final VulkanBoolean FALSE = VulkanBoolean(false);

    private final boolean value;

    private VulkanBoolean(boolean value) {
        this.value = value;
    }

    public boolean value() {
        return value;
    }

    public int toInteger() {
        return value ? 1 : 0;
    }
}

Тип картографа (-ов) регистрируется таким образом:

final DefaultTypeMapper mapper = new DefaultTypeMapper();
mapper.addTypeConverter(VulkanBoolean.class, VulkanBoolean.MAPPER);
...

final Map<String, Object> options = new HashMap<>();
options.put(Library.OPTION_TYPE_MAPPER, mapper);
Native.load("vulkan-1", VulkanLibrary.class, options);

Однако это работает, только если рассматриваемая структура (и) определена внутри интерфейса библиотеки JNA - тривиально, если вы пишете небольшую библиотеку с несколькими структурами (что обычно имеет место), но немного головная боль, когда у вас есть несколько сотен методов и ~ 500 структур (которые генерируются кодом).

В качестве альтернативы преобразователь типов может быть указан в конструкторе структуры, но для этого необходимо:

  1. инструментарий каждая структура, которая нуждается в пользовательских сопоставлениях.

  2. каждый пользовательский тип должен дополнительно реализовывать NativeMapped, чтобы JNA могла определять собственный размер пользовательского типа (не знаю, почему, по сути, одну и ту же информацию нужно указывать дважды).

  3. каждый пользовательский тип должен поддерживать конструктор по умолчанию.

Ни один из этих вариантов не является особенно приятным, было бы неплохо, если бы JNA поддерживал глобальные отображения типов, которые охватывали оба случая. Думаю, мне нужно заново сгенерировать код всех структур с помощью type-mapper. Вздох.

Однако это работает, только если рассматриваемая структура (ы) определены внутри интерфейса библиотеки JNA. Простой обходной путь - определить структуру базового класса в библиотеке и расширить все остальные:

public interface Library {
    abstract class VulkanStructure extends Structure {
        protected VulkanStructure() {
            super(VulkanLibrary.TYPE_MAPPER);
        }
    }
...
}

public class VkSwapchainCreateInfoKHR extends VulkanStructure { ... }

Я использовал тот же механизм для автоматического сопоставления ~ 300 сгенерированных кодом перечислений с собственным int, который в настоящее время выглядит следующим образом:

public enum VkSubgroupFeatureFlag implements IntegerEnumeration {
    VK_SUBGROUP_FEATURE_BASIC_BIT(1),   
    VK_SUBGROUP_FEATURE_VOTE_BIT(2),    
    ...

    private final int value;

    private VkSubgroupFeatureFlag(int value) {
        this.value = value;
    }

    @Override
    public int value() {
        return value;
    }
}

В настоящее время все структуры, которые ссылаются на «перечисление», фактически реализованы как int. С пользовательским преобразователем типа для IntegerEnumeration на месте тип поля может быть фактическим перечислением Java, и JNA будет обрабатывать преобразование в / из целочисленного значения (которое я в настоящее время должен вручную). Это, очевидно, делает структуры немного более безопасными по типу, определенно более четкими и явно ссылается на фактическое перечисление, а не на int - приятно.

т.е.

public class VkSwapchainCreateInfoKHR extends VulkanStructure {
    ...
    public int flags;
    public Pointer surface;
    public int minImageCount;
    // The following fields were int but are now the Java enumerations
    public VkFormat imageFormat = VkFormat.VK_FORMAT_UNDEFINED;
    public VkColorSpaceKHR imageColorSpace;
    ...
}

(недавно нашел пример, делающий именно это здесь ).

Надеюсь, что все эти колебания помогут кому-то попытаться обойти капризы JNA.

...