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

我已经看到了很多类似的问题,但是没有找到解决我特定问题的方法。 我试图SWIGify一些使用std :: function的C ++ 11代码,所以我可以在我的Java应用程序中使用它。

我遇到过像这样的共享指针:

virtual std::shared_ptr getTheThing(unsigned short thingID); 

并使用shared_ptr指令成功处理它们,如下所示:

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

我遇到过这样的共享指针向量:

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

并使用如下模板成功处理它们:

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

现在我有一个像这样的方法:

  void registerThingCallback(std::function<void(std::shared_ptr) > func); 

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

thing_callback.h

 #include  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) > func); }; } } 

更新

根据Flexo下面的答案,我更接近解决方案。 我能够让下面的例子完全像宣传的那样工作。 我尝试将它合并到我的实际代码中,但遇到了问题。 为了扩展我之前的简化示例,这里是我对TheThing的定义:

test_thing.h

 #ifndef THE_THING_H #define THE_THING_H #include  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 "new $typemap(jstype, some::ns::TheThing)($1,false)"; %typemap(directorin,descriptor="Lsome.ns.typemap(jstype, some::ns::TheThing);") std::shared_ptr %{ *($&1_type*)&j$1 = &$1; %} %include "test_thing.h" %include "thing_callback.h" %{ #include  #include "test_thing.h" #include "thing_callback.h" %} %std_function(Functor, void, std::shared_ptr); %{ #include  void add_and_print(std::shared_ptr thing) { std::cout << "here\n"; } %} %callback("%s_cb"); void add_and_print(std::shared_ptr); %nocallback; %inline %{ std::function<void(std::shared_ptr)> make_functor() { return [](std::shared_ptr){ std::cout << "make functor\n"; }; } void do_things(std::function<void(std::shared_ptr)> 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。 现在,问题和答案之间有一个完整的工作实例,说明我追求的是什么。

我们可以通过一些工作来做到这一点。 我在这里的答案是我之前的一个回答的更广义的版本,针对特定实例查看此问题并针对Python。

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

  1. 我们希望能够从Java代码中调用std::function对象。
  2. 包装的std::function对象需要像任何其他对象一样传递,包括跨越任一方向的语言边界。
  3. 应该可以在Java中编写std::function对象,可以将其传递回C ++,而无需修改适用于std::function对象的现有C ++代码(即维护std::function交叉语言的类型擦除)
  4. 我们应该能够使用C ++指向函数类型的指针在Java中构造std::function对象。

我将研究这些并展示我们如何实现这一目标。 在可能的情况下,我将保持解决方案语言不可知。

