使用Swig将Java对象传递给C ++ ...然后返回Java [英] Passing Java object to C++ using Swig... then back to Java
问题描述
使用Java,C ++,Swig和Swig的导演时,我可以将继承C ++类的Java对象传递给C ++。这很好用。
When using Java, C++, Swig, and Swig's directors I can pass a Java object that inherits a C++ class to C++. This works great.
现在,当我从C ++代码将同一个Java对象传回Java时,Swig创建了一个 new Java对象包装C ++指针。这个问题是新对象与旧对象的类型不同。我继承了Java中的C ++类,我需要那个Java对象。
Now, when I pass that same Java object back to Java from the C++ code, Swig creates a new Java object to wrap the C++ pointer. The problem with this is that the new object does not have the same type as the old object. I inherited the C++ class in Java and I need that Java object back.
为什么我要这样做?我有一个Java资源池,C ++代码检查这些资源然后将它们返回池中。
Why do I want to do this? I have a pool of resources in Java and the C++ code is checking out these resources then returning them to the pool.
后续是SSCE:
这是检查资源并返回它的C ++代码:
Here's the C++ code that checks out the resource and returns it:
// 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 ++类的Java代码:
Here's the Java code that overrides the C++ classes:
//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界面文件:
Here's the Swig interface file:
//c_backend.i
%module(directors="1") c_backend
%{
#include "c_backend.h"
%}
%feature("director") Person;
%feature("director") JobPool;
%include "c_backend.h"
最后,C ++标题带有基类的文件,然后是一个编译它的Makefile:
And finally, the C++ header file with the base classes and then a Makefile that compiles it all:
// 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:
# 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对象:
Here's a snippet from the swig code that creates the new Java object replacing my original Java object:
public static void SwigDirector_JobPool_returnToJobPool(JobPool jself, long person) {
jself.returnToJobPool((person == 0) ? null : new Person(person, false));
}
如何在不依赖维持 Java中的HashMap
?
推荐答案
你可以这样做,不受你喜欢的约束(即不是用一点点工作维护一个弱引用的地图。事实证明,事实上我的工作量也比我原先预期的要少。我将首先讨论解决方案,然后在我第一次尝试这样做的方式上添加一些讨论,这些讨论变得太难以完成。
You can do this, withing the constraints you favour (i.e. not maintaining a map of weak references) with a little work. It turns out in fact to be less work than I originally expected too. I'll talk through the solution first and then add some discussion on the way I first tried to do this that became too unwieldy to complete.
高级视图工作解决方案是我们添加了三件事:
The high level view of the working solution is that we have three things added:
- 一些C ++代码,通过
%extend
尝试动态转换为Director *
的内部人员(即SWIG主管heirarchy的一个基地)。这包含对原始Java类的jobject引用(如果存在)。所以我们可以简单地返回那个jboject,如果转换失败则返回NULL。 - 一些Java代码将返回我们的C ++代码的结果,或
this
如果不合适的话。然后,我们可以从我们的javadirectorin类型映射中注入调用,以允许从新代理到原始对象的升级。 - 另一种通过JNIEnv的简单类型映射形式的技巧将对象自动转换为#code>%extend 方法,因为它通常不能直接在那里访问,即使它可以像这样公开。
- Some C++ code, via
%extend
inside person that tries a dynamic cast toDirector*
(i.e. one base of the SWIG director heirarchy). This holds a jobject reference to the original Java class, if one exists. So we can trivially return either that jboject, or NULL if the cast fails. - Some Java code that will return either the result of our C++ code, or
this
if not appropriate. We can then inject call that from witihin our javadirectorin typemap to allow an "upgrade" from new proxy to original object to occur. - Another trick in the form of a trivial typemap that passes the JNIEnv object into the
%extend
method of #1 automatically because it isn't normally accessible there directly, even though it could be exposed like this.
所以你的界面文件就变成了:
So your interface file then becomes:
%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 "$1 = 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发送了一条消息,证明它确实有效。
I threw in a message to stderr just to prove that it really had worked.
在实际代码中你可能想要添加一个反映javadirectorin的javaout类型映射typemap也是如此。你也许可以在宏中整齐地装扮它,因为编写所有代码是为了避免假设一个固定的类型名称。
In real code you'd probably want to add a javaout typemap that mirrors what the javadirectorin typemap does as well. You could probably dress it all up neatly inside a macro too because all the code is written to avoid assuming an fixed type names.
如果我不得不猜测为什么选择SWIG默认情况下它不会做那样的事情几乎可以肯定,因为它会强制使用RTTI,但过去很快就会将 -fno-rtti
传递给你的编译器性能,所以很多代码库试图避免假设可以依赖动态强制转换。
If I had to guess as to why SWIG doesn't do something like that by default it's almost certainly because that would mandate use of RTTI, but it used to be trendy to pass -fno-rtti
into your compiler "for performance", so lots of code bases try to avoid assuming dynamic casts can be relied upon.
如果你关心的只是是一个解决方案现在停止阅读。但是这里以参考的方式包含了我最初放弃的原始方法。它开头是这样的:
If all you care about is a solution stop reading now. However included here by way of reference is my original approach to this which I ultimately abandoned. It started out like this:
//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_$1_basetype *dir = dynamic_cast<SwigDirector_$1_basetype*>($1);
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>($1));
}
else {
$input = dir->swig_get_self(jenv);
}
}
%include "c_backend.h"
这改变了 Person
类型以返回对象
/ jobject
从包装器代码一直到来。我的计划是它将是 Person
或 java.lang.Long
的实例,我们将动态决定基于比较实例构建什么。
Which changed the Person
types to return an Object
/jobject
all the way through from the wrapper code. My plan was that it would either be an instance of Person
or java.lang.Long
and we'd dynamically decide what to construct based on the instanceof comparison.
这个问题是jnitype和jtype tyemaps没有区分它们被使用的上下文。所以 Person <的任何其他用法/ code>(例如构造函数,函数输入,导演输出,导演代码的其他位)都需要更改为使用
Long
对象而不是 long
原始类型。即使通过匹配变量名称上的类型映射,它仍然无法避免过度匹配。 (尝试一下,注意c_backendJNI.java里面的人长的地方)。因此,在要求非常明确的类型映射命名方面会很难看,并且仍然会超出我想要的范围,因此需要对其他类型映射进行更具侵入性的更改。
The problem with this though is that jnitype and jtype tyemaps make no distinction between the context they get used in. So any other usage of Person
(e.g. constructors, function inputs, director out, other bits of the director code) all needed to be changed to work with a Long
object instead of a long
primitive type. Even by matching typemaps on the variable names it still didn't avoid the overmatching. (Try it and note the places where long becomes Person inside c_backendJNI.java). So it would have been ugly in terms of requiring very explicit naming of typemaps and still have overmatched what I wanted and thus required more intrusive changes to other typemaps.
这篇关于使用Swig将Java对象传递给C ++ ...然后返回Java的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!