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

您可以通过您喜欢的约束(即不维护弱引用映射)做一些工作来做到这一点。事实上,结果也比我最初预期的要少。我将首先讨论解决方案,然后添加一些关于我第一次尝试执行此操作的方式的讨论,该方式变得太笨拙而无法完成。

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

  1. 一些 C++ 代码,通过 %extend 尝试动态转换为 Director* 的内部人员(即 SWIG 主管层次结构的一个基础)。这包含对原始 Java class 的 jobject 引用,如果存在的话。所以我们可以简单地 return 那个 jboject,或者如果转换失败则为 NULL。
  2. 一些 Java 代码将 return 我们的 C++ 代码的结果,或者 this 如果不合适。然后我们可以从我们的 javadirectorin 类型映射中注入调用,以允许从新代理到原始对象的 "upgrade" 发生。
  3. 另一个技巧是将 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 的地方)。因此,就要求对类型映射进行非常明确的命名而言,这本来是丑陋的,并且仍然超过了我想要的,因此需要对其他类型映射进行更多侵入性更改。