出于讨论的目的,我正在浏览你问题的shared_ptr部分,它实际上并没有改变一些东西,因为你已经有了shared_ptr工作,这实际上足以在这个场景中使用它,它只会让我的例子更多不必要的冗长。

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

 %module test %include "std_function.i" %std_function(Functor, void, int, double); %{ #include  %} %inline %{ std::function 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

编写第一个版本的“std_function.i”实际上并不太棘手。 获得基本工作要求#1和#2所需要的只是:

 %{ #include  %} %define %std_function(Name, Ret, ...) %rename(Name) std::function; %rename(call) std::function::operator(); namespace std { struct function { // Copy constructor function(const std::function&); // Call operator Ret operator()(__VA_ARGS__) const; }; } %enddef 

这包括生成的包装器代码中的C ++头文件一次,然后设置宏以在接口中使用。 在这种使用场景中,SWIG对C ++ 11可变参数模板的支持实际上对我们没什么帮助,因此我编写的宏基本上使用C99可变参数宏参数重新实现了默认的模板扩展function (支持得更好)。 巧合的是,这意味着我们编写的SWIG代码将适用于2.x甚至一些1.3.x版本。 (我用2.x测试过)。 即使/当您的SWIG版本确实具有与std::function一起使用的%template支持时,保留此宏对于使其实际可调用的其余粘合剂仍然有用。

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

唯一要做的是将operator()重命名为与目标语言匹配的东西,例如,将Java重命名为仅称为“call”的常规函数​​,或者如果您将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有另一个构造std::function ,它接受兼容的函数指针:

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

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

 %module test %include "std_function.i" %std_function(Functor, void, int, double); %{ #include  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 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); } } 

我们在这一点上成功编译和运行。

(请注意,在此时创建一​​些以“SWIGTYPE_p_f _...”开头的Java类是正常的,也是可取的 - 这些Java包装了指向函数构造函数和回调常量的“指向函数的指针”类型


要求#3是事情开始变得棘手的地方。 基本上我们遇到了同样的问题,因为我之前使用SWIG生成Java接口 ,但现在我们希望在宏中更普遍地进行。

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

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

为了让Java开发人员“感觉正确”,我们仍然希望对C ++和Java实现的std::function对象使用相同的类型。 即使std::function::operator()是虚拟的,我们仍然不希望SWIG导向器直接使用该类型,因为传递std::function by value是很常见的,这会导致类型切片问题 。 因此,当Java开发人员扩展我们的std::function对象并覆盖call我们需要做一些额外的工作来使它使得使用该对象的C ++实际调用Java实现,因为我们不能只使用director来自动处理它。

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

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

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

这里的问题是自动构造代理对象并非易事。 Java将动态代理类作为reflectionAPI的一部分,但这些只实现接口,而不是扩展抽象类。 我尝试使用的一种可能性是Java端的void call(Object ...args) ,它是一个可变参数函数参数。 虽然合法,但这似乎并没有真正覆盖超级类别中的任何案例。

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

还有一个自定义类型映射来简化一些Java代码 - 在void函数中写入return other_void_function();是不合法的return other_void_function(); ,所以我们需要特殊情况的void函数,如果不是那样的话。

那么让我们看看现实中的情况。 首先是我用于测试的run.java,它只是稍微修改了以前的示例,以添加std::function对象的Java实现。

 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  #include  #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(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 "$javaclassname.getCPtr($javaclassname.makeNative($javainput))" %typemap(javacode) std::function %{ 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; %rename(call) std::function::operator(); namespace std { struct function { // Copy constructor function(const std::function&); // Call operator Ret operator()(__VA_ARGS__) const; // Conversion constructor from function pointer function(Ret(*const)(__VA_ARGS__)); %extend { function(Name##Impl *in) { return new std::function([=](FOR_EACH(lvalref,__VA_ARGS__)){ return in->call(FOR_EACH(forward,__VA_ARGS__)); }); } } }; } %enddef 

并且test.i稍微扩展以validationJava - > C ++传递std::function对象并启用director:

 %module(directors="1") test %include "std_function.i" %std_function(Functor, void, int, double); %{ #include  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 make_functor() { return [](int x, double y){ std::cout << x << ", " << y << "\n"; }; } void do_things(std::function in) { in(-1,666.6); } %} 

这与前面的示例一样编译和运行。 值得注意的是,我们已经开始编写大量Java特定代码 - 尽管如果您使用Python来设计适用于其他语言,使用Python特定function修复其中一些问题要简单得多。

我想改进两件事:

  1. 使用C ++ 14可变参数lambdas来避免我用来保持它们与C ++ 11兼容的宏预处理器魔法。 如果您有C ++ 14,则%extend构造函数变为:

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

当按照预期使用std::shared_ptr这个宏时,宏本身不需要更改。 然而,应用javadirectorin和directorin类型映射的实现存在问题,这确实阻止了“正常工作”。 即使从“主干”构建SWIG也是如此。 (关于组合导演和shared_ptr的问题很突出)

我们可以通过在调用%shared_ptr之后在我们模块的主.i文件中添加两个额外的类型图来解决这个问题:

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

这两个类型映射中的第一个实际上是死代码,因为我们强制“调用”方法在抽象类中是抽象的,但是修复此方法的编译比修改它更容易。 第二个类型图很重要。 它与普通的“out”类型jlong基本类似,它创建了一个jlong ,它实际上只是C ++指针的表示,即它准备一个对象从C ++转到Java。

请注意,如果在模块中使用包,则可能需要修改directorin typemap的descriptor属性, "L$packagepath/$typemap(...);" 或者只是手工编写。

这应该删除现在生成的虚假“SWIGTYPE_p_sstd__shared_ptr ...”类型。 如果你有返回shared_ptr对象的虚函数,你也需要为它们编写directorout和javadirectorout类型图。 这些可以基于普通的“in”类型映射。

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