Передача указателя на указатель для перемещения из Java через JNA в динамическую библиотеку C - PullRequest
0 голосов
/ 04 мая 2018

Catboost предлагает динамическую библиотеку C , которую теоретически можно использовать с любого языка программирования.

Я пытаюсь вызвать его через Java, используя JNA.

У меня проблема с функцией CalcModelPrediction, определенной в заголовочном файле следующим образом:

EXPORT bool CalcModelPrediction(
    ModelCalcerHandle* calcer,
    size_t docCount,
    const float** floatFeatures, size_t floatFeaturesSize,
    const char*** catFeatures, size_t catFeaturesSize,
    double* result, size_t resultSize);

В Java я определил интерфейсную функцию следующим образом:

public interface CatboostModel extends Library {
        public Pointer ModelCalcerCreate();
        public String GetErrorString();
        public boolean LoadFullModelFromFile(Pointer calcer, String filename);
        public boolean CalcModelPrediction(Pointer calcer, int docCount,
                PointerByReference floatFeatures, int floatFeaturesSize,
                PointerByReference catFeatures, int catFeaturesSize,
                Pointer result, int resultSize);
        public int GetFloatFeaturesCount(Pointer calcer);
        public int GetCatFeaturesCount(Pointer calcer);
    }

и тогда я называю это так:

CatboostModel catboost;
Pointer modelHandle;

catboost = Native.loadLibrary("catboostmodel", CatboostModel.class);
            modelHandle = catboost.ModelCalcerCreate();
if (!catboost.LoadFullModelFromFile(modelHandle, "catboost_test.model"))
{
    throw new RuntimeException("Cannot load Catboost model.");
}

final PointerByReference ppFloatFeatures = new PointerByReference();
final PointerByReference ppCatFeatures = new PointerByReference();
final Pointer pResult = new Memory(Native.getNativeSize(Double.TYPE));

float[] floatFeatures = {0.5f, 0.8f, 0.3f, 0.3f, 0.1f, 0.5f, 0.4f, 0.8f, 0.3f, 0.3f} ;
String[] catFeatures = {"1", "2", "3", "4"};
int catFeaturesLength = 0;
for (String s : catFeatures)
{
    catFeaturesLength += s.length() + 1;
}

try
{
    final Pointer pFloatFeatures = new Memory(floatFeatures.length * Native.getNativeSize(Float.TYPE));
    for (int dloop=0; dloop<floatFeatures.length; dloop++) {
        pFloatFeatures.setFloat(dloop * Native.getNativeSize(Float.TYPE), floatFeatures[dloop]);
    }
    ppFloatFeatures.setValue(pFloatFeatures);

    final Pointer pCatFeatures = new Memory(catFeaturesLength * Native.getNativeSize(Character.TYPE));
    long offset = 0;
    for (final String s : catFeatures) {
        pCatFeatures.setString(offset, s);
        pCatFeatures.setMemory(offset + s.length(), 1, (byte)(0));
        offset += s.length() + 1;
    }
    ppCatFeatures.setValue(pCatFeatures);

}
catch (Exception e)
{
    throw new RuntimeException("Couldn't initialize parameters for catboost");
}

try
{
    if (!catboost.CalcModelPrediction(
                modelHandle,
                1,
                ppFloatFeatures, 10,
                ppCatFeatures, 4,
                pResult, 1
                ))
    {
        throw new RuntimeException("No prediction made: " + catboost.GetErrorString());
    }
    else
    {
        double[] result = pResult.getDoubleArray(0, 1);
        log.info("Catboost prediction: " + String.valueOf(result[0]));
        Assert.assertFalse("ERROR: Result empty", result.length == 0);
    }
}
catch (Exception e)
{
    throw new RuntimeException("Prediction failed: " + e);
}

Я пытался передать Pointer, PointerByReference и Pointer[] в функцию CalcModelPrediction вместо float **floatFeatures и char ***catFeatures, но ничего не получалось. Я всегда получаю ошибку сегментации, предположительно, когда функция CalcModelPrediction пытается получить элементы floatFeatures и catFeatures, вызывая floatFeatures[0][0] и catFeatures[0][0].

Итак, вопрос в том, как правильно передать многомерный массив из Java через JNA в C, где его можно рассматривать как указатель на указатель на значение?

