2012-07-20 13 views
8

otrzymuje nagłówek jak:przekazać tablicę do zawiniętej funkcji jako wielkości wskaźnik + lub zakresu

#include <iostream> 
#include <algorithm> 
#include <iterator> 

inline void foo(const signed char *arr, size_t sz) { 
    std::copy_n(arr, sz, std::ostream_iterator<int>(std::cout, "\n")); 
} 

inline void bar(const signed char *begin, const signed char *end) { 
    std::copy(begin, end, std::ostream_iterator<int>(std::cout, "\n")); 
} 

(użyłem C++ 11 tutaj dla wygody, może to być albo C lub C++, jeśli zmienił implementacje)

Jak mogę zawinąć te funkcje, aby pobrać tylko tablicę po stronie Java i użyć (znanego) rozmiaru tablicy, aby zapewnić drugi parametr dla tych funkcji?

+0

Co powiesz na ręczne udostępnianie wrappera w Javie? To nie jest tak, że metody pobierające tablicę w Javie również nie mają parametrów 'int offset, int length' ... –

+0

@SamuelAudet - możesz to zrobić, ale twierdzę, że to nie jest dobrze zaprojektowany interfejs (duplikowanie informacji tylko dla zabawy). Problem polega jednak na tym, że jeśli masz 'byte []' będziesz musiał napisać typemap (przez większość czasu), aby przekonwertować go na 'signed char *' lub użyć '% array_class' i' for' pętla do zrobienia kopii mimo to. Oba są dość brzydkie. – Flexo

+0

@SamuelAudet - zaktualizowałem swoją odpowiedź za pomocą ręcznej metody pakowania. To dość brzydkie według mnie. – Flexo

Odpowiedz

12

Najważniejsze jest to, że aby zawrzeć jedną z tych funkcji, należy użyć multi-argument typemap.

Preambuła jest dość standardem dla SWIG. Kiedyś mój osobisty faworyt prgama automatycznie załadować biblioteki współdzielonej bez użytkownikowi interfejsu, która chciałaby wiedzieć:

%module test 

%{ 
#include "test.hh" 
%} 

%pragma(java) jniclasscode=%{ 
    static { 
    try { 
     System.loadLibrary("test"); 
    } catch (UnsatisfiedLinkError e) { 
     System.err.println("Native code library failed to load. \n" + e); 
     System.exit(1); 
    } 
    } 
%} 

Najpierw jednak trzeba użyć kilku Java typemaps pouczać SWIG używać byte[] jako typ zarówno części interfejsu Java - JNI i opakowanie, które je wywołuje. W pliku modułu generowania użyjemy JNI typu jbyteArray. Mijamy dane wejściowe bezpośrednio z interfejsu SWIG do generowanego przez nie JNI.

%typemap(jtype) (const signed char *arr, size_t sz) "byte[]" 
%typemap(jstype) (const signed char *arr, size_t sz) "byte[]" 
%typemap(jni) (const signed char *arr, size_t sz) "jbyteArray" 
%typemap(javain) (const signed char *arr, size_t sz) "$javainput" 

Kiedy to nastąpi możemy napisać typemap multi-argument:

%typemap(in,numinputs=1) (const signed char *arr, size_t sz) { 
    $1 = JCALL2(GetByteArrayElements, jenv, $input, NULL); 
    $2 = JCALL1(GetArrayLength, jenv, $input); 
} 

Zadaniem w typemap jest konwersja z czym mamy podane przez wywołanie JNI do tego, co realne funkcja naprawdę oczekuje jako dane wejściowe. Użyłem numinputs=1, aby wskazać, że dwa rzeczywiste argumenty funkcji przyjmują tylko jedno wejście po stronie Java, ale jest to i tak wartość domyślna, więc nie jest wymagane jawne stwierdzenie.

W typemap $1 jest pierwszym argumentem typemap, to znaczy pierwszy argument naszym funkcją jest w tym przypadku. Ustawiliśmy to, prosząc o wskaźnik do bazowego magazynu macierzy Java (która może, ale nie musi być, kopią). Ustawiliśmy $2, drugi argument typemap, aby był wielkością tablicy.

Makra tutaj: JCALLn upewnij się, że paczka typów może się kompilować zarówno z JNI C i C++. Rozszerza się do odpowiedniego wezwania do języka.

Musimy inny typemap oczyścić raz powrócił wywołanie prawdziwa funkcja:

%typemap(freearg) (const signed char *arr, size_t sz) { 
    // Or use 0 instead of ABORT to keep changes if it was a copy 
    JCALL3(ReleaseByteArrayElements, jenv, $input, $1, JNI_ABORT); 
} 

Wymaga ReleaseByteArrayElements powiedzieć JVM skończymy z tablicy. Potrzebny mu jest obiekt tablicy Java, z którego go uzyskaliśmy. Dodatkowo pobiera parametr, który wskazuje, czy zawartość powinna zostać skopiowana, a zmodyfikowany wskaźnik to pierwsza kopia. (Argument, który przekazaliśmy NULL jest opcjonalnym wskaźnikiem do jboolean, który wskazuje, czy dostaliśmy kopię).

Na drugim wariancie typemaps są zasadniczo podobne:

%typemap(in,numinputs=1) (const signed char *begin, const signed char *end) { 
    $1 = JCALL2(GetByteArrayElements, jenv, $input, NULL); 
    const size_t sz = JCALL1(GetArrayLength, jenv, $input); 
    $2 = $1 + sz; 
} 

