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.
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.