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

如何在C++与Python间传递兼容主机/设备内存的指针?

问题:C++通过Python C API调用支持CPU/GPU的apply_filter函数的指针转换方案

我有一个名为apply_filter的Python函数,可基于CPU(使用NumPy)或GPU(使用CuPy)执行,该函数接收一个输入缓冲区对象,代表指向系统内存或GPU全局设备内存的指针。我希望通过Python C API从C代码中调用该函数,但不清楚如何在C侧构造对应的输入缓冲区对象(对应原始指针),也不知道如何完成双向的指针与PyObject转换。以下是简化代码:

C++调用代码(待补全)

#include <Python.h>

void PythonObjectWrapper::applyFilter(float* image, std::array<int, 3> dim) {
    PyObject* python_method = PyObject_GetAttrString(class_object_, method_name_);
    PyObject* py_image = ??? // convert C-array to PyObject
    PyObject* method_args = PyTuple_New(2);
    PyTuple_SetItem(method_args, 0, py_image);
    PyTuple_SetItem(method_args, 1, ...); // transfer dim
    PyObject* py_filtered_image = PyObject_CallObject(python_method, method_args);
    float* filtered_image = ??? // convert PyObject to C-array
}

Python被调用函数(待补全)

class Filter:
    def __init__(self, gpu):
        self.gpu_ = gpu

    def apply_filter(self, image_ptr, dim):
        image_array = ??? // convert image_ptr PyObject to NumPy / CuPy array
        apply_filter_(image_array)
        filtered_image_ptr = ??? // convert image_array to ptr
        return filtered_image_ptr

需求:方案需避免不必要的数据拷贝(尤其主机与设备间的拷贝),高效且稳定支持CPU/GPU两种运行模式。


解决方案

1. C++侧:将C数组指针转为PyObject(py_image = ???)

假设PythonObjectWrapper包含is_gpu_成员变量标记运行模式,通过NumPy/CuPy的C API直接包装指针,不拷贝数据:

// 先处理维度转换为Python元组(对应代码中...的部分)
PyObject* py_dim = PyTuple_New(3);
PyTuple_SetItem(py_dim, 0, PyLong_FromLong(dim[0]));
PyTuple_SetItem(py_dim, 1, PyLong_FromLong(dim[1]));
PyTuple_SetItem(py_dim, 2, PyLong_FromLong(dim[2]));

// 处理image指针转为PyObject
PyObject* py_image = nullptr;
if (is_gpu_) {
    // GPU模式:包装CUDA设备指针为CuPy数组
    PyObject* cupy_module = PyImport_ImportModule("cupy");
    PyObject* cupy_ndarray = PyObject_GetAttrString(cupy_module, "ndarray");
    
    // 构造参数:shape、dtype、设备指针
    PyObject* shape_args = PyTuple_New(1);
    PyTuple_SetItem(shape_args, 0, py_dim);
    PyObject* dtype_obj = PyObject_GetAttrString(PyImport_ImportModule("numpy"), "float32");
    PyObject* memptr_obj = PyLong_FromVoidPtr(image);
    
    py_image = PyObject_CallFunctionObjArgs(cupy_ndarray, shape_args, dtype_obj, memptr_obj, NULL);
    
    // 释放临时对象
    Py_DECREF(shape_args);
    Py_DECREF(dtype_obj);
    Py_DECREF(memptr_obj);
    Py_DECREF(cupy_ndarray);
    Py_DECREF(cupy_module);
} else {
    // CPU模式:包装主机指针为NumPy数组
    import_array(); // 必须初始化NumPy C API
    npy_intp shape[3] = {dim[0], dim[1], dim[2]};
    PyArrayObject* np_array = (PyArrayObject*)PyArray_SimpleNewFromData(
        3, shape, NPY_FLOAT, (void*)image
    );
    // 设置数组不拥有内存,避免Python侧释放C++分配的内存
    PyArray_SetBaseObject(np_array, NULL);
    py_image = (PyObject*)np_array;
}

2. C++侧:将返回的PyObject转为C数组指针(filtered_image = ???)

根据运行模式提取底层指针:

float* filtered_image = nullptr;
if (is_gpu_) {
    // 从CuPy数组提取CUDA设备指针
    PyObject* memptr_attr = PyObject_GetAttrString(py_filtered_image, "data");
    if (memptr_attr) {
        PyObject* memptr_obj = PyObject_GetAttrString(memptr_attr, "memptr");
        filtered_image = (float*)PyLong_AsVoidPtr(memptr_obj);
        Py_DECREF(memptr_obj);
        Py_DECREF(memptr_attr);
    }
} else {
    // 从NumPy数组提取主机指针
    if (PyArray_Check(py_filtered_image)) {
        filtered_image = (float*)PyArray_DATA((PyArrayObject*)py_filtered_image);
    }
}

3. Python侧:将PyObject转为NumPy/CuPy数组(image_array = ???)

基于self.gpu_标记,直接将指针包装为数组,无数据拷贝:

import numpy as np
import cupy as cp
from ctypes import c_void_p

class Filter:
    def __init__(self, gpu):
        self.gpu_ = gpu

    def apply_filter(self, image_ptr, dim):
        if self.gpu_:
            # GPU模式:将设备指针转为CuPy数组
            image_array = cp.ndarray(shape=dim, dtype=np.float32, memptr=image_ptr)
        else:
            # CPU模式:将主机指针转为NumPy数组
            image_array = np.ndarray(shape=dim, dtype=np.float32, buffer=c_void_p(image_ptr))
        apply_filter_(image_array)
        # ...后续处理

4. Python侧:将NumPy/CuPy数组转为指针返回(filtered_image_ptr = ???)

提取数组底层指针并转为Python整数返回:

def apply_filter(self, image_ptr, dim):
        # ...前面的代码
        apply_filter_(image_array)
        if self.gpu_:
            # 提取CuPy设备指针
            filtered_image_ptr = image_array.data.memptr
        else:
            # 提取NumPy主机指针
            filtered_image_ptr = image_array.ctypes.data_as(c_void_p).value
        return filtered_image_ptr

关键注意事项

  • 内存所有权:必须明确内存由C还是Python侧管理。若C分配内存,Python侧需设置数组不拥有内存;若Python侧分配内存,C++侧需通过Python API释放内存。
  • 错误处理:实际代码需添加PyErr_Occurred()检查,避免内存泄漏或程序崩溃。
  • 类型匹配:确保指针类型与数组dtype严格匹配(此处均为float32),避免类型错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 10:43:28