pythonclasscppyy

How to create a child class in python when using cppyy?


I use cppyy to allow python call C++ functions and classes. But I don't know how to create a child class of imported C++ function.

Here is my problem.

import cppyy
cppyy.include('/include/HSTradeApi.h')  //include the CHSTradeSpi class
cppyy.load_library('win64/HSTradeApi')

Here is the class CHSTradeSpi in hearder file. I simplized it and keep the first func in this class.

// C++ header file
#include "HSStruct.h"   // this header file seems not directly related to my problem
class  CHSTradeSpi
{
public:
    virtual void OnFrontConnected(){};
};

Then I tried to create a child class of CHSTradeSpi in Python to add more functions

class CTradeSpi(cppyy.gbl.CHSTradeSpi):

    def __init__(self, tapi):
        super().__init__(self)  // is this line wrong?
        self.tapi = tapi  

    def OnFrontConnected(self) -> "void":  
        print("OnFrontConnected")
        authfield = cppyy.gbl.CHSReqAuthenticateField()  # defined in HSSruct.h
        authfield.BrokerID = BROKERID
        authfield.UserID = USERID
        authfield.AppID = APPID
        authfield.AuthCode = AuthCode  #
        self.tapi.ReqAuthenticate(authfield, 0)
        print("send ReqAuthenticate ok")

it failed and says "CHSTradeSpi not an acceptable base: no virtual destructor". I know CHSTradeSpi is abstract class, but then how to create its child class?

Thank you ahead.

*************UPDATE*********************
Many thanks to Wim Lavrijsen. I changed my plan. First I wrote a derived class CMyTradeSpi in C++ to get an instance.

#include "../include/HSDataType.h"
#include "../include/HSTradeApi.h"
class CMyTradeSpi : public CHSTradeSpi
{
public:
     void OnFrontConnected();
};

Then I import to python

import cppyy
cppyy.include('/include/HSTradeApi.h')  //include the CHSTradeSpi class
cppyy.load_library('win64/HSTradeApi')
cppyy.include('/include/newTrade.h')  ## class CMyTradeSpi in it

virt_spi = AddVirtualDtor(cppyy.gbl.CMyTradeSpi)  # call CMyTradeSpi

class CTradeSpi(virt_spi):

    def __init__(self, tapi):  
        virt_spi.__init__(self)  
        self.tapi = tapi

I got an error point to "public CMyTradeSpi {"

input_line_29:18:3: error: call to implicitly-deleted default constructor of '::workaround::CMyTradeSpiWithVDtor'
  Dispatcher1() {}
  ^
input_line_27:2:34: note: default constructor of 'CMyTradeSpiWithVDtor' is implicitly deleted because base class 'CMyTradeSpi' has no default constructor
    class CMyTradeSpiWithVDtor : public CMyTradeSpi {

it seems also need a constructor.

**************** UPDATE 2 *******************
Since above error, I tried to create an instance in Python using python abc lib.

import time
import cppyy
import abc
cppyy.include('/include/HSTradeApi.h')
cppyy.load_library('win64/HSTradeApi')

def AddVirtualDtor(cls):
    #dname = cls.__name__+"WithVDtor"
    cppyy.cppdef("""namespace workaround {{
    class {0}WithVDtor : public {1} {{
    public:
        using {0}::{0};
        virtual ~{0}WithVDtor() {{}}
    }}; }}""".format(cls.__name__, cls.__cpp_name__))
    return getattr(cppyy.gbl.workaround, "{0}WithVDtor".format(cls.__name__))

spi = AddVirtualDtor(cppyy.gbl.CHSTradeSpi)

class CTradeSpi(spi):
    __metaclass__ = abc.ABCMeta

    def __init__(self, tapi):  
        spi.__init__(self) 
        self.tapi = tapi  

    def OnFrontConnected(self) -> "void":   
        print("OnFrontConnected") 
        authfield = cppyy.gbl.CHSReqAuthenticateField() 
        authfield.HSAccountID = ACCOUNTID
        authfield.HSPassword = PASSWORD
        authfield.HSAppID = APPID
        authfield.HSAuthCode = AuthCode  #
        self.tapi.ReqAuthenticate(authfield, 0)
        print("send ReqAuthenticate ok")

It shows no error. But it did not print out "OnFrontConnected", so I guess in this way, class CTradeSpi(spi) did not override spi and nothing has run. I don't know why. Thank you.


Solution

  • It's not about the base class being an abstract base, but precisely about it not having a virtual destructor. Not having a virtual destructor means that if the derived instance gets deleted through a pointer with the type of the base class, the destructor of the derived class is not called. If OTOH, the destructor is virtual, both constructors are called as they should. Iow., w/o a virtual destructor, the python instance will leak (never be garbage collected), and therefore derivation of such base classes is disabled.

    If you absolutely want to go ahead anyway, you can interject a dummy class with a virtual destructor. You'd still have the same problem if the instance gets deleted in C++ through the original base. However, if you can make sure that the python instance only ever gets deleted in python, you'll be okay. Here is an example of such a workaround:

    import cppyy
    
    cppyy.cppdef("""
    class CHSTradeSpi {
    public:
        virtual void OnFrontConnected() = 0;
    };""")
    
    def AddVirtualDtor(cls):
        dname = cls.__name__+"WithVDtor"
        cppyy.cppdef("""namespace workaround {{
        class {0}WithVDtor : public {1} {{
        public:
            using {0}::{0};
            virtual ~{0}WithVDtor() {{}}
        }}; }}""".format(cls.__name__, cls.__cpp_name__))
        return getattr(cppyy.gbl.workaround, "{0}WithVDtor".format(cls.__name__))
    
    class CTradeSpi(AddVirtualDtor(cppyy.gbl.CHSTradeSpi)):
        def __init__(self, tapi):
            super(CTradeSpi, self).__init__()
            self.tapi = tapi
    
        def OnFrontConnected(self):
            # etc ...
            pass
    

    EDIT: A simple way to get past the constructor problem, is to add, next to the using, a constructor that does exist. That way, the default is not generated. I have, however, not been able to write a simple code that does that (you can introspect the base class to generate one, but it's not pretty). If you only have one class, then this could be a workaround. Just add e.g. {0}WithVDtor(int i) : {0}(i) {{}} just above the using in the code, if there is such a constructor (change arguments as needed).

    I'm working on changes to see whether I can relax the requirement of a virtual destructor. I still have a crash left in one case.

    You can not replace the metaclass: it is the metaclass that interject the trampoline, so by using abc.ABCMeta, that gets disabled. Yes, no error, but also no dispatch.

    UPDATE: As part of the changes to support multiple inheritance across the language barrier, the kept object on the C++ side is now the trampoline, so a virtual destructor is no longer required in the base. There is still a warning, as the same caveat remains: deletion on the C++ side will leak the Python object. To be released soon in 1.7.2.