当前位置: 首页 > 面试题库 >

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

梁丘佑运
2023-03-14
问题内容

我已经看到了很多类似的问题,但是还没有找到解决我特定问题的方法。我正在尝试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正确包装它。我已经尝试过使用%callback,director,%template和%inline功能代码,因为我已经看到了所有这些示例,但是还无法获得任何看起来可行的功能。如果有帮助(经过消毒和简化),则可以在函数调用周围找到一些上下文:

something_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值和下面答案中提供的代码加倍,然后将它们替换为指向我对象的共享指针。

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项目。这些脚本位于我项目的根目录中,头文件和Swig文件位于src / main / resources中,java源文件位于src / main
/ java中,而Java编译的类位于target / classs中。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。现在在问题和答案之间有一个我所追求的完整的工作示例。


问题答案:

尽管SWIG本身不提供std_function.i,但我们可以通过一些工作自己构建一个。我的答案是我以前的答案的更广义的版本,针对特定实例针对这个问题并针对Python。我将进行几次迭代,这些迭代定义了%std_function用于通用std::function包装的宏。

我假设您要通过的包装实现四件事std::function,这将成为我们的主要要求:

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

我将通过这些工作,展示如何实现这一目标。在可能的情况下,我也将保持解决方案语言的不可知性。

出于讨论的目的,我将掩盖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.i”的第一个版本实际上并不那么棘手。获得基本工作要求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可变参数模板
的支持实际上对我们没有太大帮助,因此,我编写的宏基本上使用C99可变参数宏参数(支持更好)重新实现了默认模板扩展功能。巧合的是,这意味着我们正在编写的SWIG代码将在2.x甚至1.3.x版本中使用。(我用2.x测试过)。即使/当您的SWIG版本确实具有%template支持std::function保留该宏的功能时,对于仍使其实际可调用的其余粘合仍然有用。

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

唯一要做的另一件事是重命名operator()为与目标语言匹配的内容,例如,对于Java,将其重命名为只是一个名为“
call”的常规函数​​,或者如果您需要将Python定位为__call__tp_slots或使用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__));

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

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

至此,我们可以成功编译并运行相同的代码。

(请注意,此时看到一些以名称“ SWIGTYPE_p_f _…”开头的Java类是正常且合乎需要的-
这些类包装了“指向函数的指针”类型,这些指针由指向函数构造函数和回调常量的指针使用)

要求#3是事情开始变得棘手的地方。本质上,我们遇到的问题与我之前使SWIG在Java中生成接口时回答的问题相同,只是现在我们希望在宏中更通用地实现它。

事实证明,在这种情况下,因为我们要生成的接口非常简单,所以我们可以在宏中使用一些技巧使SWIG为我们生成它。

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

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

因此,我们所做的工作看起来有些奇怪。如果构造要实现的Java对象,std::function则有一个特殊的受保护的构造函数。该构造函数留下了swigCPtr成员变量,该成员变量通常将一个真正的C
++对象指向0,然后创建一个匿名包装对象,该对象实现“ Impl”接口,并将所有内容简单地代理回callJava对象的成员。

在Java中,我们只要将std::function对象传递给C 的任何地方,都可以使用另一个类型映射。它的作用是检测我们遇到的情况-一个C
实现的std::function对象,或一个Java 实现的对象。在C
情况下,它没有做任何特殊的事情,并且一切正常进行。在Java情况下,它使用代理对象,并要求C
将其转换回另一个单独的std::function实例,该实例将被替换。

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

这里要注意的是,自动构造代理对象并非易事。Java将动态代理类作为反射API的一部分,但是它们仅实现接口,而不扩展抽象类。我尝试使用的一种可能性是void call(Object ...args)在Java方面,它是可变参数函数。虽然合法,但这似乎并没有实际覆盖超类中的任何情况(需要)。

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

还有一个自定义的类型映射来简化某些Java代码-在void函数中,编写该函数是不合法的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 <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

然后将test.i稍微扩展以验证std::function对象的Java-> C ++传递并启用导向器:

