2016-05-16 7 views
2

Für ein kleines Java-Projekt musste ich mit dem in C geschriebenen Code interagieren, um die Dinge einfach zu machen (ich bin leider kein C/C++ Programmierer ..) entschied ich den Schluck benutzen.SWIG bekomme returntype von String als String Array in Java

Der generierte Wrapper-Code scheint zu funktionieren; Wenn ich jedoch eine Funktion aufruft, die mir eine NULL-getrennte Liste von Strings geben soll (das ist es, was die C-Funktion zurückgeben soll, wenn ich mich nicht irre), gibt der eingepackte Code nur den ersten String-Wert des erwarteten zurück Liste von Werten. Ich nehme an, dass der richtige Rückkehr-Datentyp in Java ein String-Array anstelle eines String wäre? Ist diese Annahme korrekt und kann dies durch Angabe einer typemap in der SWIG-Schnittstellendatei behandelt werden? Oder bin ich auf dem Holzweg?

Die Funktion in der C-Header-Datei lautet:

DllImport char *GetProjects dsproto((void)); 

Die resultierende JNI Java-Datei:

public final static native String GetProjects(); 

Jede Hilfe/Zeiger wäre sehr dankbar!

+2

Ich habe noch nie von so etwas wie eine NULL-getrennte Liste von Strings gehört. –

+0

Hi Klas, die Schnittstellenspezifikationsstatus: Der Rückgabewert ist ein Zeiger auf eine Reihe von nullterminierten Strings, die mit einem zweiten Nullzeichen enden. – c3po

+0

NUL-begrenzte Strings sind eher selten. Ich bezweifle, dass es in SWIG eine eingebaute Konvertierung geben wird. Sie müssen Code schreiben, um die Zeichenfolgen in den Array-Typ zu konvertieren, der über eine Typenkarte verfügbar ist. –

Antwort

3

Lösung 1 - Java

Es gibt eine Reihe von verschiedenen Möglichkeiten, wie Sie dieses Problem in SWIG lösen können. Ich habe mit einer Lösung begonnen, die nur erfordert, dass Sie ein wenig mehr Java (innerhalb der SWIG-Schnittstelle) schreiben und das automatisch angewendet wird, damit Ihre Funktion String[] mit der von Ihnen gewünschten Semantik zurückgibt.

Als erstes habe ich eine kleine test.h-Datei, die uns die typemaps ausüben können wir arbeiten in Richtung:

static const char *GetThings(void) { 
    return "Hello\0World\0This\0Is\0A\0Lot\0Of Strings\0"; 
} 

Nichts Besonderes nur eine einzige Funktion, die mit einem Doppel mehrere Strings in ein und endet spaltet \0 (die letzte ist implizit in String-Konstanten in C).

Ich schrieb dann die folgende SWIG Schnittstelle es zu wickeln:

%module test 
%{ 
#include "test.h" 
%} 

%include <carrays.i> 

%array_functions(signed char, ByteArray); 

%apply SWIGTYPE* { const char *GetThings }; 

%pragma(java) moduleimports=%{ 
import java.util.ArrayList; 
import java.io.ByteArrayOutputStream; 
%} 

%pragma(java) modulecode=%{ 
static private String[] pptr2array(long in, boolean owner) { 
    SWIGTYPE_p_signed_char raw=null; 
    try { 
    raw = new SWIGTYPE_p_signed_char(in, owner); 
    ArrayList<String> tmp = new ArrayList<String>(); 
    int pos = 0; 
    ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
    while (ByteArray_getitem(raw, pos) != 0) { 
     byte c; 
     while ((c = ByteArray_getitem(raw, pos++)) != 0) { 
     bos.write(c); 
     } 
     tmp.add(bos.toString()); 
     bos.reset(); 
    } 
    return tmp.toArray(new String[tmp.size()]); 
    } 
    finally { 
    if (owner && null != raw) { 
     delete_ByteArray(raw); 
    } 
    } 
} 
%} 

%typemap(jstype) const char *GetThings "String[]"; 
%typemap(javaout) const char *GetThings { 
    return pptr2array($jnicall, $owner); 
} 

%include "test.h" 

Im Wesentlichen das tut, was die carrays.i SWIG Bibliotheksdatei verwenden einige Funktionen zu machen, die uns gesetzt bekommen lassen, und Arrays löschen genau wie ein roher Zeiger in C würde. Da SWIG Sonderfälle char * standardmäßig sind, müssen wir das im Falle der Funktion, die wir betrachten, mit %apply brechen, da wir das nicht wollen. Mit signed char für die Array-Funktionen erhalten wir, was wir wollen: eine Zuordnung zu Byte in Java und nicht String.

