函数调用时的 PyObject 段错误

PyObject segfault on function call

本文关键字:错误 段错误 PyObject 函数调用      更新时间:2023-10-16

我正在尝试使用 Python 打开一个对话框以接受输入到我的C++应用程序中。

这是我正在尝试做的事情的非常小的表示:

#include <iostream>
#include <Python.h>
int main()
{
    /* Begin Python Ititialization - only needs to be done once. */
    PyObject *ip_module_name = NULL;
    PyObject *ip_module = NULL;
    PyObject *ip_module_contents = NULL;
    PyObject *ip_module_getip_func = NULL;
    Py_Initialize();
    PyEval_InitThreads();
    ip_module_name     = PyString_FromString( "get_ip" );
    ip_module          = PyImport_Import( ip_module_name );
    ip_module_contents = PyModule_GetDict( ip_module );
    ip_module_getip_func = PyDict_GetItemString( ip_module_contents, "get_ip_address" );
    /* End Initialization */
    PyGILState_STATE state = PyGILState_Ensure();
    PyObject *result = PyObject_CallObject( ip_module_getip_func, NULL );
    if( result == Py_None )
        printf( "Nonen" );
    else
        printf( "%sn", PyString_AsString( result ) );
    PyGILState_Release( state );
    /* This is called when the progam exits. */
    Py_Finalize();
}

但是,当我使用 PyObject_CallObject 调用函数时,应用程序会出现段错误。我猜这是因为我正在使用 Tk 库。我尝试将我的应用程序链接到_tkinter.lib,tk85.lib,tcl85.lib,tkstub85.lib,tclstub85.lib,但这些都无济于事。我很困惑...

下面是脚本:

import Tkinter as tk
from tkSimpleDialog import askstring
from tkMessageBox import showerror
def get_ip_address():
    root = tk.Tk()
    root.withdraw()
    ip = askstring( 'Server Address', 'Enter IP:' )
    if ip is None:
        return None
    ip = ip.strip()
    if ip is '':
        showerror( 'Error', 'Please enter a valid IP address' )
        return get_ip_address()
    if len(ip.split(".")) is not 4:
        showerror( 'Error', 'Please enter a valid IP address' )
        return get_ip_address()
    for octlet in ip.split("."):
        x = 0
        if octlet.isdigit():
            x = int(octlet)
        else:
            showerror( 'Error', 'Please enter a valid IP address' )
            return get_ip_address()
        if not ( x < 256 and x >= 0 ):
            showerror( 'Error', 'Please enter a valid IP address' )
            return get_ip_address()
    return ip

编辑:添加了我的线程设置

添加PySys_SetArgv(argc, argv)(以及int argc, char **argv参数到main(,您的代码就可以工作了。

tk.Tk()访问sys.argv,除非PySys_SetArgv被调用,否则不存在。这会导致一个异常,该异常被传播出get_ip并通过返回NULL PyObject_CallObject报告给 Python/C 。NULL被存储到result并传递给PyString_AsString,这是观察到的崩溃的直接原因。

关于代码的几点评论:

  • 调试它需要付出努力,因为代码不进行任何错误检查,它会盲目地向前按,直到由于传递 NULL 指针而崩溃。至少可以做的是写这样的东西:

    if (!ip_module_name) {
        PyErr_Print();
        exit(1);
    }
    // and so on for every PyObject* that you get from a Python API call
    

    在实际代码中,您不会exit(),但会执行一些清理并返回NULL(或引发C++级异常,或任何合适的内容(。

  • 无需在已知包含 GIL 的线程中调用PyGILState_Ensure。正如PyEval_InitThreads的文档所述,它初始化 GIL 并获取它。当从 C 回调调用 Python 时,你只需要重新获取 GIL,该回调来自与 Python 无关的工具包事件循环。

  • 从 Python 收到的新引用需要Py_DECREF一次不再需要。为了简洁起见,可以从最小示例中省略引用计数,但应始终注意。