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://stackoverflow.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://stackoverflow.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< ; 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伪枚举.
标签:macros,boost,swig,python,c-4 来源: https://codeday.me/bug/20191121/2051473.html