【问题标题】:Using ctypes to call a C++ method with parameters from Python results in "Don't know how to convert parameter" error使用 ctypes 从 Python 调用带有参数的 C++ 方法会导致“不知道如何转换参数”错误
【发布时间】:2021-12-05 03:17:56
【问题描述】:

我正在尝试使用 Python3.7 中的以下 C++ 类,但无法让第一个方法“Set”工作,更不用说运算符重载方法了。我已经尝试了 Python 包装器和 extern 块的许多变体,但我得到“不知道如何转换参数 5”错误或分段错误。网上的例子和我找到的 SO 答案太基础或解决其他问题。

我的 argtypes 第一个参数应该是指向对象的指针吗?我不知道用什么语法来表示。

CMatrix.h:

#include <stdio.h>

class CMatrix
{
public:
    CMatrix(int d1);
    CMatrix(int d1, int d2);
    CMatrix(int d1, int d2, int d3);
    CMatrix (float f1, float f2, float f3);
    ~CMatrix();
    
    void Set(int x, int y, int z, float f);
    void operator=(const float f);
    CMatrix& operator=(const CMatrix &cm);
    inline float& operator()(const int i) {
        return m[i];}
    inline float& operator()(const int i, const int j) {
        return m[i*s2+j];}
    inline float& operator()(const int i, const int j, const int k) {
        return m[i*s23+j*s3+k];}
    int s1, s2, s3;     // dimensions of array
    int s23;            // s2*s3;
    float *m;           // pointer to first element of matrix.
    int dimensions;     // 1, 2, or 3.
};

extern "C" {
    CMatrix* CMatrix_new1(int i) {return new CMatrix(i); }
    CMatrix* CMatrix_new2(int i, int j) {return new CMatrix(i, j); }
    CMatrix* CMatrix_new3(int i, int j, int k) {return new CMatrix(i, j, k); }
    void CMatrix_Set(CMatrix* cm, int x, int y, int z, float f) {cm->Set(x, y, z, f); }
}

cmWrapper.py:

import ctypes as c
lib = c.cdll.LoadLibrary('./libCMatrix.o')
lib.CMatrix_Set.argtypes = [c.c_int, c.c_int, c.c_int, c.c_float]

class CMatrix(object):
    def __init__(self, i, j, k):
        if j==0 and k==0:
            self.obj = lib.CMatrix_new1(i)
        elif k==0:
            self.obj = lib.CMatrix_new2(i, j)
        else:
            self.obj = lib.CMatrix_new3(i, j, k)

    def Set(self, x, y, z, f):
        lib.CMatrix_Set(self.obj, x, y, z, f)

cm = CMatrix(2, 3, 0)
cm.Set(1, 2, 0, 99.0)

回溯:

>>> import cmWrapper
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/.../ctypes/cmWrapper.py", line 18, in <module>
    cm.Set(1, 2, 0, 99.0)
  File "/.../ctypes/cmWrapper.py", line 15, in Set
    lib.CMatrix_Set(self.obj, x, y, z, f)
ctypes.ArgumentError: argument 5: <class 'TypeError'>: Don't know how to convert parameter 5

如果重要的话,我使用以下代码编译了 C++ 代码:

g++ -c -fPIC CMatrix.cpp -o CMatrix.o
g++ -shared -Wl -o libCMatrix.o CMatrix.o

这是在运行 10.15.7 的 Mac 上。

来自 lldb:

Executable module set to "/Library/Frameworks/Python.framework/Versions/3.7/Resources/Python.app/Contents/MacOS/Python".
Architecture set to: x86_64h-apple-macosx-.
(lldb) 
There is a running process, detach from it and attach?: [Y/n] n
(lldb) thread list
Process 57460 stopped
* thread #1: tid = 0x47364c, 0x00007fff202f5656 libsystem_kernel.dylib`__select + 10, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
(lldb) thread continue
Resuming thread 0x47364c in process 57460
Process 57460 resuming
Process 57460 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x13835f20)
    frame #0: 0x00000001015f19b2 libCMatrix.o`CMatrix::Set(int, int, int, float) + 34
