You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++ DLL返回PyObject函数用ctypes调用报访问违例如何解决

问题根因

你碰到的内存访问错误核心来自两点:

  1. Python所有C API调用都要求调用方持有全局解释器锁(GIL),你在DLL中直接调用PyList_New时没有提前获取GIL,导致访问Python内部内存空间时报错。
  2. 潜在问题:编译DLL时依赖的Python头文件、库文件的版本、架构必须和你运行测试脚本的Python环境完全匹配,比如用64位Python3.9运行就要对应链接64位Python3.9的lib文件,否则也会出现调用异常。
现有代码修复方案

修改C++代码,在调用Python API前后加GIL的获取和释放逻辑即可:

#include <Python.h>

#ifdef _MSC_VER
#define DLL_EXPORT  __declspec( dllexport )
#else
#define DLL_EXPORT
#endif

#ifdef __cplusplus
extern "C"{
#endif

DLL_EXPORT PyObject *test3() {
    // 提前获取GIL
    PyGILState_STATE gstate = PyGILState_Ensure();
    PyObject* ptr = PyList_New(10);
    // 释放GIL,返回的PyObject引用计数正常,Python侧会自动接管
    PyGILState_Release(gstate);
    return ptr;
}

#ifdef __cplusplus
}
#endif

现有Python测试代码不需要修改,只需确认LIB_DLL变量填写的是你编译出的DLL的正确路径即可。

更简便的C++函数导出方案

如果你不想编写大量原生Python/C API绑定代码,有两种轻量方案可选,性能损耗几乎可以忽略,完全满足实时性要求:

方案1:使用pybind11绑定(推荐)

pybind11是header-only的C++/Python绑定库,不需要额外依赖,只需要少量代码即可完成C++函数、类的Python导出,无需处理GIL、引用计数等底层细节,导出的模块可以直接在Python中import,不需要通过ctypes加载。
示例代码如下:

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

namespace py = pybind11;

// 你的原有C++函数,不需要做任何修改
void your_original_high_perf_func() {
    // 原有实时逻辑
}

PYBIND11_MODULE(perf_utils, m) {
    m.doc() = "高性能C++工具模块";
    // 导出原有函数即可
    m.def("your_original_high_perf_func", &your_original_high_perf_func, "高性能处理函数");
    // 要返回Python列表也可以直接用py::list,不需要手动处理引用计数
    m.def("test3", []() {
        return py::list(10);
    });
}

编译后得到的.pyd文件直接放到Python脚本目录,执行import perf_utils即可调用导出的函数。

方案2:导出纯C接口,Python侧做类型转换

如果你不想在C代码中引入任何Python相关的依赖,可以直接导出纯C类型的接口,返回数组、结构体、基础数值类型,Python侧用ctypes接收后再转换为Python对象。这种方案完全不需要处理Python API相关的逻辑,出错概率更低。
比如要返回长度为10的double数组,C
侧代码:

#ifdef _MSC_VER
#define DLL_EXPORT  __declspec( dllexport )
#else
#define DLL_EXPORT
#endif

#ifdef __cplusplus
extern "C"{
#endif

// 导出函数返回数组指针,同时输出数组长度
DLL_EXPORT double* test3(int* out_len) {
    *out_len = 10;
    double* arr = (double*)malloc(*out_len * sizeof(double));
    // 给arr赋值的逻辑
    for(int i=0; i<*out_len; i++) arr[i] = i;
    return arr;
}

// 配套导出内存释放函数,避免内存泄漏
DLL_EXPORT void free_mem(void* ptr) {
    free(ptr);
}

#ifdef __cplusplus
}
#endif

Python侧接收代码:

import ctypes

if __name__ == "__main__":
    lib = ctypes.cdll.LoadLibrary(LIB_DLL)
    lib.test3.argtypes = [ctypes.POINTER(ctypes.c_int)]
    lib.test3.restype = ctypes.POINTER(ctypes.c_double)
    lib.free_mem.argtypes = [ctypes.c_void_p]

    out_len = ctypes.c_int()
    arr_ptr = lib.test3(ctypes.byref(out_len))
    # 转换为Python列表
    res = [arr_ptr[i] for i in range(out_len.value)]
    # 释放C++侧申请的内存
    lib.free_mem(arr_ptr)
    print(res)
注意事项
  1. 用ctypes返回py_object类型时,Python会自动接管返回的PyObject的引用计数,不要在C++侧手动调用Py_DECREF,否则会出现重复释放崩溃。
  2. 所有涉及跨语言调用的场景,都要保证编译出的DLL和运行环境的架构、依赖版本完全一致,避免出现奇奇怪怪的加载错误。

内容的提问来源于stack exchange,提问作者hwgy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 21:48:03