平台匹配是按照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())
默认情况下,解析器尝试使用与对等类的字段和方法相同的名称作为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++ 模板时,通常不清楚应该使用哪些类型来创建实例,以及如何命名它们,因此我们需要手动指定它们。幸运的是,它通常非常简单,类似于在 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"));
注意:由于解析器的当前状态,我们需要在每对>之间有一个空格,但模板参数之间的逗号后不应有任何空格。同样,这只是解析器当前实现的限制,而不是 InfoMap 可以和应该如何工作的继承问题。
虽然 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());
默认情况下,支持的基本容器列表包括 InfoMap.java 中列出的容器,但也可以以这种方式将其他类似模板附加到该列表中:
infoMap.put(new Info("basic/containers").cppTypes("templates::MyMap", "templates::MyVector"));
对于某些标准 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++ 类模板:
每个适配器实例的生存期都很短,因此我们不能依赖字段来存储任何应该保留的内容。例如,类似于 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