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_TRUE
和 VK_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.
的含义
使用 FFI
或 JNI
和 boolean
关键字进行网络搜索可以发现多个示例,例如 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 here,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
比 pQueueFamilyIndices
字段的 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 个结构(代码生成的)时有点头疼。
或者,可以在结构构造函数中指定类型映射器,但这需要:
检测每个需要自定义映射的结构。
每个自定义类型都必须另外实现 NativeMapped
以便 JNA 可以确定自定义类型的本机大小(不知道为什么必须指定两次本质上相同的信息)。
每个自定义类型必须支持默认构造函数。
这些都不是特别令人愉快的选择,如果 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 变幻莫测的人。
当我在 JNA 结构中传递 boolean
值时,我从我使用的本机库收到了一个令人惊讶的警告:
value of pCreateInfo->clipped (-1) is neither VK_TRUE nor VK_FALSE
在此库中,VK_TRUE
和 VK_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.
使用 FFI
或 JNI
和 boolean
关键字进行网络搜索可以发现多个示例,例如 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 here,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
比 pQueueFamilyIndices
字段的 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 个结构(代码生成的)时有点头疼。
或者,可以在结构构造函数中指定类型映射器,但这需要:
检测每个需要自定义映射的结构。
每个自定义类型都必须另外实现
NativeMapped
以便 JNA 可以确定自定义类型的本机大小(不知道为什么必须指定两次本质上相同的信息)。每个自定义类型必须支持默认构造函数。
这些都不是特别令人愉快的选择,如果 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 变幻莫测的人。