%typemap(freearg) (const signed char *begin, const signed char *end) { 
    // Or use 0 instead of ABORT to keep changes if it was a copy 
    JCALL3(ReleaseByteArrayElements, jenv, $input, $1, JNI_ABORT); 
} 

%typemap(jtype) (const signed char *begin, const signed char *end) "byte[]" 
%typemap(jstype) (const signed char *begin, const signed char *end) "byte[]" 
%typemap(jni) (const signed char *begin, const signed char *end) "jbyteArray" 
%typemap(javain) (const signed char *begin, const signed char *end) "$javainput" 

Jedyną różnicą jest użycie zmiennej lokalnej sz obliczyć end arugment stosując wskaźnik begin.

Jedyną rzeczą do zrobienia jest, aby powiedzieć SWIG owinąć samego pliku nagłówka, używając typemaps my właśnie napisane:

%include "test.hh" 

testowałem obie te funkcje z:

public class run { 
    public static void main(String[] argv) { 
    byte[] arr = {0,1,2,3,4,5,6,7}; 
    System.out.println("Foo:"); 
    test.foo(arr); 
    System.out.println("Bar:"); 
    test.bar(arr); 
    } 
} 

Co działało zgodnie z oczekiwaniami.

Dla wygody udostępniłem pliki, których użyłem podczas pisania tego na my site. Każda linia każdego pliku w tym archiwum może zostać zrekonstruowana, po kolei podążając za tą odpowiedzią.


Dla porównania mogliśmy zrobić całą rzecz bez JNI nazywa, używając %pragma(java) modulecode wygenerować przeciążenie że używamy przekształcenia wejście (w czystej Javie) w postaci oczekiwanej przez rzeczywistych funkcji. Do tego pliku moduł byłby:

%module test 

%{ 
#include "test.hh" 
%} 

%include <carrays.i> 
%array_class(signed char, ByteArray); 

%pragma(java) modulecode = %{ 
    // Overload foo to take an array and do a copy for us: 
    public static void foo(byte[] array) { 
    ByteArray temp = new ByteArray(array.length); 
    for (int i = 0; i < array.length; ++i) { 
     temp.setitem(i, array[i]); 
    } 
    foo(temp.cast(), array.length); 
    // if foo can modify the input array we'll need to copy back to: 
    for (int i = 0; i < array.length; ++i) { 
     array[i] = temp.getitem(i); 
    } 
    } 

    // How do we even get a SWIGTYPE_p_signed_char for end for bar? 
    public static void bar(byte[] array) { 
    ByteArray temp = new ByteArray(array.length); 
    for (int i = 0; i < array.length; ++i) { 
     temp.setitem(i, array[i]); 
    } 
    bar(temp.cast(), make_end_ptr(temp.cast(), array.length)); 
    // if bar can modify the input array we'll need to copy back to: 
    for (int i = 0; i < array.length; ++i) { 
     array[i] = temp.getitem(i); 
    } 
    } 
%} 

// Private helper to make the 'end' pointer that bar expects 
%javamethodmodifiers make_end_ptr "private"; 
%inline { 
    signed char *make_end_ptr(signed char *begin, int sz) { 
    return begin+sz; 
    } 
} 

%include "test.hh" 

%pragma(java) jniclasscode=%{ 
    static { 
    try { 
     System.loadLibrary("test"); 
    } catch (UnsatisfiedLinkError e) { 
     System.err.println("Native code library failed to load. \n" + e); 
     System.exit(1); 
    } 
    } 
%} 

Poza oczywistymi (dwóch) egzemplarzach wymaganych, aby uzyskać dane do prawego typu (nie ma trywialny sposób, aby przejść od byte[] do SWIGTYPE_p_signed_char) iz powrotem to ma jeszcze jedną wadę - jest to specyficzne dla funkcji foo i bar, podczas gdy mapy typów, które napisaliśmy wcześniej, nie są specyficzne dla danej funkcji - będą stosowane wszędzie tam, gdzie pasują, nawet wiele razy na tej samej funkcji, jeśli masz funkcję, która wymaga dwóch zakresy lub dwie kombinacje wskaźników + długości. Jedną z zalet robienia tego w ten sposób jest to, że jeśli masz inne zapakowane funkcje, które dają ci SWIGTYPE_p_signed_char, to nadal będziesz mieć dostępne przeciążenia, jeśli chcesz. Nawet w przypadku, gdy masz ByteArray z %array_class, nadal nie możesz wykonać arytmetyki wskaźnika w Javie potrzebnej do wygenerowania dla ciebie end.

Przedstawiony pierwotny sposób zapewnia bardziej przejrzysty interfejs w Javie, z dodatkowymi zaletami polegającymi na tym, że nie wykonuje nadmiernych kopii i jest bardziej przydatny do ponownego wykorzystania.


Jeszcze innym alternatywnym podejściem do owijania byłoby napisać kilka %inline przeciążeń dla foo i bar:

%inline { 
    void foo(jbyteArray arr) { 
    // take arr and call JNI to convert for foo 
    } 
    void bar(jbyteArray arr) { 
    // ditto for bar 
    } 
} 

Są one przedstawiane jako przeciążeń w interfejsie Java, ale nadal są specyficzne dla modułu i dodatkowo wymagany JNI jest bardziej skomplikowany niż byłby w innym przypadku - musisz jakoś zorganizować, aby uzyskać jenv, który nie jest domyślnie dostępny. Opcje to powolne wywołanie, aby je uzyskać, lub szablon typograficzny numinputs=0, który automatycznie zapełnia parametr. Tak czy inaczej, wieloargumentowa typemapa wydaje się o wiele ładniejsza.

Powiązane problemy