Python ctypes:传递引用参数错误

6
我有一个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: : 错误的类型
所以,n_y 的类型是不正确的。我应该放什么呢?
非常感谢您的帮助!
更新
根据@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))

出现了分段错误,该错误发生在该行:

*y = &_y[0];

感谢您的帮助!

你尝试过使用指向 int 的指针吗? - Giacomo Alzetta
@GiacomoAlzetta 你的意思是n_y和n_z是指向整型的指针吗? - f10w
我认为ctypes不理解*C ++*的引用传递。这就是为什么你应该坚持使用指针的原因。 - CristiFati
@GiacomoAlzetta和CristiFati:感谢您们的评论。我已经花了一个小时在这上面,但仍然失败了。请查看问题中的更新。再次感谢! - f10w
2
希望你的实际代码不会像那样“将向量转换回数组”,因为一旦你的向量自毁,就会出现悬空指针。 - user2357112
显示剩余4条评论
2个回答

5
你已经接近成功了。
与此同时,请保持接近[Python 3.Docs]: ctypes - Python的外部函数库
请记住,无论在何处,您都应以相同方式处理指针参数(实际上适用于所有参数,但对于非指针参数,事情很简单)。
换句话说,您在C语言中所做的事情(实例化一个变量并将其指针传递给函数),您也应该在Python中这样做(而不是实例化变量指针并将其传递给函数)。
转换成代码,您应修改初始化yn_y和函数(myfunction)调用的方式:
>>> 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)以避免内存泄漏
    • 使它们成为静态

一些远程连接的示例:


非常感谢您,@CristiFati!!! 我非常抱歉回复晚了。在收到您的答案之前,我不得不转向另一个紧急项目,所以我真的找不到时间早些时候进行测试和反馈。再次感谢您宝贵的帮助! - f10w
很高兴能够帮助! - CristiFati

4

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

您的向量是局部变量,并且在函数返回时被释放,因此您需要保留内存。

这是重新设计以保留内存的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接口。

1
非常感谢您详细的回答!对于我的迟回复,我很抱歉。由于@CristiFati的回答先被发布,所以我接受了他的回答。但是您的回答也很好,除了点赞之外,我还找到了您的另一个回答并进行了点赞。谢谢! - f10w

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接