Python ctypes:通过引用错误传递参数

Python ctypes: pass argument by reference error

我有一个 C++ 函数,我希望你在 Python 2.7.12 中调用它,如下所示:

extern "C" {
    double* myfunction(double* &y, double* &z, int &n_y, int &n_z, int a, int b)
    {
        vector<double> _x;
        vector<double> _y;
        vector<double> _z;

        // Call some external C++ function
        cpp_function(_x, _y, _z, a, b);

        // Convert vectors back to arrays
        double* x = &_x[0]; // or x = _x.data();
        y = &_y[0];
        z = &_z[0];
        n_y = static_cast<int>(_y.size());
        n_z = static_cast<int>(_z.size());
        return x;
    }
}

基本上这个函数将两个整数 a,b 作为输入(加上为清楚起见我省略的一些其他数据)并在将结果放入两个数组 y, z 及其各自的大小之前进行一些计算进入 n_y, n_z,并返回一个大小为 a*b.

的数组 x

将此函数构建到共享库 myfunction.so 后,我在 Python 中调用它如下:

from ctypes import *

libc = CDLL('myfunction.so')
myfunction = libc.myfunction

myfunction.restype = POINTER(c_double)
myfunction.argtypes = [POINTER(c_double), POINTER(c_double),
                       c_int, c_int,
                       c_int, c_int]

y = POINTER(c_double)()
z = POINTER(c_double)()
n_y = c_int()
n_z = c_int()

a = 18
b = 18
x = myfunction(byref(y), byref(z),
               byref(n_y), byref(n_z),
               c_int(a), c_int(b))

运行这个脚本我获取错误:

ctypes.ArgumentError: argument 3: : wrong type

所以n_yc_int类型不正确。我应该放什么?

非常感谢您的帮助!


更新

根据@GiacomoAlzetta 和@CristiFati 的建议,我更改了我的代码以使用指针而不是通过引用传递,如下所示。

(yz 相似所以让我省略 z)

extern "C" {
    double* myfunction(double** y, int* n_y, int a, int b)
    {
        vector<double> _x;
        vector<double> _y;

        // Call some external C++ function
        cpp_function(_x, _y, a, b);

        // Convert vectors back to arrays
        double* x = &_x[0]; // or x = _x.data();
        *y = &_y[0];
        *n_y = static_cast<int>(_y.size());
        return x;
    }
}

现在在C++中,我调用上面的函数如下:

double* y;
int n_y;
int a = 18;
int b = 18;
double* x = myfunction(&y, &n_y, a, b);

有效。在 Python:

from ctypes import *

libc = CDLL('myfunction.so')
myfunction = libc.myfunction

myfunction.restype = POINTER(c_double)
myfunction.argtypes = [POINTER(POINTER(c_double)), POINTER(c_int),
                       c_int, c_int]

y = POINTER(POINTER(c_double))()
n_y = POINTER(c_int)()

a = 18
b = 18
x = myfunction(y, n_y, c_int(a), c_int(b))

产生了 Segmentation fault 错误,发生在行

*y = &_y[0];

感谢您的帮助!

你快到了。
同时,靠近[Python 3.Docs]: ctypes - A foreign function library for Python

请记住,无论您身在何处,都应该以相同的方式处理指针参数(实际上它适用于所有指针参数,但对于非指针参数,事情很简单)。

换句话说,您在 C 中所做的事情(实例化一个变量传递它的指针 到函数),你也应该在 Python 中做( 而不是 实例化变量指针将它传递给函数。

翻译成代码,你应该修改初始化yn_y和函数(我的函数) 调用:

>>> from ctypes import *  # Anti-pattern. Don't ever use it
>>>
>>> y = POINTER(c_double)()
n_y = c_int()
a = 18
b = 18
x = myfunction(pointer(y), pointer(n_y), a, b)

备注:

  • 我在评论中所说的(未定义的行为 因为向量存在于堆栈中,并且在退出函数时将被销毁)仍然有效。要修复它:
    • 在返回数据之前在堆上分配数据 (malloc / new)(完成后,您还需要释放它 (free / delete),以避免内存泄漏)
    • 使它们静态

一些远程连接的例子:

您可以使用引用,因为引用只是另一层指针的语法。

你的向量是局部变量,在你的函数returns时被释放,所以你需要保留内存。

这是为保留内存而重新编写的 C++ 代码。由于您的示例不完整,我刚刚用一些数据创建了一些局部变量:

#define API __declspec(dllexport)  // Windows-specific export
#include <cstdlib>
#include <vector>

using namespace std;

extern "C" {
    API double* myfunction(double* &y, double* &z, int &n_x, int &n_y, int &n_z)
    {
        vector<double> _x {1.1,2.2,3.3};
        vector<double> _y {4.4,5.5};
        vector<double> _z {6.6,7.7,8.8,9.9};

        // Allocate some arrays to store the vectors.
        double* x = new double[_x.size()];
        y = new double[_y.size()];
        z = new double[_z.size()];
        memcpy(x,_x.data(),_x.size() * sizeof(double));
        memcpy(y,_y.data(),_y.size() * sizeof(double));
        memcpy(z,_z.data(),_z.size() * sizeof(double));
        n_x = static_cast<int>(_x.size());
        n_y = static_cast<int>(_y.size());
        n_z = static_cast<int>(_z.size());
        return x;
    }

    // A function to free up the memory.
    API void myfree(double* x, double* y, double* z)
    {
        delete [] x;
        delete [] y;
        delete [] z;
    }
}

Python:

from ctypes import *

dll = CDLL('test')
dll.myfunction.argtypes = (POINTER(POINTER(c_double)),
                           POINTER(POINTER(c_double)),
                           POINTER(c_int),
                           POINTER(c_int),
                           POINTER(c_int))
dll.myfunction.restype = POINTER(c_double)

dll.myfree.argtypes = POINTER(c_double),POINTER(c_double),POINTER(c_double)
dll.myfree.restype = None

# Helper function to allocate storage for return arrays
def myfunction():
    y = POINTER(c_double)() # create an instance of a C double*
    z = POINTER(c_double)()
    n_x = c_int()           # and instances of C int
    n_y = c_int()
    n_z = c_int()

    # Pass them all by reference so new values can be returned
    x = dll.myfunction(byref(y),byref(z),byref(n_x),byref(n_y),byref(n_z))

    # Copies the data into Python lists
    a = x[:n_x.value]
    b = y[:n_y.value]
    c = z[:n_z.value]

    # Free the C arrays and return the Python lists.
    dll.myfree(x,y,z)
    return a,b,c

x,y,z = myfunction()
print(x,y,z)

输出:

[1.1, 2.2, 3.3] [4.4, 5.5] [6.6, 7.7, 8.8, 9.9]

请注意,正在进行大量复制。查看 numpy,它以 C 可以直接访问的格式创建数组,并具有内置的 ctypes 接口。