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.
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' ... –
@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
@SamuelAudet - zaktualizowałem swoją odpowiedź za pomocą ręcznej metody pakowania. To dość brzydkie według mnie. – Flexo