使用Swig将Java对象传递给C ++ ...然后返回Java [英] Passing Java object to C++ using Swig... then back to Java

查看:127
本文介绍了使用Swig将Java对象传递给C ++ ...然后返回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:


  1. 一些C ++代码,通过%extend 尝试动态转换为 Director * 的内部人员(即SWIG主管heirarchy的一个基地)。这包含对原始Java类的jobject引用(如果存在)。所以我们可以简单地返回那个jboject,如果转换失败则返回NULL。

  2. 一些Java代码将返回我们的C ++代码的结果,或 this 如果不合适的话。然后,我们可以从我们的javadirectorin类型映射中注入调用,以允许从新代理到原始对象的升级。

  3. 另一种通过JNIEnv的简单类型映射形式的技巧将对象自动转换为#code>%extend 方法,因为它通常不能直接在那里访问,即使它可以像这样公开。

  1. Some C++ code, via %extend inside person that tries a dynamic cast to Director* (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.
  2. 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.
  3. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