Python-SWIG与来自boost预处理器的预处理器宏

我在这里建议使用ToString实现的枚举:
How to convert an enum type variable to a string?
据我所知,它利用并运行良好.

当我尝试将宏包装并导出到用SWIG包装的Python库时,就会出现我的问题.类似的问题:SWIG errors because of preprocessor directive
在那里,解决方案是向SWIG接口添加标头/声明.到目前为止,我还没有成功.可能我只是不知道要添加什么.

尝试过:

%include <boost/preprocessor/config/config.hpp>
%include <boost/preprocessor/stringize.hpp>
%include <boost/preprocessor/seq/for_each.hpp>
%include <boost/preprocessor/seq/enum.hpp>

MWE:

最小

#ifndef MINIMAL_H
#define MINIMAL_H
#include <boost/preprocessor.hpp>

//Found this here: https://*.com/questions/5093460/how-to-convert-an-enum-type-variable-to-a-string

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem)    \
    case elem : return BOOST_PP_STRINGIZE(elem);

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                \
    enum name {                                                               \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    inline const char* ToString(name v)                                       \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,          \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }

DEFINE_ENUM_WITH_STRING_CONVERSIONS(my_enum, (A)(B))
#endif

minimal.cpp

#include <iostream> 
#include "minimal.h"

int main(){
    using namespace std;
    cout << A << ": " << ToString(A) << endl;
    cout << B << ": " << ToString(B) << endl;

}

最小

%module minimal
%{
#include "minimal.h"
%}
%include "minimal.h"

该错误不是很明显.第29行是my_enum的实际定义.

matthias@rp3deb:~/dvl/swig_boost_minimal$swig minimal.i
minimal.h:29: Error: Syntax error in input(1).

关于如何包装这个的任何建议?

解决方法:

如果要使SWIG读取boost / preprocessor.hpp,可以使用以下方法:

%module minimal
%{
#include "minimal.h"
%}
%include <boost/preprocessor.hpp>
%include "minimal.h"

由于默认情况下,SWIG不遵循#include指令. (您也可以使用-includeall使其遵循它们).在这种情况下,尽管我认为制作SWIG预处理程序对Boost预处理程序库使用的疯狂魔术有任何意义,但这是一个失败的原因.

相反,尽管我们可以尝试使用同样不错的东西,但使用“ Pythonic”语法.在本质上,我们要做的是仅为SWIG包装程序编写完全不同的DEFINE_ENUM_WITH_STRING_CONVERSIONS版本.但是它将与C看到的定义兼容.

为此,我首先将文件minimal.h分为两个文件.一种带有宏定义,另一种使用宏定义. (我们可以用不同的方式完成此操作,例如,使用#ifndef DEFINE_ENUM_WITH_STRING_CONVERSIONS或#ifndef SWIG包装宏定义,这将是同样有效的解决方案).

因此,我们现在有了enum.hh:

#ifndef ENUM_H
#define ENUM_H
#include <boost/preprocessor.hpp>

//Found this here: https://*.com/questions/5093460/how-to-convert-an-enum-type-variable-to-a-string
#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem)    \
    case elem : return BOOST_PP_STRINGIZE(elem);

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                \
    enum name {                                                               \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    inline const char* ToString(name v)                                       \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,          \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }
#endif

和minimal.h:

#ifndef MINIMAL_H
#define MINIMAL_H
#include "enum.h"

DEFINE_ENUM_WITH_STRING_CONVERSIONS(my_enum, (A)(B))
#endif

因此,您的minimal.cpp仍然可以像以前一样工作,但是现在我们可以编写一个至少可以编译的SWIG模块,即使它还没有做任何有用的事情:

%module minimal
%{
#include "minimal.h"
%}
%define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name,enumerators)
%enddef
%include "minimal.h"

当前,它具有一个存根,SWIG特定的宏,我们将填写该宏.我这样做有点丑陋,仅仅是因为我试图避免完全更改现有宏的定义/使用方式.

我作为起点制作的是另一个文件enum.i:

%include <std_vector.i>
%include <std_string.i>

%{
#include <vector>
#include <string>
#include <tuple>
%}

%define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name,enumerators)
%{
  typedef std::tuple<name,std::string> name ## _entry;
  struct name ## _helper {
    std::vector<name ## _entry> list;
    name ## _helper(const name value) {
      list.push_back(std::make_tuple(value,ToString(value)));
    }
    name ## _helper operator()(const name value) {
      list.push_back(std::make_tuple(value,ToString(value)));
      return *this;
    }
  };

  static const std::vector<name ## _entry> name ## _list = name ## _helper enumerators . list;
