I have a legacy C++ code base that I am using SWIG to generate python bindings for. In this code base there are enums all over that have specific values that are then used for binary operations.
A typical header file used looks something like this:
namespace doom
{
class Bar
{
public:
struct FooIdent
{
enum Ident
{
UnknownFoo = 0,
KnownFoo = 1,
MainFoo = 2,
SecondaryFoo = 3
};
};
enum FooPresence
{
Boo = 0x0,
Foo1 = 0x8000000000ULL,
Foo2 = 0x4000000000ULL,
Foo3 = 0x2000000000ULL,
FooWithA1 = 0x1000000000ULL,
FooWithA2 = 0x0800000000ULL,
FooWithA3 = 0x0400000000ULL,
FooWithA4 = 0x0200000000ULL,
FooWithB1 = 0x0100000000ULL,
FooWithB2 = 0x0080000000,
FooWithB3 = 0x0040000000
};
Bar();
void setVesODee( int ves, doom::Bar::FooPresence pr );
void setVesOGoo( int goo, doom::Bar::FooIdent::Ident ide );
int doSomething();
private:
int m_vdee;
int m_vgoo;
};
} // namespace doom
The corresponding .cpp
file would then be:
#include "bar.h"
#include <iostream>
namespace doom
{
Bar::Bar()
{
m_vdee = 0;
m_vgoo = 0;
}
void Bar::setVesODee( int ves, doom::Bar::FooPresence pr ) {
m_vdee = static_cast< doom::Bar::FooPresence >( ves | pr );
}
void Bar::setVesOGoo( int goo, doom::Bar::FooIdent::Ident ide ) {
m_vgoo = static_cast< doom::Bar::FooIdent::Ident >( goo | ide );
}
int Bar::doSomething() {
return m_vgoo + m_vdee;
}
} // namespace doom
int main() {
doom::Bar b = doom::Bar();
b.setVesODee(3, doom::Bar::FooWithB2);
b.setVesOGoo(4, doom::Bar::FooIdent::MainFoo);
int c = b.doSomething();
std::cout << c << std::endl;
return 0;
}
The .i
file would look something like this:
%feature ("flatnested");
%module bar
%{
#include "bar.h";
%}
%rename("Bar_%s", %$isnested) "";
%include "bar.h"
I build using the following commands:
swig -python -c++ -py3 bar.i
g++ -fPIC -c $(pkg-config --cflags --libs python3) bar.cpp bar_wrap.cxx
g++ -shared -o _bar.so bar.o bar_wrap.o
And I then test the code using:
import bar
for i in dir(bar.Bar_FooIdent):
if i[0].isupper():
print(f'{i} = 0x{getattr(bar.Bar_FooIdent, i):X}')
print()
for i in dir(bar.Bar):
if i[0].isupper():
print(f'{i} = 0x{getattr(bar.Bar, i):X}')
This outputs:
KnownFoo = 0x1
MainFoo = 0x2
SecondaryFoo = 0x3
UnknownFoo = 0x0
Boo = 0x0
Foo1 = 0x0
Foo2 = 0x0
Foo3 = 0x0
FooWithA1 = 0x0
FooWithA2 = 0x0
FooWithA3 = 0x0
FooWithA4 = 0x0
FooWithB1 = 0x0
FooWithB2 = 0x-80000000
FooWithB3 = 0x40000000
It seems to be that SWIG does not convert the ULL literals correctly, and I cannot figure out how to tell SWIG to interpret these as larger types.
I am not very comfortable using SWIG but I have been able to make do for a while now and am able to generate most of the code I need. I searched the documentation and questions online, but I have not been able to make this work. Any pointers for making this conversion happen?
SWIG by default treats enum constants as int
, even if you override with:
enum FooPresence : unsigned long long { ... }
This generates code in the test_wrap.cxx
file like:
SWIG_Python_SetConstant(d, "Foo1",SWIG_From_int(static_cast< int >(Foo1)));
SWIG_Python_SetConstant(d, "Foo2",SWIG_From_int(static_cast< int >(Foo2)));
I was able to get correct results by overriding the constcode
typemap to assume 64-bit integers instead.
test.i
%module test
%typemap(constcode) int %{SWIG_Python_SetConstant(d, "$1",PyLong_FromLongLong(static_cast<long long>($1)));%}
%inline %{
enum FooPresence : unsigned long long
{
Foo1 = 0x8000000000ULL,
Foo2 = 0x4000000000ULL,
Foo3 = 0x2000000000ULL,
FooWithA1 = 0x1000000000ULL,
FooWithA2 = 0x0800000000ULL,
FooWithA3 = 0x0400000000ULL,
FooWithA4 = 0x0200000000ULL,
FooWithB1 = 0x0100000000ULL,
FooWithB2 = 0x0080000000,
FooWithB3 = 0x0040000000
};
%}
You can see the SWIG typemap matching search by using the -debug-tmsearch
flag when building. Note that it is searching for int Foo1
and not enum FooPresence
or long long Foo1
:
C:\test>swig -c++ -python -debug-tmsearch test.i
test.i(8) : Searching for a suitable 'consttab' typemap for: int Foo1
Looking for: int Foo1
Looking for: int
Looking for: SWIGTYPE Foo1
Looking for: SWIGTYPE
None found
test.i(8) : Searching for a suitable 'constcode' typemap for: int Foo1
Looking for: int Foo1
Looking for: int
Using: %typemap(constcode) int
...
Demo:
>>> import test
>>> hex(test.Foo1)
'0x8000000000'
>>> hex(test.FooWithA1)
'0x1000000000'