Интересно то, что функция CalcModelPredictionFlat, которая принимает только float **floatFeatures, а затем просто вызывает *floatFeatures, прекрасно работает при передаче PointerByReference.

ОБНОВЛЕНИЕ - 5.5.2018

Часть 1

После попытки отладить segfault, слегка изменив исходные файлы .cpp и .h Catboost и перекомпилировав библиотеку libcatboost.so, я обнаружил, что segfault произошел из-за моего сопоставления size_t в C с int в Джава. После исправления это моя интерфейсная функция в Java выглядит следующим образом:

public interface CatboostModel extends Library {
        public boolean LoadFullModelFromFile(Pointer calcer, String filename);
        public boolean CalcModelPrediction(Pointer calcer, size_t docCount,
                Pointer[] floatFeatures, size_t floatFeaturesSize,
                String[] catFeatures, size_t catFeaturesSize,
                Pointer result, size_t resultSize);
    }

Где класс size_t определяется следующим образом:

public static class size_t extends IntegerType {                                           
    public size_t() { this(0); }                                                           
    public size_t(long value) { super(Native.SIZE_T_SIZE, value); }                       
}

Часть 2 Более подробно изучая код Catboost, я заметил, что строки **floatFeatures доступны по строкам, например floatFeatures[i], а строки ***catFeatures - по строкам и столбцам, например catFetures[i][catFeatureIdx].

.

После изменения floatFeatures в Java на массив Pointer мой код начал работать с моделью, обученной без категориальных функций, то есть catFeatures длина равна нулю.

Однако этот трюк не работал с catFeatures, доступ к которому осуществляется через оператор двойного индекса [i][catFeatureidx]. Итак, пока я изменил исходный код Catboost, чтобы он принимал char **catFeatures - массив строк. В функции интерфейса Java я установил String[] catFeatures. Теперь я могу делать прогнозы для одного элемента за раз, что не идеально.

1 Ответ

0 голосов
/ 05 мая 2018

Мне удалось заставить все это работать с оригинальным кодом Catboost и libcatboost.so.

Функция интерфейса Java определяется следующим образом. Обратите внимание, что для эмуляции 2D-массива (или указателя на указатель) значений и строк с плавающей запятой я использую тип Pointer[].

public interface CatboostModel extends Library {
        public boolean LoadFullModelFromFile(Pointer calcer, String filename);
        public boolean CalcModelPrediction(Pointer calcer, size_t docCount,
                Pointer[] floatFeatures, size_t floatFeaturesSize,
                Pointer[] catFeatures, size_t catFeaturesSize,
                Pointer result, size_t resultSize);
    }

После этого я заполняю параметры floatFeatures и catFeatures следующим образом (некоторые фиктивные данные здесь). Обратите внимание, что для строк я использую JNA StringArray.

float[] floatFeatures = {0.4f, 0.8f, 0.3f, 0.3f, 0.1f, 0.5f, 0.4f, 0.8f, 0.3f, 0.3f} ;
String[] catFeatures = {"1", "2", "3", "4"};

final Pointer pFloatFeatures = new Memory(floatFeatures.length * Native.getNativeSize(Float.TYPE));
final Pointer[] ppFloatFeatures = new Pointer[2];
for (int dloop=0; dloop<10; dloop++) {
    pFloatFeatures.setFloat(dloop * Native.getNativeSize(Float.TYPE), floatFeatures[dloop]);
}
ppFloatFeatures[0] = pFloatFeatures;
ppFloatFeatures[1] = pFloatFeatures;

final Pointer[] ppCatFeatures = new Pointer[catFeatures.length];
final Pointer pCatFeatures = new StringArray(catFeatures);
ppCatFeatures[0] = pCatFeatures;
ppCatFeatures[1] = pCatFeatures;

Наконец, я передаю эти параметры в Catboost:

if (!catboost.CalcModelPrediction(
                modelHandle,
                new size_t(2L),
                ppFloatFeatures, new size_t((long)floatFeatures.length),
                ppCatFeatures, new size_t((long)catFeatures.length),
                pResult, new size_t(2L)
                ))
{
    throw new RuntimeException("No prediction made: " + catboost.GetErrorString());
}

Чтобы получить прогнозы, мы можем сделать:

double[] result = pResult.getDoubleArray(0, 2);
...