На самом деле, как выясняется, - это много логических значений в различных структурах нативных библиотек, на самом деле их несколько сотен! Было бы неплохо сохранить намерение логических полей, а не заменять их все на 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;
...
}
(недавно нашел пример, делающий именно это здесь ).
Надеюсь, что все эти колебания помогут кому-то попытаться обойти капризы JNA.