如何使用SWIG包装std :: function对象?

时间:2015-09-18 05:07:22

标签: java c++ c++11 swig std-function

我看过很多类似的问题,但没有找到解决我特定问题的方法。我试图SWIGify一些使用std :: function的C ++ 11代码,所以我可以在我的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);

我无法让SWIG正确包装它。我已经尝试使用%回调,导演,%模板和%内联功能代码,因为我已经看到所有这些事情的例子,但是还没有能够得到任何似乎接近工作的东西。如果有帮助(消毒和减少),这里有一个关于函数调用的更多上下文:

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 */

这是我的.i文件。为了让尽可能少的移动部件,我基本上只是从下面的答案中提供的代码中获取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 << "here\n";
}
%}

%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 functor\n";
    };
  }

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

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项目。这些脚本位于我的项目的根目录中,header和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 个答案:

答案 0 :(得分:9)

我们可以通过一些工作来做到这一点。我的答案是我的previous of mine answer的更通用版本,针对特定实例查看此问题并以Python为目标。

我假设你想要通过std::function的包装实现四件事:

  1. 我们希望能够从Java代码中调用std::function个对象。
  2. 包裹的std::function对象需要像任何其他对象一样被传递,包括跨越任一方向的语言边界。
  3. 应该可以在Java中编写std::function个对象,这些对象可以传递回C ++,而无需修改适用于std::function个对象的现有C ++代码(即维护std::function的类型擦除1}}交叉语言)
  4. 我们应该能够使用C ++指向函数类型的指针在Java中构造std::function个对象。
  5. 我将通过这些工作并展示我们如何实现这一目标。在可能的情况下,我也会保持解决方案语言的不可知性。

    出于讨论的目的,我对问题的shared_ptr部分进行了修饰,但实际上并没有改变事情,因为你已经shared_ptr工作了{&1 #39;实际上在这种情况下也足以使用它,它只会使我的例子更加冗长不必要。

    我正在努力解决的问题实际上是在SWIG中现有的shared_ptr支持之后建模的。我已经整理了一个测试界面来说明它将如何使用:

    %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";
        };
      }
    %}
    

    基本上使用这个你需要做的就是包含文件&#34; std_function.i&#34;然后使用带有参数的宏%std_function

    %std_function(Name, Ret, ...)
    

    在每个要包装的std::function模板的实例化时调用一次,其中Name是您要在Java中调用的类型,Ret是返回类型,然后是剩余的(可变参数) )arguments是函数的输入。所以在上面的测试界面中,我基本上想要包装std::function<void(int,double)>

    编写第一版&#34; std_function.i&#34;实际上并不太棘手。获得基本工作要求#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可变参数宏参数重新实现the default template expansion functionality(这很多更好的支持)。巧合的是,这意味着我们正在编写的SWIG代码将适用于2.x甚至一些1.3.x版本。 (我用2.x测试过)。即使你的SWIG版本确实具有%template支持,但std::function支持std:function,保留此宏仍然对其他可以实际调用的粘合剂有用。

    operator()模板的手动扩展仅限于我们关注的用途:实际的operator()和可能派上用场的复制构造函数。

    要做的唯一其他事情是将__call__重命名为与目标语言匹配的内容,例如for Java将其重命名为一个名为&#34; call&#34;的常规函数​​,或者如果您将Python定位到public class run { public static void main(String[] argv) { System.loadLibrary("test"); test.make_functor().call(1,2.5); } } 或使用tp_slots(如果需要)。

    这足以让我们的界面工作,为了演示它我写了一点Java:

    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
    

    我编译的是:

    std::function

    并且有效。

    此时要求#4很容易从列表中删除。我们需要做的就是告诉SWIG // Conversion constructor from function pointer function<Ret(__VA_ARGS__)>(Ret(*const)(__VA_ARGS__)); 中的另一个构造函数接受兼容的函数指针:

    %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";
        };
      }
    %}
    

    然后我们可以在SWIG中使用%callback mechanism,我们的测试接口文件变为:

    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是:

    std::function

    此时我们已成功编译和运行。

    (注意,这是正常的,并且希望看到此时创建的一些Java类以名称&#34开头; SWIGTYPE_p_f _...&#34; - 这些将&#34;指针包装到函数&#34;指针用于函数构造函数和回调常量的类型)

    要求#3是事情开始变得棘手的地方。基本上我们遇到的问题与我之前回答的问题on making SWIG generate an interface in Java基本相同,除非我们现在更希望在宏中更广泛地进行此操作。

    事实证明,在这个例子中,因为我们想要生成的接口非常简单,我们可以在宏中使用一些技巧让SWIG为我们生成它。

    为了完成这项工作,我们需要做的主要事情是设置SWIG导向器以提供跨语言多态性,并允许用Java编写的东西来实现C ++接口。这是使用后缀&#34; Impl&#34;生成的类。在我的代码中。

    让事情变得正确&#34;对于Java开发人员,我们仍希望对C ++和Java实现的std::function::operator()对象使用相同的类型。即使std::function是虚拟的,我们仍然不希望让SWIG导演直接使用该类型,因为通过std::function传递call非常常见,这会导致类型{{3 }}。因此,当Java开发人员扩展我们的std::function对象并覆盖swigCPtr时,我们需要做一些额外的工作来使得使用该对象的C ++实际上调用Java实现,因为我们不能只需使用导演自动处理。

    所以我们所做的看起来有点奇怪。如果构造一个旨在实现call的Java对象,那么就有一个特殊的受保护构造函数。这个构造函数离开了std::function成员变量,它通常指向一个真正的C ++对象为0,而是创建一个匿名包装器对象来实现&#34; Impl&#34;接口并简单地将所有内容代理回Java对象的std::function成员。

    我们还有另一个类型映射,在Java中,我们将std::function对象传递给C ++。它的作用是检测我们有哪种情况 - 一个C ++实现的void call(Object ...args)对象,或者一个Java对象。在C ++的情况下,它没有什么特别的,一切都正常进行。在Java的情况下,它接受代理对象并要求C ++将其转换回另一个替代的return other_void_function();实例,而不是替换它。

    这足以让我们在两种语言中都能获得我们想要的行为,而不会在任何一方感到奇怪(除了大量透明的机械提升之外)。

    这里的问题是自动构建代理对象是非常重要的。 Java将slicing problems作为反射API的一部分,但这些只实现接口,而不是扩展抽象类。我尝试使用的一种可能性是Java端的std::function,这是一个可变函数参数。虽然合法,但似乎并没有真正覆盖超级类中的任何案例。

    我最终做的是调整dynamic proxy classes以我想要的方式迭代可变参数宏参数。这是一个相当明智的解决方案,因为我们已经决定出于其他原因使用可变参数C99宏参数。这个机制在我的解决方案中总共使用了四次,一次在函数声明中,一次在Java和C ++的delgated调用中。 (C ++保留了完美的转发属性,Java需要执行类型映射查找,因此它们在每种情况下都是不同的。)

    还有一个自定义类型映射来简化一些Java代码 - 在void函数中编写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); } } 是不合法的,所以我们需要特殊情况的void函数,如果它不是那样的。

    让我们看看现实中的样子。首先是我用于测试的run.java,它只是稍微修改了以前的示例,以添加%{ #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 对象的Java实现。

    std::function

    std_function.i现在变得更大,上面列出了所有变化:

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

    并且test.i稍微扩展以验证Java - &gt; C ++传递%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)...);
        });
      }
    }
    

    这与前面的示例一样编译和运行。值得注意的是,我们已经编写了大量Java特定代码 - 尽管如果你的目标是Python,设计可以用于其他语言,使用它来解决其中的一些问题要简单得多Python特有的功能。

    我希望改进两件事:

    1. 使用C ++ 14可变参数lambda来避免宏预处理器的魔力我曾经用它来保持它们与C ++ 11兼容。如果你有C ++ 14 std::shared_ptr构造函数变为:

      %shared_ptr
    2. 当按照预期使用此%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; %} 宏时,宏本身不需要更改。然而,应用的javadirectorin和directorin类型图的实现存在问题,这确实阻止了“只是工作”的事情。即使使用来自&#34; trunk&#34;的SWIG构建也是如此。 (some macros

      上有一个悬而未决的问题

      我们可以通过在调用jlong后立即在我们模块的主.i文件中添加两个额外的文字图来解决这个问题:

      "L$packagepath/$typemap(...);"

      这两个类型映射中的第一个实际上是死代码,因为我们强制执行&#34; call&#34;在我们的抽象类中抽象的方法,但是修复此方法的编译比解决它的方法更容易。第二个类型图很重要。它与普通&#34; out&#34;基本类似。 typemap,它创建一个Functor,它实际上只是C ++指针的表示,即它准备一个对象从C ++转到Java。

      请注意,如果您在模块中使用软件包,可能需要修改directorin typemap的descriptor属性,或者{{1}}或者只需手动编写。

      这应该删除虚假的&#34; SWIGTYPE_p_sstd__shared_ptr ...&#34;现在生成的类型也是如此。如果您有返回shared_ptr对象的虚函数,那么您也需要为它们编写directorout和javadirectorout类型映射。这些可以基于正常的&#34; in&#34;类型映射。

      这足以让我自己的简单测试使用修改后的{{1}}来工作,至少我今天从主干检出了我的SWIG版本。 (我对2.0.x的测试失败,我没有花太多精力使其工作,因为这是一个已知的工作进展区域。)