libCMatrix.o`CMatrix::Set:
->  0x1015f19b2 <+34>: movl   (%rax), %esi
    0x1015f19b4 <+36>: movl   0x4(%rax), %edx
    0x1015f19b7 <+39>: movl   0x8(%rax), %ecx
    0x1015f19ba <+42>: movl   0xc(%rax), %r8d
Target 0: (Python) stopped.

【问题讨论】:

    标签: python c++ methods parameter-passing ctypes


    【解决方案1】:

    您必须完全匹配参数。设置您使用的每个函数的.argtypes.restype,以便ctypes 可以正确地将参数编组为C 并再次返回。如果不设置 .restype ctypes 假定返回值为 c_int(通常是带符号的 32 位整数)而不是(可能是 64 位)指针。

    这是一个工作示例。我没有充实每个功能,因为一个就足够了。在 32 位和 64 位 Python 上测试。

    test.cpp(使用 MS 编译器构建,cl /LD /EHsc /W4 test.cpp):

    #include <stdio.h>
    
    // Needed to export functions on Windows
    #ifdef _WIN32
    #   define API __declspec(dllexport)
    #else
    #   define API
    #endif
    
    class CMatrix
    {
    public:
        CMatrix(int d1) : s1(d1) { m = new float[d1]; }
        ~CMatrix() { delete [] m; }
        const float* Get(int& s) { s = s1; return m; }
        void Set(int x, float f) { m[x] = f; }
        int s1;
        float *m;
    };
    
    extern "C" {
        API CMatrix* CMatrix_new(int i) {return new CMatrix(i); }
        API const float* CMatrix_Get(CMatrix* cm, int& x) { return cm->Get(x); }
        API void CMatrix_Set(CMatrix* cm, int x, float f) { cm->Set(x, f); }
        API void CMatrix_delete(CMatrix* cm) { delete cm; }
    }
    

    test.py

    import ctypes as ct
    
    # For type checking the returned pointer.
    class _CMatrix(ct.c_void_p) : pass
    PCMatrix = ct.POINTER(_CMatrix)
    
    class CMatrix:
    
        _dll = ct.CDLL('./test')
        _dll.CMatrix_new.argtypes = ct.c_int, 
        _dll.CMatrix_new.restype = PCMatrix
        _dll.CMatrix_Get.argtypes = PCMatrix, ct.POINTER(ct.c_int)
        _dll.CMatrix_Get.restype = ct.POINTER(ct.c_float)
        _dll.CMatrix_Set.argtypes = PCMatrix, ct.c_int, ct.c_float
        _dll.CMatrix_Set.restype = None
        _dll.CMatrix_delete.argtypes = PCMatrix, 
        _dll.CMatrix_delete.restype = None
    
        def __init__(self, i):
            self.obj = self._dll.CMatrix_new(i)
    
        def Set(self, x, f):
            self._dll.CMatrix_Set(self.obj, x, f)
    
        def Get(self):
            size = ct.c_int()
            m = self._dll.CMatrix_Get(self.obj, ct.byref(size))
            return m[:size.value]
    
        def __del__(self):
            self._dll.CMatrix_delete(self.obj)
    
    cm = CMatrix(2)
    cm.Set(0, 1.5)
    cm.Set(1, 2.5)
    print(cm.Get())
    

    输出:

    [1.5, 2.5]
    

    【讨论】:

    • 原始 C++ 代码中存在导致分段错误的错误。在按照您的示例进行小幅更正后,我得到了它的工作。
    【解决方案2】:

    问题来了:

    lib.CMatrix_Set.argtypes = [c.c_int, c.c_int, c.c_int, c.c_float]
    

    您忘记了 self.obj 参数的条目。改成这样:

    lib.CMatrix_Set.argtypes = [c.c_void_p, c.c_int, c.c_int, c.c_int, c.c_float]
    

    此外,您可能应该将其设置为您调用的其他函数的restype,这样在intvoid * 窄的系统上,返回值就不会被截断。

    您的另一个选择是创建一个继承自 ctypes.Structure 的 Python 类,对应于您的 C++ 类,然后使用指向该类型的指针作为类型,但是您必须非常小心您的 C++ 代码或布局最终可能与 C 中的不同,以及 Python 的预期。

    【讨论】:

    • 现在它会立即出现分段错误。
    • @TomSpargo 当你使用调试器时,它说什么触发了段错误?
    • '进程以退出代码 139 结束(被信号 11 中断:SIGSEGV'
    • @TomSpargo 您没有正确使用调试器。如果你使用得当,它会在段错误发生时暂停程序,而不是让它退出。
    • 将变量值的屏幕截图放在初始帖子中。调试器刚刚停止,没有错误消息。
    猜你喜欢
    • 2013-10-08
    • 1970-01-01
    • 2021-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-05
    相关资源
    最近更新 更多