JNA 将 Java 布尔值映射到 -1 整数?

JNA maps Java boolean to -1 integer?

当我在 JNA 结构中传递 boolean 值时,我从我使用的本机库收到了一个令人惊讶的警告:

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

在此库中,VK_TRUEVK_FALSE 分别被 #defined 为 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 字段为假,则没有警告,如果为真,则我收到警告 - 看来 JNA 正在将 true 映射到整数 -1?

这个库使用的本机布尔值不多,但只要将一个设置为 true,我就会得到相同的行为(而且其他一切都正常)。

特别是,如果我将 clipped 更改为 int 并将值明确设置为 1 或 0,一切正常!

-1 是 JNA 布尔值的默认值吗true

如果是这样,我将如何覆盖类型映射?

或者我应该只使用 int 'manually'?

JNA 通过 libffi 映射到本机库。 libffi 中没有 bool 类型,因此必须使用其他映射——JNA 的 default type mapping 选择将 boolean 映射到 ffi_type_uint32。这在结构中有效,因为它恰好匹配 32 位映射大小,但不匹配定义:在 C 中,0 为假,任何非零值都为真。仅当本机类型也是 boolean 时,此 0/非零解释才重新获得 false/true.

的含义

使用 FFIJNIboolean 关键字进行网络搜索可以发现多个示例,例如 this one and this one 通过 FFI 或 JNI 访问库时会出现不可预测的结果并且不符合布尔值的 0 / 1 要求。后一个示例看起来与这种情况非常相似,其中真正的 Java boolean 被解释为 C int,其值不是 1.

在 FFI 和你的库之间的某个地方,并且可能在编译的字节代码 and/or platform/compiler-dependent 类型转换中,很可能是按位 "not" 被应用于 0x00000000,把它变成 0xffffffff,在 C 中仍然是 'true'。

底线是 JNA 默认将 Java 布尔值 false 映射到 32 位本机值 0,并将 Java 布尔值 true 映射到一个不为 0 的 32 位本机值,这就是所有可以假设的。如果您的库要求 true 的整数值为 1,请使用您可以专门设置的整数类型,或者为 boolean 使用自定义类型映射,将 int 设置为 0或 1 给你。 JNA 的 W32APITypeMapper 有一个将 Windows BOOL 类型转换为 1 或 0 的示例。

在你的例子中,假设你正在映射 VkSwapchainCreateInfoKHR 结构 defined hereclipped 的类型是 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 值很简单!

(当我查看类型映射时,您可能会发现 IntByReferencepQueueFamilyIndices 字段的 Pointer 更好。)(您的映射对于可变长度 int 数组是正确的。)

事实证明 各种本机库结构中有很多布尔值,实际上有数百个!最好保留布尔字段的意图,而不是仅仅因为实现强制执行该限制而将它们全部替换为 int。所以我花了一些时间研究 JNA 类型转换...

JNA 支持使用 TypeMapper 的自定义类型映射,在创建本机库时将其作为附加参数传递给 Native::load。自定义类型映射是使用 Java-to/from-native 转换器接口 TypeConverter.

定义的

定义映射 Java boolean to/from 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);

但是,这仅在所讨论的结构定义为 inside JNA 库接口时才有效——如果编写一个包含少量结构的小型库(通常是这种情况),则这很简单但是当你有几百个方法和 ~500 个结构(代码生成的)时有点头疼。

或者,可以在结构构造函数中指定类型映射器,但这需要:

  1. 检测每个需要自定义映射的结构。

  2. 每个自定义类型都必须另外实现 NativeMapped 以便 JNA 可以确定自定义类型的本机大小(不知道为什么必须指定两次本质上相同的信息)。

  3. 每个自定义类型必须支持默认构造函数。

这些都不是特别令人愉快的选择,如果 JNA 支持涵盖这两种情况的全局类型映射就好了。我想我需要用类型映射器重新代码生成所有结构。叹。

然而,只有在 JNA 库接口内部定义了相关结构时,这才有效。一个简单的解决方法是在库中定义一个 base-class 结构并从中扩展所有其他结构:

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;
    }
}

目前所有引用 'enumeration' 的结构实际上都是作为 int 实现的。使用 IntegerEnumeration 的自定义类型转换器,字段类型可以是实际的 Java 枚举,JNA 将处理转换 to/from 整数值(我目前必须手动) .这显然使结构稍微更加类型安全,绝对更清晰,并且明确引用实际枚举而不是 int - nice.

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;
    ...
}

(最近发现了一个例子 here)。

希望所有这些废话能帮助那些试图了解 JNA 变幻莫测的人。