%}

struct name ## _entry {
  %extend {
    const unsigned long value {
      return std::get<0>(*$self);
    }
    const std::string& label {
      return std::get<1>(*$self);
    }
  }
};

%template(name ## vec) std::vector<name ## _entry>;

const std::vector<name ## _entry> name ## _list;

%enddef

这样的minimal.i只需要成为:

%module minimal

%{
#include "minimal.h"
%}

%include "enum.i"
%include "minimal.h"

宏所做的只是获取枚举器的值,该值将类似于(A)(B)并生成一些完全标准的代码(如果很古怪),C会将其扩展为std :: vector< std :: tuple&lt ; my_enum,std :: string>>.通过将第一个枚举成员映射到构造函数调用上,将其余的映射到重载的operator()上来完成.我们使用enum.h提供的ToString()查找字符串表示形式.最终,我们的宏具有足够的信息来包装元组向量,而这在Python内部是有意义的.

有了这个,我们可以做类似的事情:

import minimal
print ", ".join(("%s(%d)" % (x.label,x.value) for x in minimal.my_enum_list))

编译和运行后,结果如下:

A(0), B(1)

即足以开始编写了解C枚举的标签和值的Python代码.

但是,我们不要就此止步!为什么我故意将生成的向量称为my_enum_list而不是my_enum?因为还有更多我们现在可以做.

Python 2.7没有任何默认的“枚举”,但这并不妨碍我们将其包装为Pythonic和自然的枚举,以了解枚举的人.我通过阅读this other answer来实现了对Python 2.7枚举的支持.首先,我使用%pythoncode向文件中添加了一些通用的枚举支持例程(在最终源中标记为#1),但在SWIG宏之外,因为无需更改它.我还在SWIG宏(标记为#2)内添加了一个%pythoncode,该宏在每个实际枚举中均调用一次.为了完成这项工作,我必须将以前版本的const std :: vector转换为函数,以便可以在生成的Python的右侧访问它.最后,我必须向SWIG展示真实枚举的前向声明,以便说服它实际接受它作为函数的参数.最终结果是:

%include <std_vector.i>
%include <std_string.i>

%{
#include <vector>
#include <string>
#include <tuple>
%}

// #1
%pythoncode %{
class EnumValue(int):
  def __new__(cls,v,l):
    result = super(EnumValue,cls).__new__(cls,v)
    result._value = l
    return result
  def __str__(self):
    return self._value

def make_enum(name,enums):
    return type(name, (), enums)
%}

%define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name,enumerators)
%{
  typedef std::tuple<name,std::string> name ## _entry;
  struct name ## _helper {
    std::vector<name ## _entry> list;
    name ## _helper(const name value) {
      list.push_back(std::make_tuple(value,ToString(value)));
    }
    name ## _helper operator()(const name value) {
      list.push_back(std::make_tuple(value,ToString(value)));
      return *this;
    }
  };

  static const std::vector<name ## _entry> name ## _list() {
    return name ## _helper enumerators . list;
  }
%}

struct name ## _entry {
  %extend {
    const unsigned long value {
      return std::get<0>(*$self);
    }
    const std::string& label {
      return std::get<1>(*$self);
    }
  }
};

%template(name ## vec) std::vector<name ## _entry>;

const std::vector<name ## _entry> name ## _list();

// #2
%pythoncode %{
  name = make_enum('name', {x.label: EnumValue(x.value, x.label) for x in name ## _list()})
%}

enum name;

%enddef

我向minimal.i添加了一个函数,以证明它确实有效:

%module minimal

%{
#include "minimal.h"
%}

%include "enum.i"
%include "minimal.h"

%inline %{
  void foo(const my_enum& v) {
    std::cerr << "GOT: " << v << "\n";
  }
%}

最后用以下命令进行测试:

import minimal
print minimal.my_enum
print minimal.my_enum.A
print minimal.my_enum.B

minimal.foo(minimal.my_enum.B)

您会很高兴看到它的工作成果:

<class 'minimal.my_enum'>
A
B
GOT: 1

如果您使用的是Python 3,则可能有一种更好的represent enums方法,但是我现在将其作为练习留给读者.您显然也可以根据自己的喜好调整Python 2.7伪枚举.

上一篇:我如何用Swig包装器在C类指针上调用方法?


下一篇:PHP-SWIG致命错误:无法重新声明类