簡體   English   中英

Python綁定C ++虛擬成員函數無法調用

[英]Python binding C++ virtual member function cannot be called

我最近在C ++中編寫了對Python 3的擴展,但是當我在python中調用C ++時遇到了一些麻煩,而且我不打算使用第三方庫。

我曾經用過Python綁定C ++虛擬成員函數,但不能調用,但是刪除virtual關鍵字就可以了。

它在運行return PyObject_CallObject(pFunction, args);時崩潰了return PyObject_CallObject(pFunction, args); ,但我沒有找到原因。

這是我的代碼:

class A 
{
    PyObject_HEAD
public:
    A()
    {
        std::cout << "A::A()" << std::endl;
    }

    ~A()
    {
        std::cout << "A::~A()" << std::endl;
    }

    virtual void test()
    {
        std::cout << "A::test()" << std::endl;
    }
};

class B : public A
{
public:
    B()
    {
        std::cout << "B::B()" << std::endl;
    }

    ~B()
    {
        std::cout << "B::~B()" << std::endl;
    }

    static PyObject *py(B *self) {
        self->test();
        return PyLong_FromLong((long)123456);
    }
};

static void B_dealloc(B *self) 
{
    self->~B();
    Py_TYPE(self)->tp_free((PyObject *)self);
}

static PyObject *B_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    B *self = (B*)type->tp_alloc(type, 0);
    new (self)B;
    return (PyObject*)self;
}

static PyMethodDef B_methods[] = {
    {"test", (PyCFunction)(B::py), METH_NOARGS, nullptr},
    {nullptr}
};

static struct PyModuleDef example_definition = {
    PyModuleDef_HEAD_INIT,
    "example",
    "example",
    -1,
    B_methods
};

static PyTypeObject ClassyType = {
    PyVarObject_HEAD_INIT(NULL, 0) "example.B", /* tp_name */
    sizeof(B),                                  /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor)B_dealloc,                      /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    0,                                          /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash  */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   /* tp_flags */
    "B objects",                                /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    B_methods,                                  /* tp_methods */
    nullptr,                                    /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    nullptr,                                    /* tp_init */
    0,                                          /* tp_alloc */
    B_new,                                      /* tp_new */
};

PyMODINIT_FUNC PyInit_example(void)
{

    PyObject *m = PyModule_Create(&example_definition);

    if (PyType_Ready(&ClassyType) < 0)
        return NULL;

    Py_INCREF(&ClassyType);
    PyModule_AddObject(m, "B", (PyObject*)&ClassyType);

    return m;
}

PyObject* importModule(std::string name)
{
    PyObject* pModule = PyImport_ImportModule(name.c_str());    // module name
    if (pModule == nullptr)
    {
        std::cout << "load module error!" << std::endl;
        return nullptr;
    }

    return pModule;
}

PyObject* callFunction(PyObject* pModule, std::string name, PyObject* args = nullptr)
{
    PyObject* pFunction = PyObject_GetAttrString(pModule, name.c_str());    // function name
    if (pFunction == nullptr)
    {
        std::cout << "call function error!" << std::endl;
        return nullptr;
    }

    return PyObject_CallObject(pFunction, args);
}

int main()
{
    // add module
    PyImport_AppendInittab("example", PyInit_example);

    // init python
    Py_Initialize();
    {
        PyRun_SimpleString("import sys");
        PyRun_SimpleString("import os");
        PyRun_SimpleString("sys.path.append(os.getcwd() + '\\script')");    // add script path
    }

    // import module
    PyImport_ImportModule("example");

    PyObject* pModule = importModule("Test");
    if (pModule != nullptr)
    {
        PyObject* pReturn = callFunction(pModule, "main");
    }

    PyErr_Print();

    Py_Finalize();

    system("pause");
    return 0;
}

我假設OP正在使用CPython API。 我們使用CPython,部分代碼看起來相似/熟悉。)

顧名思義,它是用C編寫的。

因此,在使用它編寫C ++類的Python綁定時,開發人員必須意識到CPython及其C API並不“了解”有關C ++的任何知識。 必須仔細考慮這一點(類似於為C ++類庫編寫C綁定)。

當我編寫Python Wrapper類時,我總是使用struct s(記住這一事實)。 可以在CPython的包裝器中使用C ++繼承來類似於包裝的C ++類的繼承(但這是我上面的規則中的唯一例外)。

structclass在C ++中是一樣的東西,唯一的例外是,默認情況下,所有內容在struct都是public ,而在class中是private的。 SO:類與結構只用於數據? 順便說一句。 CPython將訪問它。 成員變量由C指針強制轉換(重新解釋強制轉換)構成組件(例如ob_base ),甚至不會識別private安全嘗試。

恕我直言,值得一提的是POD普通的舊數據 ,也稱為被動數據結構 )一詞,因為這就是使C ++包裝器類與C兼容的原因。SO:什么是聚合和POD?它們為何/為什么如此特殊? 為此提供了全面的概述。

