pythoncpointerspolymorphismswig

Is it possible to downcast SwigObject to a concrete type?


I have a couple of C files:

/*mid.h*/
#ifndef mid_h
#define mid_h

#include <stdlib.h> typedef struct PtrRec *Ptr, PtrStruct;

#endif /*mid_h*/ 
/*left.h*/
#ifndef left_h
#define left_h
    
#include "mid.h"

Ptr create_left();
void print_left(Ptr,int);

#endif /*left_h*/


/*left.c*/
#include "left.h"
#include <stdio.h>

struct PtrRec {
    Ptr next;
    int g;
};

void print_left(Ptr left) {
    printf("%zu left{%p}->%p %d\n",sizeof(*l), l, l->next, l->g);
}

Ptr create_left() {
    Ptr rec;
    rec= calloc(1, sizeof(*rec)); 
    rec->g = 33;
    return rec; 
}


/*right.h*/
#ifndef right_h
#define right_h

#include "mid.h"

Ptr create_right();
void print_right(Ptr);

#endif /*right_h*/


/*right.c*/
#include "right.h"
#include <stdio.h>

struct PtrRec {
    int i,j,k;
    Ptr next;
}; 

void print_right(Ptr r) {
    printf("%zu right {%p}->%p %d %d %d\n",sizeof(*r), r, r->next, r->i, r->j, r->k);
}

Ptr create_right() {
    Ptr rec;
    rec= calloc(1, sizeof(*rec)); 
    rec->i = 31;
    rec->j = 34;
    rec->k = 36;
    return rec; 
}

I am making an library and importing interface via SWIG.


%module asd
%{
#include "mid.h"
#include "left.h"
#include "right.h"
%}

%include "mid.h"
%include "left.h"
%include "right.h"
%init %{
    Py_Initialize();
%}

And make a assemble it via:


PYTHON_INCLUDE="/usr/include/python3.11/"

swig -v -python -o asd_wrap.c all.i

clang --shared -I${PYTHON_INCLUDE} -o _asd.so left.c right.c asd_wrap.c

So I can import it from python and call some functions:

from asd import *

left = create_left()
right = create_right()
print_left(left)
print_right(right)

So, is there a way to extend such code to make PtrRecs distinguishable from the python side? Any way to downcast it and append extra method, accessible from python for both of them, like attach some get_size method so they at least could print their sizes. Is it possible with such setup or it will require actually make them structures with different names and explicitly put them into header files?


Solution

  • In a short - no you can't cast it the way it is. However, it is possible to make common interface for the pointer. All things visible to SWIG should be placed into header file, so we can make stub implementation of the structure with common fields and some type marker to allow to deduce how to process them.

    /*swig_api.h*/
    
    #ifndef api_h
    #define api_h
    
    typedef enum {
        LEFT,
        RIGHT,
    } ptr_type;
    
    struct PtrRec {
        Ptr next;
        ptr_type type;
    };
    
    void change_ptr(Ptr ptr);
    
    #endif
    
    /*api.c*/
    #include "left.h"
    #include "right.h"
    #include "api.h"
    
    void change_ptr(Ptr ptr) {
        if (!ptr) return;
        /*at this scope only stub fields are visible
        * but will be autocasted inside concrete calls
        */
        switch (ptr->type) {
            case LEFT: change_left(ptr); break;
            case RIGHT: change_right(ptr); break;
            default:
                break;
        }
    }
    
    

    And obviously add them to code generation for swig

    %module asd
    %{
    ...
    #include "api.h"
    ...
    %}
    
    ...
    %include "api.h"
    ...
    
    

    To prevent any segfaults and exception in python code all common fields we order concrete implementation the same as our stub and add type markers. So, fixed versions will look like this

    /*left.c*/
    ...
    
    struct PtrRec {
        Ptr next;
        ptr_type type;
        int g;
    };
    
    Ptr create_left() {
        Ptr rec;
        rec= calloc(1, sizeof(*rec)); 
        rec->type = LEFT;  /* don't forget type tag*/
        rec->g = 33;
        return rec; 
    }
    void change_left(Ptr ptr) {
        if (!ptr) return;
        ptr->g = 1387;
    }
    
    /*right.c*/
    ...
    struct PtrRec {
        Ptr next;
        ptr_type type;
        int i,j,k;
    };
    
    Ptr create_right() {
        Ptr rec;
        rec= calloc(1, sizeof(*rec)); 
        rec->type = RIGHT; /* don't forget type tag*/
        rec->i = 31;
        rec->j = 34;
        rec->k = 36;
        return rec; 
    }
    
    void change_right(Ptr ptr) {
        if (!ptr) return;
        ptr->i = 61947;
    }
    
    

    So, from python we finally can see necessary fields:

    from asd import *
    left = create_left();
    right = create_right();
    
    change_ptr(left);  # internally will change left.g to 1387
    change_ptr(right); # internally will change right.i to 61947
    
    

    Using this trick we can further extend our API for python and have scoped polymorphism at the same time. Important notes: