Как преобразовать в тип SWIGTYPE_p_void в сгенерированных SWIG привязках Java?

Я разрабатываю некоторые привязки Java, созданные SWIG, для библиотеки 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 для преобразования типов данных?

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


person j b    schedule 12.06.2012    source источник


Ответы (2)


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

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

Мы по-прежнему пишем некоторые (частные) вспомогательные функции, чтобы организовать извлечение реального базового указателя и освободить его, когда закончите, но хорошая вещь в этом решении заключается в том, что нам нужно сделать это только один раз, а затем мы получим карту типов, которая может использоваться для любых функций, которые принимают массив как 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 обеспечивает временную локальную переменную для хранения указателя (в long), а затем для его использования для выполнения вызова и очистки после успешного или неудачного вызова.

person Flexo    schedule 14.06.2012
comment
Это действительно очень хорошая техника, и ваш тестовый код работает отлично. Это почти работает для меня в контексте, за исключением того, что мои функции C на самом деле принимают const void *, а не void *. Я могу избежать написания функций-оболочек с %apply void *in {const void *argv};, но если я не объявлю заново все свои функции (их 50!), принимая void *, я получаю: Невозможно применить (void *in). Карты типов не определены. Могу ли я как-нибудь сказать, что относиться к const void * как к void *? - person j b; 17.06.2012
comment
@JamieBullock Я думаю, вы, вероятно, используете %apply не в том месте. Вам нужно использовать его после определения всех карт типов. Поэтому я добавил %apply void *arr { const void *argv } в конце интерфейса, и все заработало. Вы также должны быть осторожны, чтобы точно сопоставлять имена параметров, если вы сопоставляете это - в моем примере они назывались arr, но показанный вами %apply называет его in, не уверен, что это потому, что вы все это переименовали. - person Flexo; 18.06.2012
comment
это не работает для меня, но вы правы %apply void *in {const void *argv}; было ошибочно. в конце концов я решил сделать 1-строчный скрипт в своем скрипте сборки, чтобы сгенерировать redeclare.i со всеми моими функциями, повторно объявленными как принимающие void * вместо const void *, а затем добавил %include redeclare.i в конец моего основного файла .i. Может быть, не так элегантно, но это работает. Это в сочетании с вашим решением выше теперь позволяет мне свободно передавать массивы Java как void *, и это здорово. Спасибо! - person j b; 19.06.2012
comment
@JamieBullock, возможно, я должен был быть более явным - %apply в этой модифицированной версии моего ответа сгенерировал правильный интерфейс Java . - person Flexo; 19.06.2012

Самое простое решение — использовать SWIG <carrays.i> для создания типа, обертывающего массив из 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);    
  }
}

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

  1. Также можно написать некоторый код, который брал бы фактический массив Java, а не просто SWIG array_class, и реализовывал бы этот интерфейс, вызывая функцию JNI для получения базового указателя. Однако вам придется написать версию этого для каждого примитивного типа, как и выше.

    Тогда файл интерфейса может выглядеть примерно так:

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

    Что затем дает вам перегрузки foo, которые принимают float[] и int[], а также SWIGTYPE_p_void.

  2. Вы можете использовать трюк с союзом:

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

    хотя это считается дурным тоном, оно создает интерфейс Java, который можно использовать для преобразования из SWIGTYPE_p_int в SWIGTYPE_p_void.

  3. Я думаю, что FloatArray можно наследовать от SWIGTYPE_p_void, что-то вроде следующего скомпилированного, но непроверенного кода:

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

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

person Flexo    schedule 13.06.2012