mifth
mifth

Reputation: 292

Ctypes: Extended Struct/Pointer (Inheritance)

I would like to send extended Struct/Pointer(In the example below PY_LayerExtended) from Python to C++ but I've got an error.

My C++ code supposed to be like this:

typedef struct PY_LayerBase {
} PY_LayerBase;


typedef struct PY_LayerExtended : PY_LayerBase {
} PY_LayerExtended;


typedef struct PY_Layer {
    PY_LayerBase* layerBase;
} PY_Layer;


// My Method
void parseTest(PY_Layer *py_layer) {

    // I cast PY_LayerBase to PY_LayerExtended
    PY_LayerExtended* py_layerExt = static_cast<PY_LayerExtended*>(py_layer->layerBase);

}

And my Python code looks like this:

import ctypes
from ctypes import *

class PY_LayerBase (ctypes.Structure):
    _fields_ = []


class PY_LayerExtended (PY_LayerBase):
    _fields_ = []


class PY_Layer(ctypes.Structure):
    _fields_ = [("layerBase", PY_LayerBase)]


my_lib = cdll.LoadLibrary('mylib.dll')

py_layer_ext = PY_LayerExtended()
py_layer = PY_Layer(pointer(py_layer_ext))

parseTest = my_lib.parseTest
parseTest.argtypes = (POINTER(PY_Layer))

# Run C++
my_ptr = parseTest(pointer(py_layer))

And when I run Python I've got an error:

TypeError: incompatible types, PY_LayerExtended instance instead of PY_LayerBase instance

The error happens on the line: py_layer = PY_Layer(pointer(py_layer_ext))

Upvotes: 0

Views: 787

Answers (1)

Mark Tolonen
Mark Tolonen

Reputation: 177971

ctypes doesn't understand C++ inheritance, even though you can declare the ctypes Structures similarly using Python inheritance.

You can solve the issue one of two ways. Below I've adjusted the C++ code to give some feedback that the structures are accessed properly:

test.cpp:

#ifdef _WIN32
#   define API __declspec(dllexport)
#else
#   define API
#endif

typedef struct PY_LayerBase {
    int a; // add element
} PY_LayerBase;

typedef struct PY_LayerExtended : PY_LayerBase {
    int b;  // also here, if cast works properly we'll return this value
} PY_LayerExtended;

typedef struct PY_Layer {
    PY_LayerBase* layerBase;
} PY_Layer;

extern "C" API
int parseTest(PY_Layer *py_layer) {
    PY_LayerExtended* py_layerExt = static_cast<PY_LayerExtended*>(py_layer->layerBase);
    return py_layerExt->b;
}

Option 1: Cast the pointer to the type needed:

from ctypes import *

class PY_LayerBase (Structure):
    _fields_ = ('a',c_int),
    def __init__(self,a):
        self.a = a

class PY_LayerExtended(PY_LayerBase): # using Python inheritance to mimic C++ inheritance
    _fields_ = ('b',c_int),
    def __init__(self,a,b):
        super().__init__(a)
        self.b = b
 
class PY_Layer(Structure):
    _fields_ = ("layerBase", POINTER(PY_LayerBase)), # Was missing POINTER

my_lib = cdll.LoadLibrary('./test')

py_layer_ext = PY_LayerExtended(5,7)
py_layer = PY_Layer(cast(pointer(py_layer_ext),POINTER(PY_LayerBase))) # cast to required type

parseTest = my_lib.parseTest
parseTest.argtypes = POINTER(PY_Layer),
parseTest.restype = c_int

# Run C++
print(parseTest(pointer(py_layer)))

Option 2: Declare the structures in Python like you would in pure C and pass a pointer to the base strucure. This will have the same layout as a base class using only C data types.

from ctypes import *

class PY_LayerBase (Structure):
    _fields_ = ('a',c_int),
    def __init__(self,a):
        self.a = a

class PY_LayerExtended(Structure):      # don't use inheritance,
    _fields_ = (('base',PY_LayerBase),  # make base structure a member
                ('b',c_int))
    def __init__(self,a,b):
        self.base.a = a
        self.b = b
 
class PY_Layer(Structure):
    _fields_ = ("layerBase", POINTER(PY_LayerBase)),

my_lib = cdll.LoadLibrary('./test')

py_layer_ext = PY_LayerExtended(5,7)
py_layer = PY_Layer(pointer(py_layer_ext.base)) # pass pointer to base

parseTest = my_lib.parseTest
parseTest.argtypes = POINTER(PY_Layer), # added comma to make this a sequence as required
parseTest.restype = c_int

# Run C++
print(parseTest(pointer(py_layer)))

Output (both options):

7

Upvotes: 3

Related Questions