JNI 和构造函数
JNI and constructors
我有一个编译好的库需要在项目中使用。简而言之,它是一个用于与特定硬件交互的库。我拥有的是 .a 和 .dll 库文件,分别用于 linux 和 windows,以及一堆 C++ .h headers 以及所有 public 函数和 class那里有描述。
问题是项目需要在 Java 中,所以我需要为这个库编写一个 JNI 包装器,老实说,我从来没有这样做过。不过没关系,我要好好学习了。
我在网上阅读了一堆文档,我弄清楚了传递变量、从本机代码创建 java objects 等
我想不通的是,如何使用 JNI 使用本机构造函数?我不知道这些构造函数的源代码是什么,我只有这样的headers:
namespace RFDevice {
class RFDEVICE_API RFEthernetDetector
{
public:
//-----------------------------------------------------------------------------
// FUNCTION RFEthernetDetector::RFEthernetDetector
/// \brief Default constructor of RFEthernetDetector object.
///
/// \return void : N/A
//-----------------------------------------------------------------------------
RFEthernetDetector();
RFEthernetDetector(const WORD wCustomPortNumber);
所以基本上如果我要用 C++ 编写我的程序(我不会),我会做类似的事情
RFEthernetDetector ethernetDetector = new RFEthernerDetector(somePort);
然后使用那个 object。但是...如何使用 JNI 在 Java 中执行此操作?
我不明白我应该如何为构造函数创建一个本机方法,它会从我的 .a 库调用构造函数,然后有一些方法来处理那个特定的 object?我知道如何从本机代码创建 java objects - 但问题是我没有关于 RFEthernetDetector class 内部结构的任何信息 - 只有其中的一些 public 字段和 public 方法。
而且我似乎无法在网上找到合适的文章来帮助我。我该怎么做?
更新:进一步说明。
我像这样创建一个 .java 包装器 class:
public class RFEthernetDetector
{
public RFEthernetDetector(int portNumber)
{
Init(portNumber);
}
public native void Init(int portNumber); // Void? Or what?
}
然后我用-h参数编译生成JNI.h文件:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class RFEthernetDetector */
#ifndef _Included_RFEthernetDetector
#define _Included_RFEthernetDetector
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: RFEthernetDetector
* Method: Init
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_RFEthernetDetector_Init
(JNIEnv *, jobject, jint);
#ifdef __cplusplus
}
#endif
#endif
然后我创建一个实现来调用我的 .a 库中的函数:
#include "RFEthernetDetector.h" // auto-generated JNI header
#include "RFEthernetDetector_native.h" // h file that comes with the library,
//contains definition of RFEthernetDetector class
/*
* Class: RFEthernetDetector
* Method: Init
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_RFEthernetDetector_Init(JNIEnv *env, jobject thisObj, jint value)
{
RFEthernetDetector *rfeDetector = new RFEthernetDetector(value); // constructor from the library
// now how do I access this new object from Java?
// if I need to later call rfDetector->doSomething() on that exact class instance?
}
您需要在 Java 中构建 native
class,然后 运行 javah
程序将构建 Java 预计。然后,您需要将 java 存根映射到 C++ 代码,以编译和分发该绑定库以及您的 java 程序。
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html
您需要构建一个 RFEthernetDetector
Java class,通过指针,拥有 RFEthernetDetector
C++ 方面。 This is no fun,但语言间胶水永远不会。
// In this design, the C++ object needs to be explicitly destroyed by calling
// close() on the Java side.
// I think that Eclipse, at least, is configured by default to complain
// if an AutoCloseable is never close()d.
public class RFEthernetDetector implements AutoCloseable {
private final long cxxThis; // using the "store pointers as longs" convention
private boolean closed = false;
public RFEthernetDetector(int port) {
cxxThis = cxxConstruct(port);
};
@Override
public void close() {
if(!closed) {
cxxDestroy(cxxThis);
closed = true;
}
}
private static native long cxxConstruct(int port);
private static native void cxxDestroy(long cxxThis);
// Works fine as a safety net, I suppose...
@Override
@Deprecated
protected void finalize() {
close();
}
}
在 C++ 方面:
#include "RFEthernetDetector.h"
JNIEXPORT jlong JNICALL Java_RFEthernetDetector_cxxConstruct(JNIEnv *, jclass, jint port) {
return reinterpret_cast<jlong>(new RFEthernetDetector(port));
}
JNIEXPORT void JNICALL Java_RFEthernetDetector_cxxDestroy(JNIEnv *, jclass, jlong thiz) {
delete reinterpret_cast<RFEthernetDetector*>(thiz);
// calling other methods is similar:
// pass the cxxThis to C++, cast it, and do something through it
}
如果 reinterpret_cast
一切让您感到不舒服,您可以选择保持 map
左右:
#include <map>
std::map<jlong, RFEthernetDetector> references;
JNIEXPORT jlong JNICALL Java_RFEthernetDetector_cxxConstruct(JNIEnv *, jclass, jint port) {
jlong next = 0;
auto it = references.begin();
for(; it != references.end() && it->first == next; it++) next++;
references.emplace_hint(it, next, port);
return next;
}
JNIEXPORT void JNICALL Java_RFEthernetDetector_cxxDestroy(JNIEnv *, jclass, jlong thiz) {
references.erase(thiz);
}
所以,我现在所做的基本上是将地址存储在我的 .java class 中作为 "long" 变量,然后使用 Init() 方法 return C++ 实例的地址为 jlong.
然后,当我需要在 class 的那个实例上调用某些东西时 - 我将地址作为附加参数传递,并像这样在 C 代码中进行转换(在测试 [=19= 上测试) ] / 自定义 .so):
//constructor wrapper
JNIEXPORT jlong JNICALL Java_Test_Greetings(JNIEnv *env, jobject thisObj, jint value)
{
Greetings *greetings = new Greetings(value);
return (jlong)greetings;
}
JNIEXPORT void JNICALL Java_Test_HelloWorld(JNIEnv *env, jobject thisObj, jlong reference)
{
Greetings *greetings;
greetings = (Greetings*)reference;
greetings->helloValue();
}
我不知道这是否是正确的方法,但它确实有效...如果有人告诉我我错在哪里,我将不胜感激。
我有一个编译好的库需要在项目中使用。简而言之,它是一个用于与特定硬件交互的库。我拥有的是 .a 和 .dll 库文件,分别用于 linux 和 windows,以及一堆 C++ .h headers 以及所有 public 函数和 class那里有描述。
问题是项目需要在 Java 中,所以我需要为这个库编写一个 JNI 包装器,老实说,我从来没有这样做过。不过没关系,我要好好学习了。
我在网上阅读了一堆文档,我弄清楚了传递变量、从本机代码创建 java objects 等
我想不通的是,如何使用 JNI 使用本机构造函数?我不知道这些构造函数的源代码是什么,我只有这样的headers:
namespace RFDevice {
class RFDEVICE_API RFEthernetDetector
{
public:
//-----------------------------------------------------------------------------
// FUNCTION RFEthernetDetector::RFEthernetDetector
/// \brief Default constructor of RFEthernetDetector object.
///
/// \return void : N/A
//-----------------------------------------------------------------------------
RFEthernetDetector();
RFEthernetDetector(const WORD wCustomPortNumber);
所以基本上如果我要用 C++ 编写我的程序(我不会),我会做类似的事情
RFEthernetDetector ethernetDetector = new RFEthernerDetector(somePort);
然后使用那个 object。但是...如何使用 JNI 在 Java 中执行此操作? 我不明白我应该如何为构造函数创建一个本机方法,它会从我的 .a 库调用构造函数,然后有一些方法来处理那个特定的 object?我知道如何从本机代码创建 java objects - 但问题是我没有关于 RFEthernetDetector class 内部结构的任何信息 - 只有其中的一些 public 字段和 public 方法。
而且我似乎无法在网上找到合适的文章来帮助我。我该怎么做?
更新:进一步说明。
我像这样创建一个 .java 包装器 class:
public class RFEthernetDetector
{
public RFEthernetDetector(int portNumber)
{
Init(portNumber);
}
public native void Init(int portNumber); // Void? Or what?
}
然后我用-h参数编译生成JNI.h文件:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class RFEthernetDetector */
#ifndef _Included_RFEthernetDetector
#define _Included_RFEthernetDetector
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: RFEthernetDetector
* Method: Init
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_RFEthernetDetector_Init
(JNIEnv *, jobject, jint);
#ifdef __cplusplus
}
#endif
#endif
然后我创建一个实现来调用我的 .a 库中的函数:
#include "RFEthernetDetector.h" // auto-generated JNI header
#include "RFEthernetDetector_native.h" // h file that comes with the library,
//contains definition of RFEthernetDetector class
/*
* Class: RFEthernetDetector
* Method: Init
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_RFEthernetDetector_Init(JNIEnv *env, jobject thisObj, jint value)
{
RFEthernetDetector *rfeDetector = new RFEthernetDetector(value); // constructor from the library
// now how do I access this new object from Java?
// if I need to later call rfDetector->doSomething() on that exact class instance?
}
您需要在 Java 中构建 native
class,然后 运行 javah
程序将构建 Java 预计。然后,您需要将 java 存根映射到 C++ 代码,以编译和分发该绑定库以及您的 java 程序。
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html
您需要构建一个 RFEthernetDetector
Java class,通过指针,拥有 RFEthernetDetector
C++ 方面。 This is no fun,但语言间胶水永远不会。
// In this design, the C++ object needs to be explicitly destroyed by calling
// close() on the Java side.
// I think that Eclipse, at least, is configured by default to complain
// if an AutoCloseable is never close()d.
public class RFEthernetDetector implements AutoCloseable {
private final long cxxThis; // using the "store pointers as longs" convention
private boolean closed = false;
public RFEthernetDetector(int port) {
cxxThis = cxxConstruct(port);
};
@Override
public void close() {
if(!closed) {
cxxDestroy(cxxThis);
closed = true;
}
}
private static native long cxxConstruct(int port);
private static native void cxxDestroy(long cxxThis);
// Works fine as a safety net, I suppose...
@Override
@Deprecated
protected void finalize() {
close();
}
}
在 C++ 方面:
#include "RFEthernetDetector.h"
JNIEXPORT jlong JNICALL Java_RFEthernetDetector_cxxConstruct(JNIEnv *, jclass, jint port) {
return reinterpret_cast<jlong>(new RFEthernetDetector(port));
}
JNIEXPORT void JNICALL Java_RFEthernetDetector_cxxDestroy(JNIEnv *, jclass, jlong thiz) {
delete reinterpret_cast<RFEthernetDetector*>(thiz);
// calling other methods is similar:
// pass the cxxThis to C++, cast it, and do something through it
}
如果 reinterpret_cast
一切让您感到不舒服,您可以选择保持 map
左右:
#include <map>
std::map<jlong, RFEthernetDetector> references;
JNIEXPORT jlong JNICALL Java_RFEthernetDetector_cxxConstruct(JNIEnv *, jclass, jint port) {
jlong next = 0;
auto it = references.begin();
for(; it != references.end() && it->first == next; it++) next++;
references.emplace_hint(it, next, port);
return next;
}
JNIEXPORT void JNICALL Java_RFEthernetDetector_cxxDestroy(JNIEnv *, jclass, jlong thiz) {
references.erase(thiz);
}
所以,我现在所做的基本上是将地址存储在我的 .java class 中作为 "long" 变量,然后使用 Init() 方法 return C++ 实例的地址为 jlong.
然后,当我需要在 class 的那个实例上调用某些东西时 - 我将地址作为附加参数传递,并像这样在 C 代码中进行转换(在测试 [=19= 上测试) ] / 自定义 .so):
//constructor wrapper
JNIEXPORT jlong JNICALL Java_Test_Greetings(JNIEnv *env, jobject thisObj, jint value)
{
Greetings *greetings = new Greetings(value);
return (jlong)greetings;
}
JNIEXPORT void JNICALL Java_Test_HelloWorld(JNIEnv *env, jobject thisObj, jlong reference)
{
Greetings *greetings;
greetings = (Greetings*)reference;
greetings->helloValue();
}
我不知道这是否是正确的方法,但它确实有效...如果有人告诉我我错在哪里,我将不胜感激。