Die jstype Typmap ändert einfach den resultierenden Funktionsrückgabetyp zu dem, was wir wollen: String[]. Die Java-Typemap erklärt, wie wir eine Conversion von dem ausführen, was der JNI-Aufruf zurückliefert (a long, da wir es absichtlich nicht als normale Null-terminierte Zeichenfolge umschlossen haben) und stattdessen ein bisschen extra Java, das wir in das Modul geschrieben haben, verwendet) das für uns zu tun.

Inside pptr2array wir bauen im Wesentlichen unser Ausgabe-Array Byte für Byte in jedem String. Ich habe eine ArrayList verwendet, weil ich es lieber dynamisch wachsen lassen würde, als zwei Durchgänge über die Ausgabe zu machen.ein ByteArrayOutputStream Verwendung ist eine ordentliche Art und Weise eine Byte-Array byteweise zu bauen, die im Wesentlichen zwei Vorteile:

  1. Multibyte Unicode kann dies korrekt wie arbeiten. Dies steht im Gegensatz dazu, jedes Byte in char umzuwandeln und einzeln an einen String (Builder) anzuhängen.
  2. Wir können den gleichen ByteArrayOutputStream für jede Zeichenfolge verwenden, wodurch der Puffer wiederverwendet werden kann. Nicht wirklich ein Deal Breaker in diesem Maßstab, aber nicht geschadet, indem es von Tag zu tun 1.

Ein weiterer Punkt ist zu beachten: Damit $owner richtig einzustellen und anzuzeigen, wenn wir free() die erwartete sind Speicher zurückgegeben von der C-Funktion müssen Sie %newobject verwenden. Siehe discussion of $owner in docs.


Lösung 2 - JNI

Wenn Sie Sie bevorzugen fast die gleiche Lösung schreiben kann, aber ganz in typemaps einige JNI machen fordert stattdessen:

%module test 
%{ 
#include "test.h" 
#include <assert.h> 
%} 

%typemap(jni) const char *GetThings "jobjectArray"; 
%typemap(jtype) const char *GetThings "String[]"; 
%typemap(jstype) const char *GetThings "String[]"; 
%typemap(javaout) const char *GetThings { 
    return $jnicall; 
} 
%typemap(out) const char *GetThings { 
    size_t count = 0; 
    const char *pos = $1; 
    while (*pos) { 
    while (*pos++); // SKIP 
    ++count; 
    } 
    $result = JCALL3(NewObjectArray, jenv, count, JCALL1(FindClass, jenv, "java/lang/String"), NULL); 
    pos = $1; 
    size_t idx = 0; 
    while (*pos) { 
    jobject str = JCALL1(NewStringUTF, jenv, pos); 
    assert(idx<count); 
    JCALL3(SetObjectArrayElement, jenv, $result, idx++, str); 
    while (*pos++); // SKIP 
    } 
    //free($1); // Iff you need to free the C function's return value 
} 

%include "test.h" 

Hier haben wir im Wesentlichen getan das gleiche, aber 3 weitere typmaps hinzugefügt. Die jtype- und jnitype-typeMaps teilen SWIG mit, welche Rückgabetypen der generierte JNI-Code und die entsprechende native-Funktion zurückgeben werden, jeweils als Java und C (JNI) -Typ. Die Java-Typ-Map wird einfacher, sie reicht nur eine String[] als String[].

Die In Typemap ist jedoch, wo die Arbeit passiert. Wir ordnen ein Java-Array von String[] im systemeigenen Code zu. Dies geschieht durch einen ersten Durchgang, um einfach zu zählen, wie viele Elemente vorhanden sind. (Es gibt keine saubere Methode, dies in einem Durchgang in C zu tun). Dann rufen wir in einem zweiten Durchlauf NewStringUTF auf und speichern dieses an der richtigen Stelle im Ausgabe-Array-Objekt, das wir zuvor erstellt haben. Alle JNI-Aufrufe verwenden die SWIG-spezifischen JCALLx macros, die es ihnen ermöglichen, in C- und C++ - Compilern zu arbeiten. Es gibt keinen Grund, sie hier zu benutzen, aber es ist keine schlechte Angewohnheit, sich darauf einzulassen.

Jetzt ist nur noch das Ergebnis frei, das die Funktion bei Bedarf zurückgibt. (In meinem Beispiel ist es ein String-Literal, also befreien wir es nicht).


Lösung 3 - C

