Search code examples
pythonc++swig

SWIG - C++ to python - enum that has ULL values being converted to 0


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?


Solution

  • 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'