web-dev-qa-db-ja.com

JNAはJavaブール値を-1整数にマップしますか?

JNA構造でboolean値を渡すときに使用しているネイティブライブラリから驚くべき警告が表示されます。

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

このライブラリではVK_TRUEおよびVK_FALSEは、それぞれ1および0として定義されます。

構造自体は特に複雑ではなく、その他すべてが機能しているように見えます(ネイティブライブラリは「未定義」のブール値を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の場合、警告は表示されません。trueの場合、警告が表示されます。JNAがtrueを整数-1にマッピングしているようです。

このライブラリで使用されるネイティブのブール値は多くありませんが、1つがtrueに設定されている場合は常に同じ動作をします(他のすべても正常に動作します)。

特に、clippedintに変更し、値を明示的に1または0に設定すると、すべてが機能します!

JNAブール値trueのデフォルト値は-1ですか?

もしそうなら、どのようにして型マッピングを上書きするのでしょうか?

それともintを「手動で」使用するだけですか?

10
stridecolossus

実際、そこにあるようにareさまざまなネイティブライブラリ構造に多数のブール値があり、実際には数百ものブール値があります!実装がその制限を強制しているからといって、すべてをintで置き換えるのではなく、ブール型フィールドの意図を保持するのは良いことです。そこで、JNAの型変換を検討するのに少し時間を費やしました...

JNAは、ネイティブライブラリの作成時にNative::loadへの追加の引数として渡されるTypeMapperを使用したカスタムタイプのマッピングをサポートします。カスタムの型マッピングは、Javaから/へのネイティブコンバーターインターフェイス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);

ただし、これは問題の構造がinsideJNAライブラリインターフェースで定義されている場合にのみ機能します-少数の小さなライブラリを作成している場合は簡単です構造体(通常はそうです)ですが、数百のメソッドと約500の構造体(コード生成)があると、少し頭痛の種になります。

代わりに、型マッパーを構造コンストラクターで指定できますが、これには以下が必要です。

  1. カスタムマッピングが必要なevery構造のインスツルメンテーション。

  2. jNAがカスタムタイプのネイティブサイズを決定できるように、すべてのカスタムタイプはNativeMappedを追加で実装する必要があります(なぜ基本的に同じ情報を2回指定する必要があるかはわかりません)。

  3. 各カスタム型はデフォルトのコンストラクタをサポートする必要があります。

これらはどちらも特に快適なオプションではありません。JNAが両方のケースをカバーするグローバルタイプマッピングをサポートしているとしたらすばらしいでしょう。タイプマッパーを使用してすべての構造をコード生成し直す必要があると思います。はぁ。

ただし、これは、問題の構造がJNAライブラリインターフェースの内部で定義されている場合にのみ機能します。簡単な回避策は、ライブラリ内で基本クラス構造を定義し、それから他のすべてを拡張することです。

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

public class VkSwapchainCreateInfoKHR extends VulkanStructure { ... }

同じメカニズムを使用して、現在次のようなネイティブintに約300のコード生成列挙を自動的にマップします。

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の気まぐれに頭を回そうとしている誰かを助けるでしょう。

2
stridecolossus