Natürlich, wenn Sie lieber nur C schreiben Sie können auch eine Lösung. Ich habe hier eine solche Möglichkeit skizziert:

%module test 
%{ 
#include "test.h" 
%} 

%rename(GetThings) GetThings_Wrapper; 
%immutable; 
%inline %{ 
    typedef struct { 
    const char *str; 
    } StrArrHandle; 

    StrArrHandle GetThings_Wrapper() { 
    const StrArrHandle ret = {GetThings()}; 
    return ret; 
    } 
%} 
%extend StrArrHandle { 
    const char *next() { 
    const char *ret = $self->str; 
    if (*ret) 
     $self->str += strlen(ret)+1; 
    else 
     ret = NULL; 
    return ret; 
    } 
} 

%ignore GetThings; 
%include "test.h" 

Beachten Sie, dass die Lösung in diesem Fall den Rückgabetyp von GetThings() ändert sich von Ihrem gewickelt Code ausgesetzt. Es gibt jetzt einen Zwischentyp zurück, der nur im Wrapper StrArrHandle existiert.

Der Zweck dieses neuen Typs ist es, die zusätzliche Funktionalität verfügbar zu machen, die Sie benötigen, um mit allen Antworten zu arbeiten, die Sie von Ihrer eigentlichen Funktion erhalten. Ich tat dies durch Deklarieren und Definieren mit %inline eine zusätzliche Funktion, die den echten Aufruf an GetThings() und einen zusätzlichen Typ umschließt, der den Zeiger enthält, den er für später mit uns zurückgab.

Ich verwendete %ignore und %rename, um noch zu behaupten, dass meine umschlossene Funktion GetThings genannt wurde (obwohl es Namenskonflikte innerhalb des generierten C-Codes nicht vermeiden soll). Ich hätte die %ignore übersprungen und einfach %include nicht am Ende der Datei hinzugefügt, aber basierend auf der Annahme, dass in der realen Welt wahrscheinlich mehr Dinge in der Header-Datei, die Sie auch umhüllen wollten, dieses Beispiel wahrscheinlich nützlicher ist.

Mithilfe von %extend können wir dann eine Methode zu dem von uns erstellten Wrappertyp hinzufügen, der die aktuelle Zeichenfolge (wenn nicht am Ende) zurückgibt und den Cursor weiterbewegt. Wenn Sie dafür verantwortlich sind, den Rückgabewert der ursprünglichen Funktion freizugeben, sollten Sie auch eine Kopie davon behalten und %extend verwenden, um einen 'Destruktor' hinzuzufügen, den SWIG aufruft, wenn das Objekt Müll gesammelt wird.

Ich sagte SWIG, Benutzern nicht zu erlauben, das StrArrHandle Objekt mit %nodefaultctor zu konstruieren. SWIG erzeugt einen Getter für das str Mitglied von StrArrHandle. Die %immutable verhindert, dass sie einen Setter erzeugt, was hier überhaupt keinen Sinn macht. Sie hätten es einfach mit %ignore oder StrArrHandle aussortieren können anstatt %inline zu verwenden und SWIG einfach nicht über dieses Mitglied zu informieren.

mit diesem Jetzt können Sie es aus Java aufrufen, wie etwas mit:

StrArrHandle ret = test.GetThings(); 
for (String s = ret.next(); s != null; s = ret.next()) { 
    System.out.println(s); 
} 

Wenn man wollte, obwohl Sie diese mit Teilen der Lösung # 1, um eine Java-Array zurückzukehren kombinieren könnte. Sie würden wollen, dass zwei typemaps hinzuzufügen, in der Nähe der Spitze:

%typemap(jstype) StrArrHandle "String[]"; 
%typemap(javaout) StrArrHandle { 
    $javaclassname tmp = new $javaclassname($jnicall, $owner); 
    // You could use the moduleimports pragma here too, this is just for example 
    java.util.ArrayList<String> out = new java.util.ArrayList<String>(); 
    for (String s = tmp.next(); s != null; s = tmp.next()) { 
    out.add(s); 
    } 
    return out.toArray(new String[out.size()]); 
} 

die so ziemlich das gleiche Ergebnis wie Lösung 1, aber in einer ganz anderen Art und Weise hat.

+0

Wow ! das ist erstaunlicher Flexo! das hat mir wirklich geholfen! – c3po

+0

@ c3po Ich habe jetzt Lösungen 2 und 3 hinzugefügt, die alternative Wege zeigen, um das gleiche Ergebnis zu erzielen. – Flexo

+0

@Flexo Du bist ein sehr kranker Mann ... ;-) –