在CPython包裝器類中引入至少一個virtual成員函數會帶來致命的后果。 仔細閱讀以上鏈接可以使這一點變得清楚。 但是,我決定通過一些示例代碼來說明這一點:

#include <iomanip>
#include <iostream>

// a little experimentation framework:

struct _typeobject { }; // replacement (to keep it simple)
typedef size_t Py_ssize_t; // replacement (to keep it simple)

// copied from object.h of CPython:
/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA            \
    struct _object *_ob_next;           \
    struct _object *_ob_prev;

// copied from object.h of CPython:
/* Nothing is actually declared to be a PyObject, but every pointer to
 * a Python object can be cast to a PyObject*.  This is inheritance built
 * by hand.  Similarly every pointer to a variable-size Python object can,
 * in addition, be cast to PyVarObject*.
 */
typedef struct _object {
  _PyObject_HEAD_EXTRA
  Py_ssize_t ob_refcnt;
  struct _typeobject *ob_type;
} PyObject;

/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD                   PyObject ob_base;

void dump(std::ostream &out, const char *p, size_t size)
{
  const size_t n = 16;
  for (size_t i = 0; i < size; ++p) {
    if (i % n == 0) {
      out << std::hex << std::setw(2 * sizeof p) << std::setfill('0')
        << (size_t)p << ": ";
    }
    out << ' '
      << std::hex << std::setw(2) << std::setfill('0')
      << (unsigned)*(unsigned char*)p;
    if (++i % n == 0) out << '\n';
  }
  if (size % n != 0) out << '\n';
}

// the experiment:

static PyObject pyObj;

// This is correct:
struct Wrapper1 {
  PyObject_HEAD
  int myExt;
};
static Wrapper1 wrap1;

// This is possible:
struct Wrapper1Derived: Wrapper1 {
  double myExtD;
};
static Wrapper1Derived wrap1D;

// This is effectively not different from struct Wrapper1
// but things are private in Wrapper2
// ...and Python will just ignore this (using C pointer casts).
class Wrapper2 {
  PyObject_HEAD
  int myExt;
};
static Wrapper2 wrap2;

// This is FATAL - introduces a virtual method table.
class Wrapper3 {
  private:
    PyObject_HEAD
    int myExt;
  public:
    Wrapper3(int value): myExt(value) { }
    virtual ~Wrapper3() { myExt = 0; }
};
static Wrapper3 wrap3{123};

int main()
{
  std::cout << "Dump of PyObject pyObj:\n";
  dump(std::cout, (const char*)&pyObj, sizeof pyObj);
  std::cout << "Dump of Wrapper1 wrap1:\n";
  dump(std::cout, (const char*)&wrap1, sizeof wrap1);
  std::cout << "Dump of Wrapper1Derived wrap1D:\n";
  dump(std::cout, (const char*)&wrap1D, sizeof wrap1D);
  std::cout << "Dump of Wrapper2 wrap2:\n";
  dump(std::cout, (const char*)&wrap2, sizeof wrap2);
  std::cout << "Dump of Wrapper3 wrap3:\n";
  dump(std::cout, (const char*)&wrap3, sizeof wrap3);
  return 0;
}

編譯並運行:

Dump of PyObject pyObj:
0000000000601640:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000000000601650:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Dump of Wrapper1 wrap1:
0000000000601600:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000000000601610:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000000000601620:  00 00 00 00 00 00 00 00
Dump of Wrapper1Derived wrap1D:
00000000006015c0:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000000006015d0:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000000006015e0:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Dump of Wrapper2 wrap2:
0000000000601580:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000000000601590:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000000006015a0:  00 00 00 00 00 00 00 00
Dump of Wrapper3 wrap3:
0000000000601540:  d8 0e 40 00 00 00 00 00 00 00 00 00 00 00 00 00
0000000000601550:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000000000601560:  00 00 00 00 00 00 00 00 7b 00 00 00 00 00 00 00

在coliru上進行現場演示

pyObjwrap1wrap1Dwrap2的轉儲僅包含00 s –難怪,我將它們設置為static wrap3看起來有些不同,部分原因是構造函數( 7b == 123),部分原因是C ++編譯器將VMT ponter放入d8 0e 40可能屬於的類實例中。 (我假設VMT指針具有任何函數指針的大小,但我真的不知道編譯器如何在內部組織事物。)

想象一下,當CPython獲取wrap3的地址,將其轉換為PyObject*並寫入_ob_next指針(其偏移量為0)並用於將Python對象鏈接到一個雙向鏈接列表中時,會發生什么。 (希望發生崩潰或其他會使情況更糟的事情。)

想象一下OP的創建功能會發生什么

static PyObject *B_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    B *self = (B*)type->tp_alloc(type, 0);
    new (self)B;
    return (PyObject*)self;
}

B的展示位置構造函數覆蓋可能在tp_alloc()發生的PyObject內部結構的初始化時。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM