2012-07-20 9 views
8

Bei einem Header wie:Übergeben Sie ein Array an eine eingewickelt Funktion als Zeiger + Größe oder den Bereich

#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")); 
} 

(I verwendet, um dieses C++ 11 hier der Einfachheit halber könnte entweder C oder C++, wenn Sie die geänderte Implementierungen obwohl)

Wie kann ich diese Funktionen umschreiben, um nur ein Array auf der Java-Seite zu nehmen und die (bekannte) Größe des Arrays zu verwenden, um den zweiten Parameter für diese Funktionen bereitzustellen?

+0

Wie wäre es, nur eine Wrapper-Methode in Java manuell bereitzustellen? Es ist nicht wie Methoden, die ein Array in Java nehmen, auch nicht mit 'int offset, int length' Parametern sowie ... –

+0

@SamuelAudet - Sie könnten das tun, aber ich würde argumentieren, dass das nicht eine gut gestaltete Schnittstelle ist (Duplizieren von Informationen nur zum Spaß). Das Problem ist jedoch, dass wenn Sie 'byte []' haben, Sie eine Typenkarte schreiben müssen (die meiste Zeit), um sie in 'signed char *' umzuwandeln, oder verwenden Sie% array_class und ein 'for' Schleife, um trotzdem eine Kopie zu machen. Beide sind ziemlich hässlich. – Flexo

+0

@ SamuelAudet - Ich habe meine Antwort mit einer manuellen Wrapper-Methode aktualisiert. Es ist meiner Meinung nach ziemlich hässlich. – Flexo

Antwort

12

Der Kernpunkt davon ist, dass Sie eine dieser Funktionen verwenden möchten, die Sie eine multi-argument typemap verwenden möchten.

Die Präambel ist ziemlich Standard für SWIG. Früher habe ich meinen persönlichen Favoriten prgama automatisch ohne dass der Benutzer der Schnittstelle die gemeinsame Bibliothek geladen werden, um zu wissen:

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

Zuerst wenn Sie ein paar Java typemaps SWIG instruieren verwenden brauchen werden byte[] als Typ von beiden zu verwenden Teile der Java-Schnittstelle - die JNI und der Wrapper, der sie aufruft. In der Generate-Modul-Datei verwenden wir den JNI-Typ jbyteArray. Wir geben die Eingabe direkt von der SWIG-Schnittstelle an die JNI weiter, die sie generiert.

%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" 

Wenn dies geschehen ist wir ein Multi-Argument typemap schreiben:

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

Die Aufgabe der in typemap ist von zu konvertieren, was wir durch den JNI Aufruf gegeben sind, was die reale Funktion erwartet wirklich als Input. Ich habe numinputs=1 verwendet, um anzuzeigen, dass die beiden reellen Funktionsargumente nur eine Eingabe auf der Java-Seite haben, aber dies ist ohnehin der Standardwert, daher ist es nicht erforderlich, dies explizit anzugeben.

In diesem typemap $1 ist das erste Argument der typecap, d. H. Das erste Argument unserer Funktion in diesem Fall. Wir legen das fest, indem wir nach einem Zeiger auf den zugrunde liegenden Speicher des Java-Arrays fragen (was eine Kopie wirklich sein kann oder nicht). Wir setzen $2, das zweite typemap-Argument ist die Größe des Arrays.

Die JCALLn Makros hier stellen sicher, dass die typemap mit C und C++ JNI kompilieren kann. Es wird auf den entsprechenden Aufruf für die Sprache erweitert.

Wir brauchen eine andere typemap aufzuräumen, sobald die eigentliche Funktion Aufruf zurückgegeben hat:

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

Dies erfordert ReleaseByteArrayElements die JVM zu sagen, dass wir mit dem Array fertig sind. Es benötigt den Zeiger und das Java-Array-Objekt, von dem wir es erhalten haben. Außerdem wird ein Parameter benötigt, der angibt, ob der Inhalt zurückkopiert werden soll. iff sie wurden modifiziert und der Zeiger, den wir bekommen haben, war eine Kopie an erster Stelle. (Das Argument, das wir NULL übergeben haben, ist ein optionaler Zeiger auf eine jboolean, die anzeigt, ob wir eine Kopie erhalten haben).

für die zweite Variante sind die typemaps Wesentlichen ähnlich:

%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" 

Der einzige Unterschied ist die Verwendung der lokalen Variablen ist, die szend arugment unter Verwendung des begin Zeiger zu berechnen.

Das einzige, was noch zu tun SWIG zu sagen, die Header-Datei selbst zu wickeln, mit den typemaps wir gerade geschrieben haben:

%include "test.hh" 

Getestet habe ich diese beiden Funktionen mit:

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

Das hat wie erwartet funktioniert.

Der Bequemlichkeit halber habe ich die Dateien geteilt, die ich beim Schreiben dieses auf my site geschrieben habe. Jede Zeile jeder Datei in diesem Archiv kann rekonstruiert werden, indem diese Antwort der Reihe nach befolgt wird.


Als Referenz wir das Ganze ohne JNI getan haben könnte nennt, %pragma(java) modulecode eine Überlastung zu erzeugen, verwenden, die wir den Eingang umwandeln verwenden (in reinem Java) in die durch die realen Funktionen erwartet Form. Für die Modul Datei gewesen wäre:

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

Neben den offensichtlichen (zwei) Kopien erforderlich ist, um die Daten in die richtige Art zu bekommen (es gibt keinen trivialen Weg gehen von byte[] zu SWIGTYPE_p_signed_char) und zurück hat dies eine weiteren Nachteil - Es ist spezifisch für die Funktionen foo und bar, während die Typmaps, die wir zuvor geschrieben haben, nicht für eine bestimmte Funktion spezifisch sind - sie werden überall dort angewendet, wo sie übereinstimmen, sogar mehrere Male auf derselben Funktion, wenn Sie eine Funktion haben, die zwei übernimmt Bereiche oder zwei Zeiger + Längenkombinationen. Der einzige Vorteil ist, dass wenn Sie andere Funktionen haben, die Ihnen SWIGTYPE_p_signed_char zurückgeben, dann haben Sie immer noch die Überladungen zur Verfügung, wenn Sie es wünschen. Selbst in dem Fall, in dem Sie eine ByteArray aus der %array_class haben, können Sie immer noch nicht die Zeigerarithmetik in Java verwenden, um end für Sie zu generieren.

Der ursprüngliche Weg zeigt eine sauberere Schnittstelle in Java, mit den zusätzlichen Vorteilen, keine übermäßigen Kopien zu machen und wiederverwendbar zu sein.

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

Diese werden präsentiert als Überlastungen in der Java-Schnittstelle, aber sie sind immer noch das Modul spezifisch:


Noch ein weiterer alternativer Ansatz zur Verpackung für foo und bar ein paar %inline Überlastungen zu schreiben wäre und außerdem ist das JNI, das hier benötigt wird, komplexer als es sonst sein müsste - Sie müssen arrangieren, um irgendwie auf jenv zuzugreifen, auf das standardmäßig nicht zugegriffen werden kann. Die Optionen sind ein langsamer Aufruf, um es zu erhalten, oder eine numinputs=0 typemap, die den Parameter automatisch ausfüllt. In beiden Fällen scheint die Multi-Argument-Typkarte viel schöner zu sein.

Verwandte Themen