使用 Swig 将 Java 对象传递给 C++...然后返回 Java
Passing Java object to C++ using Swig... then back to Java
当使用 Java、C++、Swig 和 Swig 的控制器时,我可以将继承 C++ class 的 Java 对象传递给 C++。这很好用。
现在,当我将相同的 Java 对象从 C++ 代码传递回 Java 时,Swig 创建了一个 new Java 对象包装 C++ 指针。这样做的问题是新对象与旧对象的类型不同。我在 Java 中继承了 C++ class,我需要那个 Java 对象。
我为什么要这样做?我在 Java 中有一个资源池,C++ 代码正在检查这些资源,然后将它们返回到池中。
以下是 SSCE:
这是检查资源和 returns 它的 C++ 代码:
// c_backend.cpp
#include "c_backend.h"
#include <stdio.h>
void Server::doSomething( JobPool *jp ) {
printf("In doSomthing\n");
Person *person = jp->hireSomeone();
person->doSomeWorkForMe(3);
jp->returnToJobPool(person);
printf("exiting doSomthing\n");
}
这是覆盖 C++ classes 的 Java 代码:
//JavaFrontend.java
import java.util.List;
import java.util.ArrayList;
public class JavaFrontend {
static {
System.loadLibrary("CBackend");
}
public static void main( String[] args ) {
JobPool jobPool = new JobPoolImpl();
new Server().doSomething(jobPool);
}
public static class JobPoolImpl extends JobPool {
private List<PersonImpl> people = new ArrayList<>();
public Person hireSomeone() {
if ( people.size() > 0 ) {
Person person = people.get(0);
people.remove(person);
return person;
} else {
System.out.println("returning new PersonImpl");
return new PersonImpl();
}
}
public void returnToJobPool(Person person) {
people.add((PersonImpl)person);
}
}
public static class PersonImpl extends Person {
public void doSomeWorkForMe(int i) {
System.out.println("Java working for me: "+i);
}
}
}
这是 Swig 接口文件:
//c_backend.i
%module(directors="1") c_backend
%{
#include "c_backend.h"
%}
%feature("director") Person;
%feature("director") JobPool;
%include "c_backend.h"
最后,带有基础 classes 的 C++ 头文件,然后是编译所有内容的 Makefile:
// c_backend.h
#ifndef C_BACKEND_H
#define C_BACKEND_H
#include <stdio.h>
class Person {
public:
virtual ~Person() {}
virtual void doSomeWorkForMe(int i) {
printf("in C++ doSomeWorkForMe %i\n",i);
}
};
class JobPool {
public:
virtual ~JobPool() {}
virtual Person *hireSomeone() {
printf("in C++ hireSomeone\n");
return NULL;
}
virtual void returnToJobPool(Person *person) {
printf("in C++ returnToJobPool\n");
}
};
class Server {
public:
void doSomething( JobPool * );
};
#endif
生成文件:
# Makefile
JAVA_INCLUDE=-I/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/include/darwin
all:
c++ -c c_backend.cpp
swig -java -c++ $(JAVA_INCLUDE) c_backend.i
c++ $(JAVA_INCLUDE) -c c_backend_wrap.cxx
c++ -dynamiclib -o libCBackend.jnilib *.o -framework JavaVM
javac *.java
clean:
rm -rf *.class *.o *_wrap.cxx *_wrap.h Server.java SWIGTYPE*.java c_backend*.java JobPool.java Person.java
这是 swig 代码的一个片段,它创建了新的 Java 对象来替换我原来的 Java 对象:
public static void SwigDirector_JobPool_returnToJobPool(JobPool jself, long person) {
jself.returnToJobPool((person == 0) ? null : new Person(person, false));
}
如何在不依赖 Java 内部维护 HashMap
的情况下完成这项工作?
您可以通过您喜欢的约束(即不维护弱引用映射)做一些工作来做到这一点。事实上,结果也比我最初预期的要少。我将首先讨论解决方案,然后添加一些关于我第一次尝试执行此操作的方式的讨论,该方式变得太笨拙而无法完成。
工作解决方案的高级视图是我们添加了三件事:
- 一些 C++ 代码,通过
%extend
尝试动态转换为 Director*
的内部人员(即 SWIG 主管层次结构的一个基础)。这包含对原始 Java class 的 jobject 引用,如果存在的话。所以我们可以简单地 return 那个 jboject,或者如果转换失败则为 NULL。
- 一些 Java 代码将 return 我们的 C++ 代码的结果,或者
this
如果不合适。然后我们可以从我们的 javadirectorin 类型映射中注入调用,以允许从新代理到原始对象的 "upgrade" 发生。
- 另一个技巧是将 JNIEnv 对象自动传递到 #1 的
%extend
方法中,因为它通常不能在那里直接访问,即使它可以像这样公开。
所以你的界面文件变成:
%module(directors="1") c_backend
%{
#include "c_backend.h"
#include <iostream>
%}
%feature("director") Person;
%feature("director") JobPool;
// Call our extra Java code to figure out if this was really a Java object to begin with
%typemap(javadirectorin) Person * "$jniinput == 0 ? null : new $*javaclassname($jniinput, false).swigFindRealImpl()"
// Pass jenv into our %extend code
%typemap(in,numinputs=0) JNIEnv *jenv " = jenv;"
%extend Person {
// return the underlying Java object if this is a Director, or null otherwise
jobject swigOriginalObject(JNIEnv *jenv) {
Swig::Director *dir = dynamic_cast<Swig::Director*>($self);
std::cerr << "Dynamic_cast: " << dir << "\n";
if (dir) {
return dir->swig_get_self(jenv);
}
return NULL;
}
}
%typemap(javacode) Person %{
// check if the C++ code finds an object and just return ourselves if it doesn't
public Person swigFindRealImpl() {
Object o = swigOriginalObject();
return o != null ? ($javaclassname)o : this;
}
%}
%include "c_backend.h"
我向 stderr 发送了一条消息,只是为了证明它确实有效。
在实际代码中,您可能想要添加一个 javaout 类型映射来反映 javadirectorin 类型映射的作用。您也可以在宏中将其整齐地修饰,因为编写所有代码都是为了避免采用固定的类型名称。
如果我不得不猜测为什么 SWIG 默认情况下不做类似的事情,那几乎可以肯定是因为这将强制使用 RTTI,但过去流行将 -fno-rtti
传递给您的编译器"for performance",因此许多代码库都尽量避免假设可以依赖动态转换。
如果您只关心解决方案,请立即停止阅读。然而,这里以参考的方式包含了我最终放弃的最初方法。它开始是这样的:
//c_backend.i
%module(directors="1") c_backend
%{
#include "c_backend.h"
%}
%feature("director") Person;
%feature("director") JobPool;
%typemap(jtype) Person * "Object"
%typemap(jnitype) Person * "jobject"
%typemap(javadirectorin) Person * "$jniinput instanceof $*javaclassname ? ($*javaclassname)$jniinput : new $*javaclassname((Long)$jniinput), false)"
%typemap(directorin,descriptor="L/java/lang/Object;") Person * {
SwigDirector__basetype *dir = dynamic_cast<SwigDirector__basetype*>();
if (!dir) {
jclass cls = JCALL1(FindClass, jenv, "java/lang/Long");
jmid ctor = JCALL3(GetMethodID, jenv, cls, "<init>", "J(V)");
$input = JCALL3(NewObject, jenv, cls, ctor, reinterpret_cast<jlong>());
}
else {
$input = dir->swig_get_self(jenv);
}
}
%include "c_backend.h"
从包装代码一直将 Person
类型更改为 return 和 Object
/jobject
。我的计划是它要么是 Person
的实例,要么是 java.lang.Long
的实例,我们将根据 instanceof 比较动态决定构造什么。
虽然这个问题是 jnitype 和 jtype tyemaps 在它们使用的上下文之间没有区别。所以 Person
的任何其他用法(例如构造函数,函数输入,director out,其他位导演代码)都需要更改以使用 Long
对象而不是 long
原始类型。即使通过在变量名上匹配类型映射,它仍然无法避免过度匹配。 (试试看,注意 long 在 c_backendJNI.java 里面变成 Person 的地方)。因此,就要求对类型映射进行非常明确的命名而言,这本来是丑陋的,并且仍然超过了我想要的,因此需要对其他类型映射进行更多侵入性更改。
当使用 Java、C++、Swig 和 Swig 的控制器时,我可以将继承 C++ class 的 Java 对象传递给 C++。这很好用。
现在,当我将相同的 Java 对象从 C++ 代码传递回 Java 时,Swig 创建了一个 new Java 对象包装 C++ 指针。这样做的问题是新对象与旧对象的类型不同。我在 Java 中继承了 C++ class,我需要那个 Java 对象。
我为什么要这样做?我在 Java 中有一个资源池,C++ 代码正在检查这些资源,然后将它们返回到池中。
以下是 SSCE:
这是检查资源和 returns 它的 C++ 代码:
// c_backend.cpp
#include "c_backend.h"
#include <stdio.h>
void Server::doSomething( JobPool *jp ) {
printf("In doSomthing\n");
Person *person = jp->hireSomeone();
person->doSomeWorkForMe(3);
jp->returnToJobPool(person);
printf("exiting doSomthing\n");
}
这是覆盖 C++ classes 的 Java 代码:
//JavaFrontend.java
import java.util.List;
import java.util.ArrayList;
public class JavaFrontend {
static {
System.loadLibrary("CBackend");
}
public static void main( String[] args ) {
JobPool jobPool = new JobPoolImpl();
new Server().doSomething(jobPool);
}
public static class JobPoolImpl extends JobPool {
private List<PersonImpl> people = new ArrayList<>();
public Person hireSomeone() {
if ( people.size() > 0 ) {
Person person = people.get(0);
people.remove(person);
return person;
} else {
System.out.println("returning new PersonImpl");
return new PersonImpl();
}
}
public void returnToJobPool(Person person) {
people.add((PersonImpl)person);
}
}
public static class PersonImpl extends Person {
public void doSomeWorkForMe(int i) {
System.out.println("Java working for me: "+i);
}
}
}
这是 Swig 接口文件:
//c_backend.i
%module(directors="1") c_backend
%{
#include "c_backend.h"
%}
%feature("director") Person;
%feature("director") JobPool;
%include "c_backend.h"
最后,带有基础 classes 的 C++ 头文件,然后是编译所有内容的 Makefile:
// c_backend.h
#ifndef C_BACKEND_H
#define C_BACKEND_H
#include <stdio.h>
class Person {
public:
virtual ~Person() {}
virtual void doSomeWorkForMe(int i) {
printf("in C++ doSomeWorkForMe %i\n",i);
}
};
class JobPool {
public:
virtual ~JobPool() {}
virtual Person *hireSomeone() {
printf("in C++ hireSomeone\n");
return NULL;
}
virtual void returnToJobPool(Person *person) {
printf("in C++ returnToJobPool\n");
}
};
class Server {
public:
void doSomething( JobPool * );
};
#endif
生成文件:
# Makefile
JAVA_INCLUDE=-I/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/include/darwin
all:
c++ -c c_backend.cpp
swig -java -c++ $(JAVA_INCLUDE) c_backend.i
c++ $(JAVA_INCLUDE) -c c_backend_wrap.cxx
c++ -dynamiclib -o libCBackend.jnilib *.o -framework JavaVM
javac *.java
clean:
rm -rf *.class *.o *_wrap.cxx *_wrap.h Server.java SWIGTYPE*.java c_backend*.java JobPool.java Person.java
这是 swig 代码的一个片段,它创建了新的 Java 对象来替换我原来的 Java 对象:
public static void SwigDirector_JobPool_returnToJobPool(JobPool jself, long person) {
jself.returnToJobPool((person == 0) ? null : new Person(person, false));
}
如何在不依赖 Java 内部维护 HashMap
的情况下完成这项工作?
您可以通过您喜欢的约束(即不维护弱引用映射)做一些工作来做到这一点。事实上,结果也比我最初预期的要少。我将首先讨论解决方案,然后添加一些关于我第一次尝试执行此操作的方式的讨论,该方式变得太笨拙而无法完成。
工作解决方案的高级视图是我们添加了三件事:
- 一些 C++ 代码,通过
%extend
尝试动态转换为Director*
的内部人员(即 SWIG 主管层次结构的一个基础)。这包含对原始 Java class 的 jobject 引用,如果存在的话。所以我们可以简单地 return 那个 jboject,或者如果转换失败则为 NULL。 - 一些 Java 代码将 return 我们的 C++ 代码的结果,或者
this
如果不合适。然后我们可以从我们的 javadirectorin 类型映射中注入调用,以允许从新代理到原始对象的 "upgrade" 发生。 - 另一个技巧是将 JNIEnv 对象自动传递到 #1 的
%extend
方法中,因为它通常不能在那里直接访问,即使它可以像这样公开。
所以你的界面文件变成:
%module(directors="1") c_backend
%{
#include "c_backend.h"
#include <iostream>
%}
%feature("director") Person;
%feature("director") JobPool;
// Call our extra Java code to figure out if this was really a Java object to begin with
%typemap(javadirectorin) Person * "$jniinput == 0 ? null : new $*javaclassname($jniinput, false).swigFindRealImpl()"
// Pass jenv into our %extend code
%typemap(in,numinputs=0) JNIEnv *jenv " = jenv;"
%extend Person {
// return the underlying Java object if this is a Director, or null otherwise
jobject swigOriginalObject(JNIEnv *jenv) {
Swig::Director *dir = dynamic_cast<Swig::Director*>($self);
std::cerr << "Dynamic_cast: " << dir << "\n";
if (dir) {
return dir->swig_get_self(jenv);
}
return NULL;
}
}
%typemap(javacode) Person %{
// check if the C++ code finds an object and just return ourselves if it doesn't
public Person swigFindRealImpl() {
Object o = swigOriginalObject();
return o != null ? ($javaclassname)o : this;
}
%}
%include "c_backend.h"
我向 stderr 发送了一条消息,只是为了证明它确实有效。
在实际代码中,您可能想要添加一个 javaout 类型映射来反映 javadirectorin 类型映射的作用。您也可以在宏中将其整齐地修饰,因为编写所有代码都是为了避免采用固定的类型名称。
如果我不得不猜测为什么 SWIG 默认情况下不做类似的事情,那几乎可以肯定是因为这将强制使用 RTTI,但过去流行将 -fno-rtti
传递给您的编译器"for performance",因此许多代码库都尽量避免假设可以依赖动态转换。
如果您只关心解决方案,请立即停止阅读。然而,这里以参考的方式包含了我最终放弃的最初方法。它开始是这样的:
//c_backend.i
%module(directors="1") c_backend
%{
#include "c_backend.h"
%}
%feature("director") Person;
%feature("director") JobPool;
%typemap(jtype) Person * "Object"
%typemap(jnitype) Person * "jobject"
%typemap(javadirectorin) Person * "$jniinput instanceof $*javaclassname ? ($*javaclassname)$jniinput : new $*javaclassname((Long)$jniinput), false)"
%typemap(directorin,descriptor="L/java/lang/Object;") Person * {
SwigDirector__basetype *dir = dynamic_cast<SwigDirector__basetype*>();
if (!dir) {
jclass cls = JCALL1(FindClass, jenv, "java/lang/Long");
jmid ctor = JCALL3(GetMethodID, jenv, cls, "<init>", "J(V)");
$input = JCALL3(NewObject, jenv, cls, ctor, reinterpret_cast<jlong>());
}
else {
$input = dir->swig_get_self(jenv);
}
}
%include "c_backend.h"
从包装代码一直将 Person
类型更改为 return 和 Object
/jobject
。我的计划是它要么是 Person
的实例,要么是 java.lang.Long
的实例,我们将根据 instanceof 比较动态决定构造什么。
虽然这个问题是 jnitype 和 jtype tyemaps 在它们使用的上下文之间没有区别。所以 Person
的任何其他用法(例如构造函数,函数输入,director out,其他位导演代码)都需要更改以使用 Long
对象而不是 long
原始类型。即使通过在变量名上匹配类型映射,它仍然无法避免过度匹配。 (试试看,注意 long 在 c_backendJNI.java 里面变成 Person 的地方)。因此,就要求对类型映射进行非常明确的命名而言,这本来是丑陋的,并且仍然超过了我想要的,因此需要对其他类型映射进行更多侵入性更改。