Python C API: PyDateTime_FromTimestamp 导致段错误

Python C API: PyDateTime_FromTimestamp causes segmentation fault

我跟着this answer调用了PyDateTime_FromTimestamp在C++中创建了一个datetime对象。但是当调用 PyDateTime_FromTimestamp 时我得到了一个 Segmentation fault

这是我的 C++ 代码:

#include <python3.6/Python.h>
#include <stdio.h>

#include <python3.6/datetime.h>
#include <sys/time.h>

static PyObject *iGetDateTime_PyFn(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) {
    static double doubleValue = 1314761451;
    PyObject *floatObj = NULL;
    PyObject *timeTuple = NULL;
    PyObject *dateTime = NULL;
    floatObj = PyFloat_FromDouble(doubleValue);
    timeTuple = Py_BuildValue("(O)", floatObj);
    printf("timeTuple = %08x\n", (unsigned int)(long long)timeTuple);
    printf("PyTuple_Check(timeTuple) = %d\n", PyTuple_Check(timeTuple));
    dateTime = PyDateTime_FromTimestamp(timeTuple);
    printf("ready to return\n");
    return dateTime;
}

static PyMethodDef all_methods[] = {
    { "get_datetime", iGetDateTime_PyFn, METH_VARARGS, NULL },
    { NULL, NULL, 0, NULL }
};

static struct PyModuleDef main_module = {
    PyModuleDef_HEAD_INIT,
    "cpp",
    NULL,
    -1,
    all_methods
};

PyMODINIT_FUNC PyInit_cpp(void) {
    return PyModule_Create(&main_module);
}

我用这个命令编译:

g++ --shared -fPIC -o cpp.so t1.cpp

我的 g++ 版本是 7.3.0.

在python中,我执行:

import cpp
print(cpp.get_datetime())

然后我打印了以下内容:

timeTuple = a7934358
PyTuple_Check(timeTuple) = 1
Segmentation fault (core dumped)

正如我们所见,timeTuple 已成功构建,并被检查为 tuple。但是我们无法到达 return 句子。

根据[GitHub]: python/cpython - (3.6) cpython/Include/datetime.h (${PYTHON_SRC_DIR}/Include/datetime.h):

  1. PyDateTime_FromTimestamp 是一个 预处理器

    #define PyDateTime_FromTimestamp(args) \
        PyDateTimeAPI->DateTime_FromTimestamp( \
            (PyObject*) (PyDateTimeAPI->DateTimeType), args, NULL)
    
  2. PyDateTimeAPI 初始化为 NULL(在文件前面)

    static PyDateTime_CAPI *PyDateTimeAPI = NULL;
    

调用宏时导致段错误访问冲突)。

修复需要通过 PyDateTime_IMPORT 宏初始化 PyDateTimeAPI

#define PyDateTime_IMPORT \
    PyDateTimeAPI = (PyDateTime_CAPI *)PyCapsule_Import(PyDateTime_CAPSULE_NAME, 0)

最初,我在浏览代码时发现了这一点(我在 getDateTimePyFn 函数中做到了),然后我遇到了 [Python 3.Docs]: DateTime Objects (重点是我的)

Before using any of these functions, the header file datetime.h must be included in your source (note that this is not included by Python.h), and the macro PyDateTime_IMPORT must be invoked, usually as part of the module initialization function.

我修改了你的代码,我将在 Win 上举例说明(因为这对我来说更容易,而且行为是可重现的)。

cpp.c:

#include <stdio.h>
#include <Python.h>
#include <datetime.h>

#define MOD_NAME "cpp"


static double doubleValue = 1314761451;

static PyObject *getDateTimePyFn(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) {
    PyObject *floatObj = NULL,
        *timeTuple = NULL,
        *dateTime = NULL;
    floatObj = PyFloat_FromDouble(doubleValue);
    if (!floatObj)
    {
        return NULL;
    }
    timeTuple = Py_BuildValue("(O)", floatObj);
    Py_XDECREF(floatObj);
    if (!timeTuple)
    {
        return NULL;
    }
    dateTime = PyDateTime_FromTimestamp(timeTuple);
    Py_XDECREF(timeTuple);
    return dateTime;
}


static PyMethodDef all_methods[] = {
    { "get_datetime", getDateTimePyFn, METH_VARARGS, NULL },
    { NULL, NULL, 0, NULL }
};


static struct PyModuleDef main_module = {
    PyModuleDef_HEAD_INIT,
    MOD_NAME,
    NULL,
    -1,
    all_methods
};


PyMODINIT_FUNC PyInit_cpp(void) {
    PyDateTime_IMPORT;  // @TODO - cfati: !!! This initializes the struct containing the function pointer !!!
    return PyModule_Create(&main_module);
}

code.py:

#!/usr/bin/env python3

import sys
import cpp


def main():
    print("cpp.get_datetime returned: {:}".format(cpp.get_datetime()))


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()
    print("Done.")

输出:

[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q055903897]> sopr.bat
*** Set shorter prompt to better fit when pasted in Whosebug (or other) pages ***

[prompt]> "c:\Install\x86\Microsoft\Visual Studio Community15\vc\vcvarsall.bat" x64

[prompt]> dir /b
code.py
cpp.c

[prompt]> cl /nologo /DDLL /MD /I"c:\Install\x64\Python\Python.06.08\include" cpp.c  /link /NOLOGO /DLL /LIBPATH:"c:\Install\x64\Python\Python.06.08\libs" /OUT:cpp.pyd
cpp.c
   Creating library cpp.lib and object cpp.exp

[prompt]> dir /b
code.py
cpp.c
cpp.exp
cpp.lib
cpp.obj
cpp.pyd

[prompt]> "e:\Work\Dev\VEnvs\py_064_03.06.08_test0\Scripts\python.exe" code.py
Python 3.6.8 (tags/v3.6.8:3c6b436a57, Dec 24 2018, 00:16:47) [MSC v.1916 64 bit (AMD64)] on win32

cpp.get_datetime returned: 2011-08-31 06:30:51
Done.