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

使用Python ctypes调用带py_object参数的C++函数触发访问冲突

Python ctypes调用C++ DLL时PyArg_ParseTuple触发访问冲突的问题分析与解决

问题场景

在Python 3.9 + MSVC 2022环境下,通过ctypes调用C++ DLL中接收py_object参数的My_Function3函数时触发访问冲突。该函数尝试用PyArg_ParseTuple从传入的PyObject提取字符串,但执行到该步骤时报错。此前传递c_char_p给对应参数的函数并返回PyObject*的操作可正常运行,且已确认地址传递正确。后续排查发现问题出在异常处理与参数类型匹配环节,调整字符串复制逻辑后以扩展形式正常运行。

问题根源

  1. PyArg_ParseTuple使用场景错误:该函数是为Python扩展模块设计的,要求传入的参数是参数元组(比如Python侧调用func(a,b)时,C侧收到的是包含a和b的元组)。但当前Python代码直接传递单个字符串对象,C侧将其当作元组解析,必然导致内存访问错误。
  2. 未初始化异常对象:全局变量MyError未初始化,调用PyErr_SetString时会访问空指针,加剧错误。
  3. 未初始化局部数组:局部数组s未初始化就调用strcat_s,会导致未定义行为。

修复方案

方案1:直接处理单个字符串对象(推荐)

跳过PyArg_ParseTuple,直接将传入的PyObject(Python字符串)转换为C字符串:

修复后的C++代码

#include "pch.h"
#define PY_SSIZE_T_CLEAN
#include <Python.h>

extern "C"
{
    __declspec(dllexport) PyObject* __stdcall My_Function3(PyObject *str_obj)
    {
        // 检查传入对象是否为字符串
        if (!PyUnicode_Check(str_obj)) {
            PyErr_SetString(PyExc_TypeError, "Expected a string");
            return NULL;
        }

        // 将Python字符串转换为UTF-8编码的C字符串
        const char* str = PyUnicode_AsUTF8(str_obj);
        if (!str) {
            return NULL; // 转换失败时已自动设置异常
        }

        // 检查字符串长度
        size_t str_len = strlen(str);
        if (str_len >= 100) {
            PyErr_SetString(PyExc_ValueError, "Error: string too long");
            return NULL;
        }

        // 初始化局部数组并复制字符串
        char s[100] = {0};
        strcpy_s(s, 100, str);
        return Py_BuildValue("s", s);
    }
}

Python代码(无需修改)

import ctypes
import sys
import os 

y = __file__
str_path = os.path.dirname(os.path.realpath("..\\..\\string\\x64\\Debug\\string.dll"))
handle = ctypes.CDLL(str_path + "/string.dll")     

handle.My_Function3.argtypes = [ctypes.py_object]
handle.My_Function3.restype = ctypes.py_object

z = handle.My_Function3(y)

print(z)

方案2:适配PyArg_ParseTuple的元组参数

如果坚持使用PyArg_ParseTuple,Python侧需要传递参数元组:

修改后的Python代码

import ctypes
import sys
import os 

y = __file__
str_path = os.path.dirname(os.path.realpath("..\\..\\string\\x64\\Debug\\string.dll"))
handle = ctypes.CDLL(str_path + "/string.dll")     

handle.My_Function3.argtypes = [ctypes.py_object]
handle.My_Function3.restype = ctypes.py_object

# 传递参数元组而非单个字符串
z = handle.My_Function3((y,))

print(z)

修复后的C++代码(初始化异常对象)

#include "pch.h"
#define PY_SSIZE_T_CLEAN
#include <Python.h>

static PyObject *MyError;

// DLL初始化时注册自定义异常类型
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        MyError = PyErr_NewException("mydll.MyError", NULL, NULL);
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

extern "C"
{
    __declspec(dllexport) PyObject* __stdcall My_Function3(PyObject *args)
    {
        const char* str;
        char s[100] = {0}; // 初始化局部数组

        // 解析元组中的单个字符串参数
        if (!PyArg_ParseTuple(args, "s", &str))
            return NULL;
 
        if (strlen(str) >= 100)
        {
            PyErr_SetString(MyError, "Error: string too long");
            return NULL;
        }

        strcpy_s(s, 100, str);
        return Py_BuildValue("s", s);
    }
}

关键注意点

  • 区分Python扩展模块和ctypes调用的差异:扩展模块的函数参数是元组,而ctypes直接传递单个对象时,C侧收到的是该对象本身,不是元组。
  • 始终初始化全局异常对象,或直接使用Python标准异常类型(如PyExc_ValueError、PyExc_TypeError)。
  • 局部数组必须初始化后再使用,避免未定义行为。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 09:24:54