Search code examples
pythonc++interopctypesdllexport

How to return a class object from c++ (DllExport) to python using ctypes vice-versa?


I have a function in c++ which returns class object to python and I have a wrapper in python(ctypes import)in below snippet, when i call the function(Get_Student) from python, the School has new instance created in c++ and I cannot retrieve data of Student class,

//The c++ code is:

 #include"stdafx.h"
 #include<string>
 using namespace std;

class Student
{
    int ID;
public:
  void SetId(int id)
    {
      ID = id;
    }
  int GetId()
    {
     return ID;
    }
};
class School{
    Student* stud;
    public:

    void* CreateStudent()
    {
      stud = new Student();
      return stud;
    }

    Student* GetStudent()
    {
      return stud;
    }
};

extern "C"
{
    __declspec(dllexport) void Student_SetId(Student* stud ,int id)
   {
     stud->SetId(id);
   }
    __declspec(dllexport) int Student_GetId(Student* stud)
    {
       return (stud->GetId());
    }
    __declspec(dllexport) School* School_new(){
    return new School();
    }

    __declspec(dllexport) void* School_CreateStudent(School* school){
    return (school->CreateStudent());
    }

    __declspec(dllexport)Student* School_GetStudent(School* school){ 
    return (school->GetStudent());
    }
}


#The Python code is:
from ctypes import*
mydll=cdll.LoadLibrary("D:\\shameel\\Cpp\\Sample_class\\Debug\\Sample_class.dll")

class Student():
    def __init__(self):
        self.nativeStudent = mydll.School_CreateStudent()
    def Set_Id(self,arg):
        mydll.Student_SetId(self.nativeStudent,arg)

    def Get_Id(self):
        return (mydll.Student_GetId(self.nativeStudent))

class School():
    def __init__(self):
        self.nativeSchool = mydll.School_new()

    def CreateStudent(self):
        return Student()
    def Get_Student(self):
        mydll.School_GetStudent.restype =  c_void_p
        self.nativestd = mydll.School_GetStudent(self.nativeSchool)
        return (self.nativestd)

#The Python call is as follows:

fobj = School()
std_new = fobj.CreateStudent()
std_new.Set_Id(3)
Id = std_new.Get_Id()
getobj =fobj.Get_Student()
print(getobj)

when i print getobj its output is "none".

How do i get the same object from the CPP?


Solution

  • ctypes assumes arguments and return values are 32-bit integers (ctypes.c_int) unless told otherwise. Particularly if you are using 64-bit Python, pointers will be truncated to 32 bits. The first thing to do is declare the function arguments and return values correctly:

    mydll.Student_SetId.argtypes = c_void_p,c_int
    mydll.Student_SetId.restype = None
    mydll.Student_GetId.argtypes = c_void_p,
    mydll.Student_GetId.restype = c_int
    mydll.School_new.argtypes = ()
    mydll.School_new.restype = c_void_p
    mydll.School_CreateStudent.argtypes = c_void_p,
    mydll.School_CreateStudent.restype = c_void_p
    mydll.School_GetStudent.argtypes = c_void_p,
    mydll.School_GetStudent.restype = c_void_p
    

    This will also help you catch errors, such as School_CreateStudent() needs to be passed a School* to work properly.

    To catch even more errors, declare specific pointer types. Here is a little class that will display its type and an example:

    class Ptr(c_void_p):
        def __repr__(self):
            cls_name = self.__class__.__name__
            if self.value is not None:
                return f'{cls_name}({self.value:#x})'
            return f'{cls_name}(None)'
    
    class pStudent(Ptr): pass
    class pSchool(Ptr): pass
    

    Demo:

    >>> p = pSchool()
    >>> p
    pSchool(None)
    

    Then declare the function arguments and results like so:

    mydll.Student_SetId.argtypes = pStudent,c_int
    mydll.Student_SetId.restype = None
    mydll.Student_GetId.argtypes = pStudent,
    mydll.Student_GetId.restype = c_int
    mydll.School_new.argtypes = ()
    mydll.School_new.restype = pSchool
    mydll.School_CreateStudent.argtypes = pSchool,
    mydll.School_CreateStudent.restype = pStudent
    mydll.School_GetStudent.argtypes = pSchool,
    mydll.School_GetStudent.restype = pStudent
    

    Now anywhere the code is used incorrectly in Python should give you a good error message.

    Once you fix your C++ and Python code your output for print(getobj) will look something like:

    pStudent(0x1b54b89b350)