Как использовать SWIG для обертывания объектов std::function?


Я видел довольно много подобных вопросов, но не нашел решения своей конкретной проблемы. Я пытаюсь проглотить некоторый код C++11, который использует функцию std::, так что я могу использовать его в моем приложении Java.

Я сталкивался с такими общими указателями:

virtual std::shared_ptr<some::ns::TheThing> getTheThing(unsigned short thingID);

И успешно обработал их с помощью директивы shared_ptr следующим образом:

%shared_ptr(some::ns::TheThing);

Я столкнулся с векторами общих указателей, такими как:

virtual std::vector<std::shared_ptr<some::ns::TheThing>> getAllTheThings() const = 0;

И успешно справился с ними с шаблоном примерно так:

%template(ThingVector) std::vector<std::shared_ptr<some::ns::TheThing>>;

Теперь у меня есть такой метод:

 void registerThingCallback(std::function<void(std::shared_ptr<some::ns::TheThing>) > func);
И я не могу заставить СВИГА завернуть его должным образом. Я пробовал использовать %callback, directors, % template и % inline функциональный код, поскольку я видел примеры со всеми этими вещами, но не смог получить ничего, что кажется близким к работе. Вот немного больше контекста вокруг вызова функции, если это помогает (очищено и сокращено):

Thing_callback.h

#include <functional>

namespace some {
  namespace ns {

    /**
     * Hold some callbacks.
     */
    class ThingCallbacks {
    public:

        /**
         * Registers a callback 
         * @param func The callback function
         */
        void registerThingCallback(std::function<void(std::shared_ptr<some::ns::TheThing>) > func);

    };

  }
}

Обновление

Основываясь на замечательном ответе Flexo ниже, я гораздо ближе к решению. Я смог получить примеры ниже, работающие точно так, как рекламируется. Я попытался включить его в свой реальный код, но столкнулся с проблемами. Чтобы расширить мой более ранний упрощенный пример, вот мое определение TheThing:

Test_thing.h

#ifndef THE_THING_H
#define THE_THING_H

#include <string>

namespace some {
  namespace ns {

    class TheThing {
    public:

        virtual ~TheThing() {};

        virtual unsigned long longThing() const = 0;

        virtual std::string stringThing() const = 0;
    };
  }
}

#endif  /* THE_THING_H */

А вот моя .я подаю заявление. Чтобы иметь как можно меньше движущихся частей, я в основном просто взял int и double из кода, приведенного в ответе ниже, и заменил их общим указателем на мой объект.

Func_thing_test.i

%module(directors="1") Thing
%include "stl.i"
%include "std_function.i"
%include "std_shared_ptr.i"

%shared_ptr(some::ns::TheThing);


%typemap(javadirectorin) std::shared_ptr<some::ns::TheThing> "new $typemap(jstype, some::ns::TheThing)($1,false)";
%typemap(directorin,descriptor="Lsome.ns.typemap(jstype, some::ns::TheThing);") std::shared_ptr<some::ns::TheThing> %{
     *($&1_type*)&j$1 = &$1;
%}


%include "test_thing.h"
%include "thing_callback.h"

%{
#include <memory>

#include "test_thing.h"
#include "thing_callback.h"

%}

%std_function(Functor, void, std::shared_ptr<some::ns::TheThing>);

%{
#include <iostream>
void add_and_print(std::shared_ptr<some::ns::TheThing> thing) {
  std::cout << "heren";
}
%}

%callback("%s_cb");
void add_and_print(std::shared_ptr<some::ns::TheThing>);
%nocallback;

%inline %{
  std::function<void(std::shared_ptr<some::ns::TheThing>)> make_functor() {
    return [](std::shared_ptr<some::ns::TheThing>){
      std::cout << "make functorn";
    };
  }

  void do_things(std::function<void(std::shared_ptr<some::ns::TheThing>)> in) {
      std::cout << "inside do thingsn";
  }
%}

Test_thing.h - это то, что я только что опубликовал выше, и thing_callback.h-это код, который я разместил в своем первоначальном вопросе. Все это компилируется через цепочку swig, c++ и Java без ошибок, но похоже, что swig испытывает небольшие проблемы с соединением точек между c++ и Java. Он создает эти три классы:

SWIGTYPE_p_f_std__function__f_std__shared_ptr__some__ns__TheThing____void____void
SWIGTYPE_p_f_std__shared_ptr__some__ns__TheThing____void
SWIGTYPE_p_std__functionT_void_fstd__shared_ptrT_some__ns__TheThing_tF_t
И, к сожалению, большинство методов из простого основного кода Java теперь принимают или возвращают эти объекты, что делает их довольно непригодными. Есть идеи, как это исправить? Спасибо!