%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可变参数lambda可以避免宏预处理器的魔力,我一直在使它们与C 11兼容。如果您具有C ++ 14,则%extend构造函数将变为:
    %extend {
    

    function (Name##Impl *in) {
    return new std::function ( ={
    return in->call(std::forward (param)…);
    });
    }
    }

std::shared_ptr按预期使用此宏时,宏本身无需更改。但是,所应用的javadirectorin和Directorin类型映射的实现存在问题,它们确实阻止了事情“正常运行”。即使使用“
trunk”构建SWIG,也是如此。(将Director和shared_ptr组合在一起时存在一个悬而未决的问题)

不过,我们可以通过在调用以下命令后在模块的主.i文件中添加两个附加的typemap来解决此问题%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;
%}

这两个类型映射中的第一个实际上是无效代码,因为我们在抽象类中强制将“
call”方法抽象化,但是修复该方法的编译比抑制它更容易。第二个类型图很重要。它与普通的“输出”类型图基本相似,因为它创建了一个jlong实际上只是C 指针的表示形式,即它准备了一个从C 到Java的对象。

请注意,如果您在模块中使用包,则可能需要修改Directorin类型图的描述符属性,或者"L$packagepath/$typemap(...);"手工编写。

这也应该删除现在生成的伪造的“ SWIGTYPE_p_sstd__shared_ptr
…”类型。如果您具有返回shared_ptr对象的虚函数,则还需要为其编写Directorout和javadirectorout类型映射。这些可以基于普通的“输入”类型映射。

这足以完成修改后的我自己的简单测试Functor,至少在今天从后备箱中检出我的SWIG版本时。(我在2.0.x上的测试失败了,我没有付出太多努力使其工作,因为这是一个正在进行的工作领域)。



 类似资料:
  • 问题内容: 我正在使用SWIG从Java访问C ++代码。 公开非常量引用传递的std :: string参数的最简单方法是什么? 我有参考暴露为Java数组传递原语,感谢typemaps.i,和s ^暴露,感谢std_string.i。但是非const std :: string&被公开为不透明指针类型。 当前: 期望的: 更新 :我找到 了 解决方案,如下所述。但是,它花费了超过几秒钟的时间。

  • 所有报价均来自N3797。 4/3[conv] 表达式e可以隐式转换为类型T,当且仅当声明T=e;对于一些发明的临时变量t来说 这意味着任何表达式都不能隐式转换为,因为对于所有表达式都是非法的。即使是类型的表达式,例如,也是如此。 因此,类型的表达式不能隐式转换为。 这让我们想到: 20.9.2/2要求[func.require] 将INVOKE(f, t1, t2,..., tN, R)定义为隐

  • 标准库函数bind()和function()定义于头文件<functional>中(该头文件还包括许多其他函数对象),用于处理函数及函数参数。bind()接受一个函数(或者函数对象,或者任何你可以通过”(…)”符号调用的事物),生成一个其有某一个或多个函数参数被“绑定”或重新组织的函数对象。(译注:顾名思义,bind()函数的意义就像它的函数名一样,是用来绑定函数调用的某些参数的。)例如: int

  • 问题 你想让你写的C代码作为一个C扩展模块来访问,想通过使用 Swig包装生成器 来完成。 解决方案 Swig通过解析C头文件并自动创建扩展代码来操作。 要使用它,你先要有一个C头文件。例如,我们示例的头文件如下: /* sample.h */ #include <math.h> extern int gcd(int, int); extern int in_mandel(double x0,

  • 显然,SWIG不理解并破坏Python绑定。例如,这适用于C: 但是这会破坏代码(除了为了简单起见有不同的行为,但这不是重点): (普通)未绑定函数的情况相同。那么,有人知道解决这个问题的方法吗?

  • 我使用SWIG类型映射包装一个类,使用SWIG提供的“std_vector.I”包装这个类的。目标语言是Python。除了我不能在向量上迭代之外,一切似乎都正常。 我创建了一个关于复数的最小示例(仅为方便起见,它与我的真实项目无关)。 这是我想包装的C类: 这是我的SWIG接口文件: 我可以使用以下Python代码简单地测试typecheck/in/out typemaps: 正如您在输出中看到的