当我在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
字段为false则没有警告,如果它是真的那么我得到警告 - 看来JNA将true
映射到整数-1?
这个库使用的本机布尔值不多,但只要一个设置为true,我就会得到相同的行为(并且其他一切工作正常)。
特别是,如果我将clipped
更改为int
并将值明确设置为1或0,一切正常!
-1是JNA boolean true
的默认值吗?
如果是这样,我将如何过度使用类型映射?
或者我应该'手动'使用int
?
JNA通过libffi
映射到本机库。在bool
中没有libffi
类型,因此必须使用其他映射 - JNA的default type mapping选择将boolean
映射到ffi_type_uint32
。这在结构中起作用,因为它恰好匹配32位映射大小,但不匹配定义:在C中,0为false,任何非零都为真。仅当本机类型也是boolean
时,此0 /非零解释才重新获得含义为false / true。
使用FFI
或JNI
和boolean
关键字的网络搜索可以发现多个示例,例如this one和this one,当通过FFI或JNI访问库时不会出现不可预测的结果,并且不符合布尔值的0/1要求。后一个例子看起来非常类似于这种情况,其中真正的Java boolean
被解释为具有1以外的值的C int
。
在FFI和你的库之间的某个地方,可能在编译的字节代码和/或平台/编译器相关的类型转换中,可能是一个按位“not”被应用于0x00000000
,把它变成0xffffffff
仍然是'真的'在C.
最重要的是,JNA默认将Java boolean false
映射到32位本机值0,将Java boolean 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
比Pointer
更适合pQueueFamilyIndices
字段。)
(您的映射对于可变长度的int
数组是正确的。)
事实上,事实证明,在各种原生图书馆结构中有很多布尔,其实几百个!保留布尔字段的意图是很好的,而不是仅仅因为实现强制执行该限制而将它们全部替换为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种结构(代码生成)。
或者,可以在结构构造函数中指定类型映射器,但这需要:
NativeMapped
,以便JNA可以确定自定义类型的本机大小(不知道为什么基本上必须指定两次相同的信息)。这些都不是特别令人愉快的选择,如果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;
...
}
(最近发现一个例子正是在做here)。
希望所有这些莫名其妙的帮助有人试图了解JNA的变幻莫测。