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 PtrRec
s 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?
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:
create_left
and create_right
in this case - and not direct construction via StructureName()
because it will construct only common part of public API and not the rest of the data invisible to Python. Feeding such structure to specified function will also cause segfault due to structure bound crossing.