Вопрос по java-native-interface, java, swig, c – Как я могу преобразовать в тип SWIGTYPE_p_void в SWIG-генерируемых привязках Java?

5

Я разрабатываю некоторые сгенерированные SWIG Java-привязки для библиотеки C. Библиотека содержит функции, которые принимают параметры типаvoid *, На стороне C они обычно передаются как указатель на массив типаfloat или жеint приведение кvoid *, В сгенерированных привязках Java это приводит к методам, которые принимают параметры типаSWIGTYPE_p_void.

Каков наилучший способ для создания массива с плавающей точкой / целых в привязках Java, чтобы они могли быть переданы как типSWIGTYPE_p_void к этим методам?

На данный момент я определяю вспомогательную функцию в моем файле example.i:

void *floata_to_voidp(float f[])
{
    return (void *)f;
}

А затем на стороне Java делает что-то вроде этого:

float foo[] = new float[2];
SWIGTYPE_p_void av = null;

// do something with foo

av = example.floata_to_voidp(foo);
example.myfunction(av);

Это кажется довольно уродливым, особенно потому, что мне тоже нужноinta_to_voidp() и т.д. в моем файле интерфейса SWIG для каждого преобразования типов, которое я хочу поддерживать.

Есть ли способ сделать это без вспомогательных функций и задействовать меньше дополнительного кода на стороне Java для преобразования типов данных?

UPDATE (17/6/12): дать дополнительные подробности вопроса: что я пытаюсь сделать, это взять набор функций C с прототипомint foo(const float *data, int N, const void *argv, float *result) и сопоставить их с методами на стороне Java, где массив произвольного типа может быть передан какargv, Обратите внимание, чтоargv являетсяconst void * и неvoid *.

Ваш Ответ

2   ответа
1

использовать SWIG<carrays.i> to make a type that wraps an array изfloat и массивint и любые другие типы, которые вам небезразличны. Они могут быть преобразованы вSWIGTYPE_p_float и т.д. тривиально, используяcast() функция-член. Проблема заключается в том, что это не может быть автоматически преобразовано вSWIGTYPE_p_void изнутри Java. Вы можете теоретически назвать:

new SWIGTYPE_p_void(FloatArray.getCPtr(myfloatarr));

но по разным причинам (не в последнюю очередь это громоздко), что не идеально. (Это также имеет проблемы с владением памятью и сборкой мусора).

Поэтому вместо этого я определил интерфейс:

public interface VoidPtr {
  public long asVoidPtr();
}

То, что мы можем заставить обернутую версию вашей библиотеки принимать в качестве входных данных и классов массиваFloatArray, IntArray и т. д. реализовать для нас.

Это заканчивается файлом модуля:

%module test

%include <carrays.i>

%typemap(javainterfaces) FloatArray "VoidPtr"
%typemap(javainterfaces) IntArray "VoidPtr"

%typemap(javacode) FloatArray %{
  public long asVoidPtr() {
    return getCPtr(this);    
  }
%}

%typemap(javacode) IntArray %{
  public long asVoidPtr() {
    return getCPtr(this);
  }
%}

%array_class(float, FloatArray);
%array_class(int, IntArray);

%typemap(jstype) void *arr "VoidPtr"
%typemap(javain) void *arr "$javainput.asVoidPtr()"

void foo(void *arr);

Который модифицируетvoid *arr рассматриваться как нашVoidPtr введите и автоматически вызываетasVoidPtr() метод. Вы могли бы использоватьтипографское копирование или макросы, чтобы сделать это менее повторяющимся. (Обратите внимание, что возможна проблема спреждевременная сборка мусора это может потребоваться решить в зависимости от того, как вы планируете использовать это)

Это позволяет нам писать код вроде:

public class run {
  public static void main(String[] argv) {
    FloatArray arr = new FloatArray(100);
    test.foo(arr);    
  }
}

Я думаю, что это самое простое и чистое решение. Есть несколько других способов решить эту проблему:

It's also possible to write some code that would take an actual Java array rather than just the SWIG array_class and implement this interface by calling a JNI function to obtain the underlying pointer. You'd have to write a version of this for every primitive type though, just like the above.

An interface file could then look something like:

%module test

%{
void foo(void *arr);
%}

%typemap(in,numinputs=0) JNIEnv *env "$1 = jenv;"

%rename(foo) fooFloat;
%rename(foo) fooInt;
%inline %{
void fooFloat(JNIEnv *env, jfloatArray arr) {
  jboolean isCopy;
  foo((*env)->GetFloatArrayElements(env, arr, &isCopy));
  // Release after call with desired semantics
}

void fooInt(JNIEnv *env, jintArray arr) {
  jboolean isCopy;
  foo((*env)->GetIntArrayElements(env, arr, &isCopy));
  // Release after call
}
%}

void foo(void *arr);

Which then gives you overloads of foo which take float[] and int[] as well as SWIGTYPE_p_void.

You could use a trick with a union:

%inline %{
  union Bodge {
    void *v;
    float *f;
    int *i;
  };
%}

although this is considered bad form, it does generate you a Java interface that can be used to convert from SWIGTYPE_p_int to SWIGTYPE_p_void.

I think it's possible to make FloatArray inherit from SWIGTYPE_p_void, something like the following compiled but untested code:

%module test

%include <carrays.i>

%typemap(javabase) FloatArray "SWIGTYPE_p_void"
%typemap(javabody) FloatArray %{
  private long swigCPtr; // Minor bodge to work around private variable in parent
  private boolean swigCMemOwn;
  public $javaclassname(long cPtr, boolean cMemoryOwn) {
    super(cPtr, cMemoryOwn);
    this.swigCPtr = SWIGTYPE_p_void.getCPtr(this);
    swigCMemOwn = cMemoryOwn;
  }
%}

%array_class(float, FloatArray);

void foo(void *arr);

This duplicates the pointer on the Java side, but nothing changes that (currently) in either the void pointer or array classes so that's not as big a problem as it first seems. (You could also make it protected in the base class with an alternative typemap I think, or use a modified version of carrays.i that gets swigCPtr via the getCPtr function instead)

3

этот ответон очень отличается и дает более естественное решение этой проблемы, ближе к тому, что вы искали изначально. Другие предложения были сосредоточены на добавлении перегрузок (утомительных, ручных) или созданииarray_classОни реализуют общий интерфейс, так или иначе.

То, что он упускает из виду, это то, чтоObject хорошо подходит дляvoid* в Java большую часть времени. Четноемассивы в JavaObjects, Это означает, что если у вас есть карта SWIGvoid* вObject он будет принимать в качестве входных данных любые массивы, которые вы, возможно, захотите передать. С некоторой осторожностью и некоторым JNI мы можем затем получить указатель на начало этого массива для передачи в функцию. Очевидно, нам нужно отклонить не массивObjectс исключением, хотя.

Мы все еще заканчиваем тем, что пишем некоторые (приватные) вспомогательные функции для организации извлечения реального базового указателя и выпускаем его, когда закончим, но хорошая вещь в этом решении состоит в том, что мы должны сделать это только один раз, а затем мы получаем карту типов, которая может использоваться для любых функций, которые принимают массив какvoid* как это.

Я получил следующий интерфейс SWIG для этого решения:

%module test

%{
#include <stdint.h>

void foo(void *in) {
  printf("%p, %d, %g\n", in, *(jint*)in, *(jdouble*)in);
}
%}

%typemap(in,numinputs=0) JNIEnv *env "$1 = jenv;"

%javamethodmodifiers arr2voidd "private";
%javamethodmodifiers arr2voidi "private";
%javamethodmodifiers freearrd "private";
%javamethodmodifiers freearri "private";
%inline %{
jlong arr2voidd(JNIEnv *env, jdoubleArray arr) {
  void *ptr = (*env)->GetDoubleArrayElements(env, arr, NULL);
  return (intptr_t)ptr;
}

void freearrd(JNIEnv *env, jdoubleArray arr, jlong map) {
  void *ptr = 0;
  ptr = *(void **)&map;
  (*env)->ReleaseDoubleArrayElements(env, arr, ptr, JNI_ABORT);
}

jlong arr2voidi(JNIEnv *env, jintArray arr) {
  void *ptr = (*env)->GetIntArrayElements(env, arr, NULL);
  return (intptr_t)ptr;
}

void freearri(JNIEnv *env, jintArray arr, jlong map) {
  void *ptr = 0;
  ptr = *(void **)&map;
  (*env)->ReleaseIntArrayElements(env, arr, ptr, JNI_ABORT);
}
%}


%pragma(java) modulecode=%{
  private static long arrPtr(Object o) {
    if (o instanceof double[]) {
      return arr2voidd((double[])o);
    }
    else if (o instanceof int[]) {
      return arr2voidi((int[])o);
    }
    throw new IllegalArgumentException();
  }

  private static void freeArrPtr(Object o, long addr) {
    if (o instanceof double[]) {
      freearrd((double[])o, addr);
      return;
    }
    else if (o instanceof int[]) {
      freearri((int[])o, addr);
      return;
    }
    throw new IllegalArgumentException();
  }
%}

%typemap(jstype) void *arr "Object"
%typemap(javain,pre="    long tmp$javainput = arrPtr($javainput);",post="      freeArrPtr($javainput, tmp$javainput);") void *arr "tmp$javainput"

void foo(void *arr);

Это реализует его для двух типов массивов, имеется небольшое конечное число, и вы также можете использовать фрагменты или макросы, чтобы помочь с этим. Внутренне SWIG используетjlong представлять указатели. Таким образом, для каждого типа массива нам нужна функция, которая возвращает указатель для данного массива и еще одну для его освобождения. Они являются частными и являются частью класса модуля - никто, кроме модуля, не должен знать, как это работает.

Затем две функции, которые принимаютObject и использоватьinstanceof (уродливо, но массивы в Java не имеют какой-либо другой общей базы или интерфейса, а общие шаблоны не помогают) и вызывают правильную функцию для получения / освобождения указателей.

С этими двумя настройками SWIG можно использовать для всехvoid *arr аргументы. Карта типов jstype инструктирует SWIG использоватьObject заvoid* в этих случаях. Javain Typemap организует временную локальную переменную для хранения указателя (вlong), а затем использовать его для выполнения вызова и очистки после успешного или неудачного вызова.

Это действительно очень хорошая техника, и ваш тестовый код работает отлично. Этоalmost работает для меня в контексте, за исключением деталей, которые на самом деле принимают мои функции Cconst void * и неvoid *, Я могу избежать написания функций оболочки с%apply void *in {const void *argv};, но если я не объявлю заново все свои функции (их 50!), какvoid *, Я получил:Can't apply (void *in). No typemaps are defined, Есть ли способ, которым я могу просто сказать "лечить"const void * какvoid *& Quot ;? j b
@JamieBullock, возможно, я должен был быть более явным -%apply вthis modified version of my answer генерируется правильный интерфейс Java.
это не работает для меня, но вы правы%apply void *in {const void *argv}; был ошибочным. в конце концов я решил сделать 1-строчный скрипт в моем скрипте сборки, чтобы сгенерировать redeclare.i со всеми моими функциями, объявленными какvoid * вместоconst void *, а затем добавил%include redeclare.i до конца моей основной.i файл. Может быть, не так элегантно, но это работает. Это в сочетании с вашим решением теперь позволяет мне свободно передавать массивы Java как void *, и это здорово. Спасибо! j b
@JamieBullock Я думаю, вы, вероятно, используете%apply в неправильном месте. Вы должны использовать этоafter все шрифты были определены. Итак, я добавил%apply void *arr { const void *argv } в конце интерфейса все заработало нормально. Вы также должны быть осторожны, чтобы точно соответствовать именам параметров, если вы соответствуете этому - в моем примере они называлисьarr но%apply ты показал звонки этоin, не уверен, что это потому, что вы переименовали все это.

Похожие вопросы