使用Swig将Java对象传递给C ++ ...然后返回Java

时间:2017-02-20 16:03:36

标签: java c++ swig

使用Java,C ++,Swig和Swig的控制器时,我可以将继承C ++类的Java对象传递给C ++。这很有效。

现在,当我从C ++代码将同一个Java对象传递回Java时,Swig创建了一个 new Java对象来包装C ++指针。这个问题是新对象与旧对象的类型不同。我在Java中继承了C ++类,我需要那个Java对象。

为什么我要这样做?我有一个Java资源池,C ++代码检查这些资源然后将它们返回池中。

以下是SSCE:

这里是检出资源并将其返回的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 ++类的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"

最后,带有基类的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:

# 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的情况下完成这项工作?

1 个答案:

答案 0 :(得分:2)

你可以通过一点点工作来实现这一点,使用你喜欢的约束(即不保持弱引用的映射)。事实证明,事实上我的工作量也比我原先预期的要少。我将首先讨论解决方案,然后在我第一次尝试这样做的方式上添加一些讨论,这些讨论变得太难以完成。

工作解决方案的高级视图是我们添加了三件事:

  1. 一些C ++代码,通过%extend内部人员尝试动态转换为Director*(即SWIG导演层次结构的一个基础)。这包含对原始Java类的jobject引用(如果存在)。所以我们可以简单地返回那个jboject,如果转换失败则返回NULL。
  2. 某些Java代码将返回我们的C ++代码的结果,如果不合适则返回this。然后,我们可以从我们的javadirectorin类型映射中注入调用,以允许从新代理到原始对象的“升级”。
  3. 另一个琐碎类型映射形式的技巧,它将JNIEnv对象自动传递到#1的%extend方法,因为它通常不能直接在那里访问,即使它可能像这样暴露。
  4. 所以你的界面文件就变成了:

    %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发送了一条消息,证明它确实有效。

    在实际代码中,您可能希望添加一个javaout类型映射,它反映了javadirectorin类型映射的功能。你可以在宏中整齐地装扮它,因为所有的代码都是为了避免假设一个固定的类型名称而写的。

    如果我不得不猜测为什么SWIG默认情况下不会做那样的事情,那几乎可以肯定是因为这会强制使用RTTI,但过去很快就会把-fno-rtti传递给你的编译器“性能“,所以很多代码库试图避免假设可以依赖动态强制转换。

    如果您关心的是解决方案,请立即停止阅读。然而,这里作为参考包括我最初放弃的原始方法。它开头是这样的:

    //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类型,从包装器代码一直返回Object / jobject。我的计划是它可以是Personjava.lang.Long的实例,我们会根据比较实例动态决定构建什么。

    这个问题是jnitype和jtype tyemaps没有区分它们被使用的上下文。所以Person的任何其他用法(例如构造函数,函数输入,导出器,导演的其他部分)代码)所有需要更改为使用Long对象而不是long基元类型。即使通过匹配变量名称上的类型映射,它仍然无法避免过度匹配。 (尝试一下,注意c_backendJNI.java里面长的人变成的地方)。因此,在要求非常明确地命名字体图并且仍然具有超出我想要的范围并且因此需要对其他类型映射进行更多侵入性更改时,它将是丑陋的。