Немного подробнее Для полноты: я использую следующие три скрипта для компиляции и запуска кода. Параметры немного отличаются, но я не думаю, что это имеет значение. С моей стороны он настроен как проект Eclipse maven. Эти сценарии находятся в корне моего проекта, заголовочные и swig-файлы находятся в src / main / resources, исходные файлы java - в src/main/java, а скомпилированные классы java-в target / classes. Eclipse выполняет компиляцию java.

Swigthing.sh

MODULE_NAME=Thing
PACKAGE=some.ns
OUTDIR=./src/main/java/some/ns
I_FILE=./src/main/resources/func_thing_test.i

mvn clean

rm $OUTDIR/*.*
mkdir -p $OUTDIR

swig -java -c++ -module $MODULE_NAME -package $PACKAGE -outdir $OUTDIR $I_FILE

./compileThingSwigTest.sh

CompileThingSwigTest.sh

#!/bin/bash

pushd src/main/resources
g++ -c -std=gnu++11 -fpic 
func_thing_test_wrap.cxx 
-I/usr/lib/jvm/java/include 
-I/usr/lib/jvm/java/include/linux

g++ -shared func_thing_test_wrap.o -o libFunc.so
popd

RunThingTest.sh

pushd target/classes
java -Xmx512M -Xms512M -Djava.library.path=. some.ns.test.RunThingTest
popd

Последнее Обновление

Исправлен приведенный выше код для передачи правильных параметров в std_function. Теперь между вопросом и ответом есть полный рабочий пример того, кем я был после.

1 8

1 ответ:

Мы можем сделать это с небольшой работой. Мой ответ здесь является более обобщенной версией моего предыдущего ответа, рассматривая эту проблему для конкретного экземпляра и ориентируясь на Python.

Я предполагаю, что есть четыре вещи, которые вы хотите получить от упаковки std::function:

    Мы хотим иметь возможность вызывать объекты std::functionиз кода Java.
  1. обернутые std::function объекты должны передаваться по кругу, как и любой другой объект, включая пересечение языковые границы в обоих направлениях.
  2. должна быть возможность писать std::function объекты внутри Java, которые могут быть переданы обратно в C++ без необходимости изменять существующий код C++, который работает на std::function объектах (т. е. сохраняя стирание типов std::function кросс-языка)
  3. мы должны быть в состоянии построить std::function объекты в Java, используя указатель C++ на типы функций.
Я собираюсь проработать их и показать, как мы можем этого достичь. Там, где это возможно, я сохраню решение языка агностиков тоже.

Для целей обсуждения я замазываю часть вашего вопроса shared_ptr, на самом деле это ничего не меняет, потому что, поскольку у вас есть shared_ptr работа, которой на самом деле достаточно, чтобы использовать ее и в этом сценарии, это просто сделает мои примеры более подробными без необходимости.

Решение, над которым я работаю, на самом деле смоделировано после существующей поддержки shared_ptr в SWIG. Я собрал тестовый интерфейс, чтобы проиллюстрировать, как это будет используется:

%module test
%include "std_function.i"

%std_function(Functor, void, int, double);

%{
#include <iostream>
%}

%inline %{
  std::function<void(int,double)> make_functor() {
    return [](int x, double y){
      std::cout << x << ", " << y << "\n";
    };
  }
%}

В основном, чтобы использовать это все, что вам нужно сделать, это включить файл " std_function.i", а затем использовать макрос %std_function, который принимает аргументы как:

%std_function(Name, Ret, ...)

Вы вызываете это один раз за экземпляр шаблона std::function, который вы хотите обернуть, где Name - это то, что вы хотите вызвать тип в Java, Ret-возвращаемый тип, а затем остальные (вариадические) аргументы являются входными данными для вашей функции. Так что в моем тестовом интерфейсе выше я в основном ищу, чтобы обернуть std::function<void(int,double)>.

Написание первой версии " std_function.я " на самом деле не слишком сложно. Все, что вам нужно, чтобы получить основные рабочие требования №1 и №2, это:

%{
  #include <functional>
%}

%define %std_function(Name, Ret, ...)
%rename(Name) std::function<Ret(__VA_ARGS__)>;
%rename(call) std::function<Ret(__VA_ARGS__)>::operator();
namespace std {
  struct function<Ret(__VA_ARGS__)> {
    // Copy constructor
    function<Ret(__VA_ARGS__)>(const std::function<Ret(__VA_ARGS__)>&);

    // Call operator
    Ret operator()(__VA_ARGS__) const;
  };
}

%enddef

Это включает файл заголовка C++ в сгенерированный код оболочки один раз, а затем настраивает макрос для использования в интерфейсах. Поддержка SWIG для C++11 variadic templates На самом деле не очень полезна для нас в этом сценарии использования, поэтому макрос, который я написал, в основном повторно реализует расширение шаблона по умолчанию функциональность с использованием аргументов макроса C99 variadic (которые поддерживаются гораздо лучше). По совпадению это означает, что SWIG-код, который мы пишем, будет работать с 2.х или даже 1.3.х выпусках. (Я проверил с 2.икс). Даже если / когда ваша версия SWIG имеет поддержку %template, которая работает с std::function, сохранение этого макроса все равно полезно для остальной части клея, который делает его фактически вызываемым.

Ручное расширение шаблона std:function ограничивается только теми битами, которые мы заботимся о наших использование: фактический operator() и конструктор копирования, который может пригодиться.

Единственное, что еще нужно сделать, это переименовать operator() во что-то, что соответствует целевому языку, например, для Java переименовать его в обычную функцию под названием "вызов", или если вы нацеливали Python на __call__ или использовали tp_slots, если требуется.

Этого теперь достаточно, чтобы наш интерфейс работал, чтобы продемонстрировать это я написал немного Java:

public class run {
    public static void main(String[] argv) {
        System.loadLibrary("test");
        test.make_functor().call(1,2.5);
    }
}

Который я составил с:

swig2.0 -Wall -c++ -java  test.i
g++ -Wall -Wextra -std=c++11 test_wrap.cxx -o libtest.so -I/usr/lib/jvm/default-java/include/ -I/usr/lib/jvm/default-java/include/linux -shared -fPIC
javac run.java
LD_LIBRARY_PATH=. java run

И это сработало.


Требование №4 теперь довольно легко вычеркнуть из списка. Все, что нам нужно сделать, это сказать SWIG, что в std::function есть другой конструктор, который принимает совместимые указатели функций:

// Conversion constructor from function pointer
function<Ret(__VA_ARGS__)>(Ret(*const)(__VA_ARGS__));

И затем мы можем использовать это с %callback механизм В SWIG наш тестовый файл интерфейса становится:

%module test
%include "std_function.i"

%std_function(Functor, void, int, double);

%{
#include <iostream>
void add_and_print(int a, double b) {
  std::cout << a+b << "\n";
}
%}

%callback("%s_cb");
void add_and_print(int a, double b);
%nocallback;

%inline %{
  std::function<void(int,double)> make_functor() {
    return [](int x, double y){
      std::cout << x << ", " << y << "\n";
    };
  }
%}

И Java, которую мы используем, чтобы вызвать это, тогда:

public class run {
    public static void main(String[] argv) {
    System.loadLibrary("test");
    test.make_functor().call(1,2.5);
    new Functor(test.add_and_print_cb).call(3,4.5);
    }
}

Который мы компилируем и запускаем идентично успешно на этом этапе.

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


Требование №3-это то, где все начинает становиться сложнее. По существу, мы столкнулись с той же проблемой, на которую я ответил при создании SWIG - интерфейса в Java ранее, за исключением теперь мы хотим сделать это в рамках макроса более обобщенно.

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

Главное, что нам нужно сделать, чтобы сделать эту работу,-это настроить SWIG directors, чтобы обеспечить межъязыковой полиморфизм и позволить чему-то написанному на Java реализовать интерфейс C++. Это класс, созданный с суффиксом " Impl" в моем коде.

Чтобы сделать вещи "правильными" для разработчиков Java, мы хотим по-прежнему использовать один и тот же тип для объектов C++ и Java, реализованных std::function. Даже если бы std::function::operator() были виртуальными, мы все равно не хотим, чтобы директора SWIG использовали этот тип напрямую, поскольку довольно часто передают std::function по значению, что приведет к проблемам с нарезкой типа . Поэтому, когда разработчик Java расширяет наши std::function объекты и переопределяет call, нам нужно сделать некоторую дополнительную работу, чтобы сделать так, чтобы C++, который использует это объект фактически вызывает реализацию Java, учитывая, что мы не можем просто использовать директора для обработки этого автоматически.

Так что то, что мы делаем, выглядит немного странно. Если вы создаете объект Java, предназначенный для реализации std::function, то для этого существует специальный защищенный конструктор. Этот конструктор оставляет переменную-член swigCPtr, которая обычно указывает на реальный объект C++ как 0 и вместо этого создает анонимный объект-оболочку, реализующий интерфейс "Impl" и просто прокси все возвращается к члену call объекта Java.

У нас есть еще одна типовая карта, которая применяется в Java везде, где мы передаем объект std::function В C++. Его роль состоит в том, чтобы определить, в каком случае мы имеем-c++ реализованный объект std::function, или Java-объект. В случае C++ он не делает ничего особенного, и все идет как обычно. В случае Java он берет прокси-объект и просит C++ преобразовать его обратно в другой, отдельный экземпляр std::function, который вместо этого подставляется.

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

Подвох здесь в том, что нетривиально автоматически создавать прокси-объект. Java имеетдинамические прокси-классы как часть API отражения, но они только реализуют интерфейсы, а не расширяют абстрактные классы. Одна из возможностей я пытался использовать был void call(Object ...args) на Java стороне, которая является вариативным аргумент функции. Хотя законно это, казалось, фактически не отменяло никаких дел в супер-классе, как это было бы необходимо.

В итоге я адаптировалнекоторые макросы , чтобы перебирать вариадические аргументы макросов так, как я хотел. Это довольно разумное решение, учитывая, что мы уже решили использовать аргументы макроса variadic C99 по другим причинам. Этот механизм используется всего четыре раза в моем решении, один раз в объявлении функции и один раз в вызове delgated как для Java, так и для C++. (C++ имеет отличные свойства пересылки, а Java нуждается в поиске по типовой карте, поэтому они различны в каждом конкретном случае).

Существует также пользовательская типовая карта для упрощения некоторого кода Java - в функции void не разрешается писать return other_void_function();, поэтому нам понадобились бы специальные функции void, если бы не это.

Итак, давайте посмотрим, как это выглядит на самом деле. Во-первых, это бег.java я использовал для тестирования, это только немного модифицировано из предыдущих примеров, чтобы добавить реализацию Java объекта std::function.
public class run extends Functor {
    public static void main(String[] argv) {
        System.loadLibrary("test");
        test.make_functor().call(1,2.5);

        new Functor(test.add_and_print_cb).call(3,4.5);

        Functor f = new run();
        test.do_things(f);
    }

    @Override
    public void call(int a, double b) {
        System.out.println("Java: " + a + ", " + b);
    }
}

Функция std_function.i теперь значительно больше со всеми изменениями, описанными выше:

%{
  #include <functional>
  #include <iostream>

  #ifndef SWIG_DIRECTORS
  #error "Directors must be enabled in your SWIG module for std_function.i to work correctly"
  #endif
%}

// These are the things we actually use
#define param(num,type) $typemap(jstype,type) arg ## num
#define unpack(num,type) arg##num
#define lvalref(num,type) type&& arg##num
#define forward(num,type) std::forward<type>(arg##num)

// This is the mechanics
#define FE_0(...)
#define FE_1(action,a1) action(0,a1)
#define FE_2(action,a1,a2) action(0,a1), action(1,a2)
#define FE_3(action,a1,a2,a3) action(0,a1), action(1,a2), action(2,a3)
#define FE_4(action,a1,a2,a3,a4) action(0,a1), action(1,a2), action(2,a3), action(3,a4)
#define FE_5(action,a1,a2,a3,a4,a5) action(0,a1), action(1,a2), action(2,a3), action(3,a4), action(4,a5)

#define GET_MACRO(_1,_2,_3,_4,_5,NAME,...) NAME
%define FOR_EACH(action,...) 
  GET_MACRO(__VA_ARGS__, FE_5, FE_4, FE_3, FE_2, FE_1, FE_0)(action,__VA_ARGS__)
%enddef

%define %std_function(Name, Ret, ...)

%feature("director") Name##Impl;
%typemap(javaclassmodifiers) Name##Impl "abstract class";

%{
  struct Name##Impl {
    virtual ~Name##Impl() {}
    virtual Ret call(__VA_ARGS__) = 0;
  };
%}

%javamethodmodifiers Name##Impl::call "abstract protected";
%typemap(javaout) Ret Name##Impl::call ";" // Suppress the body of the abstract method

struct Name##Impl {
  virtual ~Name##Impl();
protected:
  virtual Ret call(__VA_ARGS__) = 0;
};

%typemap(maybereturn) SWIGTYPE "return ";
%typemap(maybereturn) void "";

%typemap(javain) std::function<Ret(__VA_ARGS__)> "$javaclassname.getCPtr($javaclassname.makeNative($javainput))"
%typemap(javacode) std::function<Ret(__VA_ARGS__)> %{
  protected Name() {
    wrapper = new Name##Impl(){
      public $typemap(jstype, Ret) call(FOR_EACH(param, __VA_ARGS__)) {
    $typemap(maybereturn, Ret)Name.this.call(FOR_EACH(unpack, __VA_ARGS__));
      }
    };
    proxy = new $javaclassname(wrapper);
  }

  static $javaclassname makeNative($javaclassname in) {
    if (null == in.wrapper) return in;
    return in.proxy;
  }

  // Bot of these are retained to prevent garbage collection from happenign to early
  private Name##Impl wrapper;
  private $javaclassname proxy;
%}

%rename(Name) std::function<Ret(__VA_ARGS__)>;
%rename(call) std::function<Ret(__VA_ARGS__)>::operator();

namespace std {
  struct function<Ret(__VA_ARGS__)> {
    // Copy constructor
    function<Ret(__VA_ARGS__)>(const std::function<Ret(__VA_ARGS__)>&);

    // Call operator
    Ret operator()(__VA_ARGS__) const;

    // Conversion constructor from function pointer
    function<Ret(__VA_ARGS__)>(Ret(*const)(__VA_ARGS__));

    %extend {
      function<Ret(__VA_ARGS__)>(Name##Impl *in) {
    return new std::function<Ret(__VA_ARGS__)>([=](FOR_EACH(lvalref,__VA_ARGS__)){
          return in->call(FOR_EACH(forward,__VA_ARGS__));
    });
      }
    }
  };
}

%enddef

И тест.i немного расширен для проверки передачи Java - > C++ объектов std::function и включения директоров:

%module(directors="1") test
%include "std_function.i"

%std_function(Functor, void, int, double);

%{
#include <iostream>
void add_and_print(int a, double b) {
  std::cout << a+b << "\n";
}
%}

%callback("%s_cb");
void add_and_print(int a, double b);
%nocallback;

%inline %{
  std::function<void(int,double)> make_functor() {
    return [](int x, double y){
      std::cout << x << ", " << y << "\n";
    };
  }

  void do_things(std::function<void(int,double)> in) {
    in(-1,666.6);
  }
%}

Это скомпилировано и запущено, как и в предыдущих примерах. Стоит отметить, что мы перешли к написанию большого количества специфичного кода Java, хотя дизайн был бы работа для других языков если вы были нацелены на Python, то гораздо проще исправить некоторые из этих проблем, используя специфические функции Python.

Есть две вещи, которые я хотел бы улучшить:
  1. Использовать C++14 вариативная лямбды, чтобы избежать макрос препроцессора магии я использовал, чтобы держать их совместимость с C++11. Если у вас есть C++ 14 конструктор %extend становится:

    %extend {
      function<Ret(__VA_ARGS__)>(Name##Impl *in) {
        return new std::function<Ret(__VA_ARGS__)>([=](auto&& ...param){
          return in->call(std::forward<decltype(param)>(param)...);
        });
      }
    }
    

Когда речь заходит об использовании этого макроса с std::shared_ptr, как и ожидалось, сам макрос не нуждается в изменениях. Существует, однако, проблема с реализацией javadirectorin и directorin typemaps, которые применяются, которые действительно мешают вещам "просто работать". Это верно даже при построении глотка из "ствола". (Есть нерешенный вопрос о объединении директоров и shared_ptr )

Однако мы можем обойти это, добавив две дополнительные типовые карты в основной .i файл нашего модуля сразу после вызова %shared_ptr:
%shared_ptr(some::ns::TheThing);
%typemap(javadirectorin) std::shared_ptr<some::ns::TheThing> "new $typemap(jstype, some::ns::TheThing)($1,false)";
%typemap(directorin,descriptor="L$typemap(jstype, some::ns::TheThing);") std::shared_ptr<some::ns::TheThing> %{ 
  *($&1_type*)&j$1 = &$1;
%}

Первый из эти два typemaps на самом деле мертвый код, потому что мы заставили метод "call" быть абстрактным в нашем абстрактном классе, но легче исправить компиляцию этого метода, чем подавить его. Вторая типовая карта очень важна. Это существенно похоже на обычную типовую карту" out " в том, что она создает jlong, которая на самом деле является всего лишь представлением указателя C++, то есть подготавливает объект для перехода с C++ на Java.

Обратите внимание, что вам может потребоваться изменить атрибут дескриптора directorin typemap если вы используете пакеты в своем модуле, либо "L$packagepath/$typemap(...);", либо просто напишите его от руки.

Это должно устранить паразитные "SWIGTYPE_п_sstd__общая_ПТР..."тип генерируется и сейчас. Если у вас есть виртуальные функции, возвращающие объекты shared_ptr, вам также нужно написать для них типовые карты directorout и javadirectorout. Они могут быть основаны на обычной типовой карте "in".

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