• javacpp 映射


    为每个平台提供属性

    平台匹配是按照String.startWith方法进行匹配的,比如写了value=android-arm 那么配置的是android-arm和android-arm64,不会配到android-x86

    @Properties(value = {
        @Platform(
            includepath = {"/path/to/generic/include/"},
            linkpath = {"/path/to/generic/lib/"},
            include = {"NativeLibrary.h"},
            link = {"NativeLibrary"}
        ),
        @Platform(
            value = {"android", "ios"},
            includepath = {"/path/to/include/for/mobile/"},
            linkpath = {"/path/to/lib/for/mobile/"}
        ),
        @Platform(
            value = "windows-x86",
            include = {"NativeLibrary.h", "HacksForWindows.h"},
            link = {"NativeLibraryForWindows"}
        )},
        // ...
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    包含多个头文件

    头文件需要按照包含的先后顺序,否则可能会递归。如果头文件是C的头文件,并且没有包含在extern \''\''{} 块里面,我们需要clincude

    忽略属性和宏定义

    默认情况下解析器尝试转换#define宏为java中的final常量,他会尝试采取常量的类型,这样有时候会成功,有时候会失败。

    #ifdef _WIN32
    #define EXPORTS  __declspec(dllexport)
    #define NOINLINE __declspec(noinline)
    #elif defined(__GNUC__)
    #define EXPORTS  __attribute__((visibility ("default")))
    #define NOINLINE __attribute__((noinline))
    #else
    #define EXPORTS
    #define NOINLINE
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    我们可以这样做

    infoMap.put(new Info("EXPORTS", "NOINLINE").cppTypes().annotations());
    
    • 1

    一个空但非空的 Info.cppTypes 列表会阻止解析器尝试猜测要分配给变量的类型,而一个空但非空的 Info.annotations 则指示它将其视为属性,但没有任何相应的 Java 注释,因此其输出也为空。

    宏和控制块

    我们可以定义宏的两个地方:在@Platform(define = { … }, …)注释值中和在InfoMap中的Info.define中。第一个是为了生成器,它只是针对每个字符串输出一个#define行。第二个是由解析器使用的,为用户提供一些通用控制,以决定文件的哪一部分被解析。在这种情况下,条件组#if、#ifdef和#ifndef不会按照通常的方式进行评估。整个条件会与Info匹配,以决定是否解析该块。此外,如果没有Info匹配,则默认解析所有块,而不考虑条件。例如,头文件可能已经包含了像以下这样的块,以防止其他工具如Doxygen或SWIG在某些棘手的代码上出现问题:

    #if !defined(DOXYGEN) && !defined(SWIG)
        // ...
    #endif
    
    • 1
    • 2
    • 3

    JavaCPP 很可能也会在这些块上出现问题,因此最好添加以下内容:

    infoMap.put(new Info("!defined(DOXYGEN) && !defined(SWIG)").define(false));
    
    • 1

    “然而,我们不希望在编译时跳过这些代码块,因此我们不将它们添加到@Platform注解中,但我们可能希望在那里定义其他宏,比如NDEBUG或USE_OPENMP,以便启用函数内联、并行处理等功能,例如:@Platform(define = {“NDEBUG 1”, “USE_OPENMP 1”}, …)。”

    定义宏到域或者方法

    宏的另一个可能要做的事情是将它们作为变量或方法可用。默认情况下,看起来像常量的宏可以很容易地转换为正确的Java语法,将导致一个public static final变量,例如:

    #define VERSION MAJOR "." MINOR
    
    • 1

    默认,他们被转换成这样

    public static final String VERSION = MAJOR + "." + MINOR;
    
    • 1

    但是,如果MAJOR或MINOR实际上没有被定义,或者如果它们被定义为除String以外的其他类型,我们将得到Java编译错误。使用以下信息,我们可以将此宏视为返回给定C++类型值的函数。

    infoMap.put(new Info("VERSION").cppTypes("const char*").translate(false));
    
    • 1

    函数式宏默认情况下不会被映射到Java。但是,在提供了C++类型之后,我们将会得到调用它们的方法,例如:

    #define SQUARE(x) x * x
    
    • 1

    使用这个映射

    infoMap.put(new Info("SQUARE").cppTypes("double", "double"));
    
    • 1

    它会给我们生成这样的结果

    public static native double SQUARE(double x);
    
    • 1

    跳过头文件的行

    当宏无法被(滥用)用来跳过头文件的正确部分时,我们可以将行与正则表达式进行匹配。我们可能只能使用注释,比如这些,例如:

    // START COMPLEX DECLARATIONS
    // ...
    // END COMPLEX DECLARATIONS
    
    • 1
    • 2
    • 3

    在这种情况下,我们可以使用标记各个部分的起始和结束的模式,跳过这些包含信息的行。

    infoMap.put(new Info("filename.h").linePatterns("// START COMPLEX DECLARATIONS", "// END COMPLEX DECLARATIONS").skip());
    
    • 1

    请注意,这些字符串需要是正则表达式。此外,剩余的行不能包含由跳过的行引入的任何语法错误。此外,如果没有使用Info.skip,这将以相反的方式工作,即列出要解析的行。

    除了跳过行模式,还可以跳过单个变量定义。

    infoMap.put(new Info("FFI_SYSV", "FFI_THISCALL", "FFI_FASTCALL", "FFI_STDCALL", "FFI_PASCAL", "FFI_REGISTER", "FFI_MS_CDECL").skip())
    
    • 1

    指定在java中的名称

    默认情况下,解析器尝试使用与对等类的字段和方法相同的名称作为C/C++标识符,但也可以更改它们。一般来说,对于结构体、类或联合,我们可以使用Info.pointerTypes,而对于其他成员变量和函数,我们使用Info.javaNames,就像这样:

    infoMap.put(new Info("full::namespace::TypeNameInCPP").pointerTypes("ClassNameInJava"));
    infoMap.put(new Info("full::namespace::FunctioNameInCPP").javaNames("MethodNameInJava"));
    infoMap.put(new Info("full::namespace::operator +(ns1::TypeA*, ns2::TypeB&)").javaNames("AddNameInJava"));
    
    • 1
    • 2
    • 3

    注意:operator函数我们需要包含一个空格和函数参数通常是可选的,但如果给定的参数不能包含它们的名称,则每个逗号后面必须有一个空格,但在 *, &,( or ) 之前或之后没有空格。此外,类型不应是别名,而应是真正的基础类型名称。这只是解析器的当前限制,而不是如何工作和应该工作的继承问题。

    关于 typedef,由于 Java 中没有等价物,解析器将始终尽可能使用底层类型,但它仅适用于简单情况。C 库的一种常见模式是将结构指针别名为另一个名称,例如:

    struct DataStruct { /* ... */ };
    typedef struct DataStruct* DataHandle;
    
    • 1
    • 2

    尽管默认情况下,解析器应该可以更好地处理这些情况,但现在,我们需要提供这种信息,以便以预期的方式进行映射:

    infoMap.put(new Info("DataStruct").pointerTypes("DataHandle"));
    infoMap.put(new Info("DataHandle").valueTypes("DataHandle").pointerTypes("@Cast(\"DataHandle*\") PointerPointer", "@ByPtrPtr DataHandle"));
    
    • 1
    • 2

    也可以使用 Info.base 更改 Pointer 子类的父类,只要我们提供的类型实现 Pointer,它可以是 Pointer 本身,以便在我们对父类不感兴趣的情况下强制它返回,例如:

    infoMap.put(new Info("ChildClass").base("Pointer"));
    
    • 1

    将声明映射到自定义代码

    有时解析器会惨遭失败,无法使用其他信息来纠正这种情况。在这种情况下,可以使用 Info.javaText 提供自定义 Java 代码,解析器将按原样输出这些代码。例如,在 C++ 中设置成员变量可能并不总是可行的,因为delete函数和其他内容,解析器当前无法理解。虽然我们可以使用 Info.skip 完全忽略该字段,但我们也可以允许使用如下所示的 Info 进行只读访问:

    infoMap.put(new Info("DataStruct::aReadOnlyField").javaText("public native @MemberGetter @Const @ByRef FieldType aReadOnlyField();"));
    
    • 1

    重定义宏代码

    对于宏,也可以在实际处理之前重新定义其全部内容。例如,当存在一个类似函数的宏,该宏附加了调用约定、导出指令和其他导致分析器出现问题的属性时,它可能很有用。在这种情况下,我们可以使用 Info.cppText 使宏无效,如下所示:

    infoMap.put(new Info("DECORATE").cppText("#define DECORATE(returnType) returnType"));
    
    • 1

    在帮助程序类中编写其他代码

    如果解析器没有失败,但没有得到完全正确的结果,或者如果我们想提供特定于 Java 的附加功能,例如使用 Pointer.DeallocatorReference 自定义解除分配器,我们可以将该代码放在帮助程序类中。对于名为 NativeLibrary 的库,它可能如下所示:

    import org.bytedeco.javacpp.*;
    import org.bytedeco.javacpp.annotation.*;
    
    public class NativeLibraryHelper extends NativeLibraryConfig {
        /** Registers a custom deallocator when the user calls our DataHandle.create(). */
        public static abstract class AbstractDataHandle extends Pointer {
            protected static class ReleaseDeallocator extends NativeLibrary.DataHandle implements Pointer.Deallocator {
                ReleaseDeallocator(NativeLibrary.DataHandle p) { super(p); }
                @Override public void deallocate() { NativeLibrary.releaseData(this); }
            }
    
            public AbstractDataHandle(Pointer p) { super(p); }
    
            public static NativeLibrary.DataHandle create() {
                NativeLibrary.DataHandle p = NativeLibrary.createData();
                if (p != null) {
                    p.deallocator(new ReleaseDeallocator(p));
                }
                return p;
            }
        }
    
        public static void customDataMethod(NativeLibrary.DataHandle p) { /* ... */ }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    然后,我们唯一需要指定的另一件事是 @Properties(…, helper = “…”) 注解值中该类的完全限定名称:

    @Properties(
        // ...
        target = "NativeLibrary",
        helper = "NativeLibraryHelper"
    )
    public class NativeLibraryConfig implements InfoMapper {
        public void map(InfoMap infoMap) {
            infoMap.put(new Info("DataStruct").pointerTypes("DataHandle").base("AbstractDataHandle"));
            // ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这允许目标类从帮助程序类继承,这样我们就可以从目标类引用到帮助程序类中定义的任何方法或类,反之亦然。

    创建C++模板实例

    使用 C++ 模板时,通常不清楚应该使用哪些类型来创建实例,以及如何命名它们,因此我们需要手动指定它们。幸运的是,它通常非常简单,类似于在 Java 中指定要使用的名称,再次将 Info.pointerTypes 用于数据结构,将 Info.javaNames 用于函数,例如:

    infoMap.put(new Info("data::Blob").pointerTypes("FloatBlob"));
    infoMap.put(new Info("data::Blob").pointerTypes("DoubleBlob"));
    infoMap.put(new Info("processor::process >").javaNames("processFloatBlob"));
    infoMap.put(new Info("processor::process >").javaNames("processDoubleBlob"));
    
    • 1
    • 2
    • 3
    • 4

    注意:由于解析器的当前状态,我们需要在每对>之间有一个空格,但模板参数之间的逗号后不应有任何空格。同样,这只是解析器当前实现的限制,而不是 InfoMap 可以和应该如何工作的继承问题。

    定义基本 C++ 容器的包装器

    虽然 std::vector 和 std::map 等容器只是模板,但它们的定义非常复杂,并且因 C++ 编译器而异,因此它们不可移植。相反,解析器为这些基本容器提供了一组通用功能。与普通模板一样,我们需要手动创建实例,每个实例都有一个 Info,但要创建一个对等类,我们还需要设置 Info.define,例如:

    infoMap.put(new Info("std::vector >").pointerTypes("FloatBlobVector").define());
    infoMap.put(new Info("std::map >").pointerTypes("StringFloatBlobMap").define());
    
    • 1
    • 2

    默认情况下,支持的基本容器列表包括 InfoMap.java 中列出的容器,但也可以以这种方式将其他类似模板附加到该列表中:

    infoMap.put(new Info("basic/containers").cppTypes("templates::MyMap", "templates::MyVector"));
    
    • 1

    对 C++ 容器类型使用适配器

    对于某些标准 C++ 容器类型,有时最好使用适配器将它们映射到现有 Java 类型。默认情况下,生成器为 std::string、std::wstring、std::vector、std::shared_ptr 和 std::unique_ptr 提供了一些适配器。因此,默认情况下,解析器使用相应的注释@StdString、@StdWString、@StdVector、@SharedPtr 和 @UniquePtr)将这些类型直接映射到指针类型和标准 Java 类型(String、int[] 等),如 InfoMap.java 中的默认值所示。对于 @SharedPtr 和 @UniquePtr,由于命名空间有时可能是 boost 或 std,我们需要在@Platform注解中指定它,如下所示:

    @Platform(compiler = "cpp11", define = {"SHARED_PTR_NAMESPACE std", "UNIQUE_PTR_NAMESPACE std"}, ... )
    
    • 1

    这可能会导致如下输出,但请注意,现有的 Java 类型具有局限性,例如,Java 数组无法调整大小,而 std::vector 可以:

    public static native void transform(@SharedPtr DataHandle arg0, @StdVector int[] parameters);
    
    • 1

    用户可以自行创建更多适配器,并将它们与@Adapter注释一起使用,可以直接使用,也可以在新创建的注释上使用。至少,我们基本上需要定义一个 C++ 类模板:

    • 一个构造函数,它采用const指针(可以是数组)指向值,它的size(对于某些容器,可以始终为 0 或 1)以及容器本身的owner指针(可能为 null 或等于值指针),
    • 具有相同参数集的 assign() 方法,但不是 const
    • 另一个构造函数引用现有容器对象,如果需要,该对象可以是右值引用,
    • 静态 void deallocate(void *owner) 方法来调用析构函数,
    • 适当的强制转换运算符,以返回函数调用所需的类型,以及名为 ptr、size 和 owner 的成员变量,它们基本上反映了容器的状态,但在容器之外。

    每个适配器实例的生存期都很短,因此我们不能依赖字段来存储任何应该保留的内容。例如,类似于 std::shared_ptr 的智能指针所需的适配器可能如下所示:

    template<class T> class SmartPtrAdapter {
    public:
        SmartPtrAdapter(const T* ptr, int size, void *owner) :
            ptr((T*)ptr),
            size(size),
            owner(owner),
            smartPtr2(owner != NULL && owner != ptr ? *(smart_ptr<T>*)owner : smart_ptr<T>((T*)ptr)),
            smartPtr(smartPtr2) { }
        SmartPtrAdapter(const smart_ptr<T>& smartPtr) :
            ptr(0),
            size(0),
            owner(0),
            smartPtr2(smartPtr),
            smartPtr(smartPtr2) { }
        void assign(T* ptr, int size, void* owner) {
            this->ptr = ptr;
            this->size = size;
            this->owner = owner;
            this->smartPtr = owner != NULL && owner != ptr ? *(smart_ptr<T>*)owner : smart_ptr<T>((T*)ptr);
        }
        static void deallocate(void* owner) {
            delete (smart_ptr<T>*)owner;
        }
        operator T*() {
            ptr = smartPtr.get();
            if (owner == NULL || owner == ptr) {
                owner = new smart_ptr<T>(smartPtr);
            }
            return ptr;
        }
        operator smart_ptr<T>&() {
            return smartPtr;
        }
        operator smart_ptr<T>*() {
            return ptr ? &smartPtr : 0;
        }
        T* ptr;
        int size;
        void* owner;
        smart_ptr<T> smartPtr2;
        smart_ptr<T>& smartPtr;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    跟着下面的注解 Info.annotations

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.PARAMETER})
    @Adapter("SmartPtrAdapter")
    public @interface SmartPtr {
        /** template type */
        String value() default "";
    }
    
    // ...
    
    infoMap.put(new Info("ns::smart_ptr").skip().annotations("@SmartPtr"));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    处理抽象类和虚拟方法

    对于抽象类或其他由于删除的构造函数而无法实例化的类,或者解析器可能不理解的类,我们可以使用 Info.purify 跳过构造函数,而对于包含我们想在 Java 中重写的虚拟方法的类,我们可以使用 Info.virtualize 让解析器用@Virtual注解来注释方法, 这让 Generator 输出必要的机制,使用隐藏的具体实现和 JNI 回调来使其工作。出于这个原因,对于具有最终用户需要实现的纯虚函数的抽象类,我们不应该同时激活这两个设置,例如:

    class Logger {
        protected:
        virtual void log(const std::string& message) = 0;
        virtual ~Logger() {}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    他的Info

    infoMap.put(new Info("Logger").purify(false).virtualize());
    
    • 1

    生成以下可用的对等类:

    public static class Logger extends Pointer {
        static { Loader.load(); }
        /** Default native constructor. */
        public Logger() { super((Pointer)null); allocate(); }
        /** Pointer cast constructor. Invokes {@link Pointer#Pointer(Pointer)}. */
        public Logger(Pointer p) { super(p); }
        private native void allocate();
    
        @Virtual(true) protected native void log(@Const @StdString @ByRef BytePointer message);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    其他

    Info (JavaCPP 1.5.9 API) (bytedeco.org)

    Mapping Recipes · bytedeco/javacpp Wiki · GitHub

    入门

    import org.bytedeco.javacpp.*;
    import org.bytedeco.javacpp.annotation.*;
    import org.bytedeco.javacpp.tools.*;
    
    @Properties(
        value = @Platform(
            includepath = {"/path/to/include/"},
            preloadpath = {"/path/to/deps/"},
            linkpath = {"/path/to/lib/"},
            include = {"NativeLibrary.h"},
            preload = {"DependentLib"},
            link = {"NativeLibrary"}
        ),
        target = "NativeLibrary"
    )
    public class NativeLibraryConfig implements InfoMapper {
        public void map(InfoMap infoMap) {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    执行下面的指令生成java和动态库

    $ java -jar javacpp.jar NativeLibraryConfig.java
    $ java -jar javacpp.jar NativeLibrary.java
    
    • 1
    • 2

    使用例子

    在Example里面

    手写pointer并使用

    Examples · bytedeco/javacpp Wiki · GitHub

  • 相关阅读:
    Swoole协程
    7-8 循环日程安排问题
    sqlalchemy event监听
    3D调研-摄像头
    〖全域运营实战白宝书 - 运营角色认知篇②〗- 什么是运营?
    新品上市的软文怎么写?品宣软文写作要注意以下几点
    php基于微信小程序的医院预约挂号系统+uinapp+Mysql+计算机毕业设计
    UE5实现PS图层样式投影效果
    机器学习——代价敏感错误率与代价曲线
    spring boot flowable多人前加签
  • 原文地址:https://blog.csdn.net/ughome/article/details/134449198