【问题标题】:How to pass a 2d array from Python to C?如何将二维数组从 Python 传递给 C?
【发布时间】:2020-03-02 19:20:56
【问题描述】:

我正在尝试使用 ctypes 将二维数组从 Python 传递到 C。 数组 dtype 是 uint16。 我写了一个简单的代码来了解它是如何工作的:

C:

#include <stdint.h>

__declspec(dllexport) uint16_t Test(uint16_t **arr)
{
     return (arr[5][5]);
}

Python:

import numpy as np
from ctypes import cdll
import ctypes
from numpy.ctypeslib import ndpointer

_p = ndpointer(dtype=np.uint16, ndim=2, shape=(10, 10), flags='C')
mydll = cdll.LoadLibrary("mydll.dll")
_func = mydll.Test
_func.argtypes = [_p]
_func.restypes = ctypes.c_uint16

data = np.empty([10, 10], dtype=np.uint16)
data[5, 5] = 5
print(_func(data))

我得到 OSError: 访问冲突读取 0xFFFFFFFFFFFFFFFFFFFFFF 我做错了什么,我该如何解决?

【问题讨论】:

    标签: python c arrays pointers ctypes


    【解决方案1】:

    列出[SciPy.Docs]: C-Types Foreign Function Interface (numpy.ctypeslib)(和[Python 3.Docs]: ctypes - A foreign function library for Python以防万一)。

    这和[SO]: Calling C function in Python through ctypes, but function returns incorrect value (@CristiFati's answer) 完全一样(重复),只是碰巧也涉及到NumPy
    换句话说,你有 Undefined Behavior,因为 argtypes 必须是 CTypes 类型(不是 NumPy)。

    下面是你的代码的修改版本,它可以工作。

    dll00.c

    #include <stdint.h>
    
    #if defined(_WIN32)
    #  define DLL00_EXPORT_API __declspec(dllexport)
    #else
    #  define DLL00_EXPORT_API
    #endif
    
    
    #if defined(__cplusplus)
    extern "C" {
    #endif
    
    DLL00_EXPORT_API uint16_t dll00Func00(uint16_t **ppArr);
    
    #if defined(__cplusplus)
    }
    #endif
    
    
    uint16_t dll00Func00(uint16_t **ppArr)
    {
        return ppArr[5][5];
    }
    

    code00.py

    #!/usr/bin/env python3
    
    import sys
    import ctypes as ct
    import numpy as np
    
    
    DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
    
    
    def main(*argv):
        UI16Ptr = ct.POINTER(ct.c_uint16)
        UI16PtrPtr = ct.POINTER(UI16Ptr)
    
        dll00 = ct.CDLL(DLL_NAME)
        dll00Func00 = dll00.dll00Func00
        dll00Func00.argtypes = [UI16PtrPtr]
        dll00Func00.restype = ct.c_uint16
    
    
        dim0 = 10
        dim1 = 10
        np_arr_2d = np.empty([dim0, dim1], dtype=np.uint16)
    
        np_arr_2d[5][5] = 5
        print(np_arr_2d)
    
        # The "magic" happens in the following lines of code
        ct_arr = np.ctypeslib.as_ctypes(np_arr_2d)
        UI16PtrArr = UI16Ptr * ct_arr._length_
        ct_ptr = ct.cast(UI16PtrArr(*(ct.cast(row, UI16Ptr) for row in ct_arr)), UI16PtrPtr)
        res = dll00Func00(ct_ptr)
    
        print("\n{0:s} returned: {1:d}".format(dll00Func00.__name__, res))
    
    
    if __name__ == "__main__":
        print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")),
                                                       64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        print("NumPy: {0:s}\n".format(np.version.version))
        rc = main(*sys.argv[1:])
        print("\nDone.")
        sys.exit(rc)
    

    输出

    [cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q058727931]> sopr.bat
    ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###
    
    [prompt]> "c:\Install\x86\Microsoft\Visual Studio Community\2017\VC\Auxiliary\Build\vcvarsall.bat" x64>nul
    
    [prompt]> dir /b
    code00.py
    dll00.c
    
    [prompt]> cl /nologo /DDLL dll00.c  /link /NOLOGO /DLL /OUT:dll00.dll
    dll00.c
       Creating library dll00.lib and object dll00.exp
    
    [prompt]> dir /b
    code00.py
    dll00.c
    dll00.dll
    dll00.exp
    dll00.lib
    dll00.obj
    
    [prompt]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py
    Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 064bit on win32
    
    NumPy: 1.16.2
    
    [[19760  5277   632     0 32464  5280   632     0   111   114]
     [  107    92    68   101   118    92    86    69   110   118]
     [  115    92   112   121    95    48    54    52    95    48]
     [   51    46    48    55    46    48    51    95   116   101]
     [  115   116    48    92   108   105    98    92   115   105]
     [  116   101    45   112    97     5   107    97   103   101]
     [  115    92   110   117   109   112   121    92   116   101]
     [  115   116   105   110   103    92    95   112   114   105]
     [  118    97   116   101    92   110   111   115   101   116]
     [  101   115   116   101   114    46   112   121     0     0]]
    
    dll00Func00 returned: 5
    
    Done.
    

    所有这些时髦转换的解释可以在[SO]: C++ & Python: Pass and return a 2D double pointer array from python to c++ (@CristiFati's answer)(以及引用的[SO]: Problems with passing and getting arrays for a C function using ctypes (@CristiFati's answer))找到。

    【讨论】:

    • 哦,伙计,你在那里做的事情太疯狂了,而且很有效,非常感谢。但是可以说,我不想返回一个 uint16,而是返回一个在 C 中分配的 uint8 数组,并通过 python 作为一个 numpy 数组访问它?数组是 uint8 但指向数组开头的指针是 32 位
    • 如果使用 CTypes 作为 PythonC 之间的层,则必须保持一致。您可能需要的任何 NumPy 数组都将由 CTypes 数组(或缓冲区)构造(在 Python 中)。我要重复一遍,但是如果您知道 C 所需的数组长度,请将其作为函数的附加(输出)参数而不是返回值(因为您不会不必担心内存泄漏)。备注:我的答案末尾的 2 个 URL 包含您需要的确切信息。如果有不清楚的地方,请随时提出另一个问题:)
    • 是的,我明白了,非常感谢,在 python 中分配内存并将数组作为参数传递是个好主意
    • 不客气!如果它回答了您的问题,请接受答案,以便其他人也能够承认它([SO]: What should I do when someone answers my question?)。
    • 如何将 ct_ptr 转换回 numpy 二维数组?
    猜你喜欢
    • 1970-01-01
    • 2020-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-10
    相关资源
    最近更新 更多