2017-02-20 86 views
1

當使用Java,C++,痛飲,並痛飲的董事,我可以通過繼承一個C++類C++的Java對象。這很好。傳遞Java對象到C++使用痛飲......然後回Java

現在,當我傳遞相同的Java從C++代碼對象回Java,痛飲創建一個新 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"); 
} 

這裏的Java代碼覆蓋C++類:

//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); 
     } 
    } 
} 

這裏的痛飲接口文件:

//c_backend.i 
%module(directors="1") c_backend 

%{ 
#include "c_backend.h" 
%} 

%feature("director") Person; 
%feature("director") JobPool; 

%include "c_backend.h" 

最後,與基類,然後生成文件的C++頭文件,編譯這一切:

// 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 

下面是從用於創建所述痛飲一個代碼段新的Java對象替換我原來的Java對象:

public static void SwigDirector_JobPool_returnToJobPool(JobPool jself, long person) { 
    jself.returnToJobPool((person == 0) ? null : new Person(person, false)); 
} 

我怎樣才能使這項工作不依賴於保持HashMap裏面的Java?

+0

我想我在這裏回答基本上相同的問題:http://stackoverflow.com/questions/9817516/swig-java-retaining-class-information-of-the-objects-bouncing-from-c - 這樣做幫幫我?如果不是的話,我可以寫出一些說明任何差異的東西。 – Flexo

+0

事實上,這是同樣的問題,解決方案將適用於我的問題。雖然我不喜歡答案(即使它有效)。我希望Swig直接支持的東西......事實上......我希望Swig在創建director實例時將存儲Java對象,並在C++對象傳遞給Java時檢測並檢索它。這是可能的,而且很簡單。沒有散​​列。 但是,唉,目前似乎有必要。 – Jason

+1

[SWIG Java保留從C++彈出的對象的類信息]的重複(http://stackoverflow.com/questions/9817516/swig-java-retaining-class-information-of-the-objects-bouncing-from- c) – Jason

回答

2

你可以做到這一點,withing一點點的工作,你贊成限制(即不保持地圖弱引用的)。事實證明,我的工作比我原先預期的要少。我會先通過解決方案進行討論,然後再討論一下我第一次嘗試這樣做的方式,這個方法太難以完成了。

工作方案的高層次的看法是,我們已經增加了三件事情:

  1. 一些C++代碼,通過%extend內試圖一個動態轉換到痛飲導演層次結構的Director*(即一個基本的人)。如果存在的話,它會保存對原始Java類的jobject引用。所以我們可以簡單地返回jboject,如果轉換失敗,則返回NULL。
  2. 一些Java代碼,將返回我們的C++代碼,或者this如果沒有合適的任何結果。然後我們可以注入來自我們的javadirectorin類型映射的調用,以允許從新代理到原始對象的「升級」發生。
  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 "$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" 

我在消息拋出,只是標準錯誤,證明它確實幹過。

在真實代碼中,您可能希望添加一個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基本類型。即使通過匹配變量名稱上的類型映射,它仍然不能避免過度匹配。 (嘗試一下,注意long變成c_backendJNI.java中Person的地方)。所以在要求非常明確的類型映射命名方面它會很醜陋,並且仍然超出了我想要的,因此需要對其他類型映射進行更多侵入式更改。

+0

不錯!並感謝所有的工作! – Jason

+0

不錯,但我不同意你關於禁用RTTI的看法。 RTTI與大量模板元編程代碼不能很好地交互,因爲在所有實例中都會產生RTTI開銷,而沒有RTTI,它們將從二進制中消失。這可能很容易達到許多兆無用的RTTI數據。所以禁用它是有正當理由的。 – enobayram

+0

我認爲反對rtti的案例常常根據民間文學藝術而不是實際的基準進行誇大。在很多情況下,Ram很便宜,「多MB」仍然是花生,除了嵌入式以外,即使在某些移動設備上。如果您只動態投射一種或兩種類型,實際影響可能只是工作集中的一兩個額外頁面。因此,假設通過慢速鏈接下載不在關鍵路徑上,我敢打賭,一個基準測試顯示了許多其他的開銷,這些開銷完全超過了它。我不喜歡的是沒有證據的假設,而且我看到的代碼庫似乎正在減少。 – Flexo