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"}
    )},
    // ...
)

包含多个头文件

头文件需要按照包含的先后顺序,否则可能会递归。如果头文件是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

我们可以这样做

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

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

宏和控制块

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

#if !defined(DOXYGEN) && !defined(SWIG)
    // ...
#endif

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

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

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

定义宏到域或者方法

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

#define VERSION MAJOR "." MINOR

默认,他们被转换成这样

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

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

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

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

#define SQUARE(x) x * x

使用这个映射

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

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

public static native double SQUARE(double x);

跳过头文件的行

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

// START COMPLEX DECLARATIONS
// ...
// END COMPLEX DECLARATIONS

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

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

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

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

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

指定在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"));

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

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

struct DataStruct { /* ... */ };
typedef struct DataStruct* DataHandle;

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

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

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

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

将声明映射到自定义代码

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

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

重定义宏代码

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

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

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

如果解析器没有失败,但没有得到完全正确的结果,或者如果我们想提供特定于 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) { /* ... */ }
}

然后,我们唯一需要指定的另一件事是 @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"));
        // ...
    }
}

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

创建C++模板实例

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

infoMap.put(new Info("data::Blob<float>").pointerTypes("FloatBlob"));
infoMap.put(new Info("data::Blob<double>").pointerTypes("DoubleBlob"));
infoMap.put(new Info("processor::process<double,data::Blob<float> >").javaNames("processFloatBlob"));
infoMap.put(new Info("processor::process<double,data::Blob<double> >").javaNames("processDoubleBlob"));

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

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

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

infoMap.put(new Info("std::vector<data::Blob<float> >").pointerTypes("FloatBlobVector").define());
infoMap.put(new Info("std::map<std::string,data::Blob<float> >").pointerTypes("StringFloatBlobMap").define());

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

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

对 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"}, ... )

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

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

用户可以自行创建更多适配器,并将它们与@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;
};

跟着下面的注解 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"));

处理抽象类和虚拟方法

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

class Logger {
    protected:
    virtual void log(const std::string& message) = 0;
    virtual ~Logger() {}
};

他的Info

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

生成以下可用的对等类:

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);
}

其他

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) {
    }
}

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

$ java -jar javacpp.jar NativeLibraryConfig.java
$ java -jar javacpp.jar NativeLibrary.java

使用例子

在Example里面

手写pointer并使用

Examples · bytedeco/javacpp Wiki · GitHub

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值