考虑下面这个简单的C源代码,它计算一个由int组成的数组的平均值,将其存储在一个 struct 中并返回错误代码:

#include <stdio.h>

enum errors {
    NO_ERRORS,
    EMPTY_ARRAY
};

struct Result {
    double mean;
};

enum errors calculateMean(struct Result *result, int *array, int length) {
    if (length == 0) {
        return EMPTY_ARRAY; // Return EMPTY_ARRAY if the array is empty
    }

    int sum = 0;
    for (int i = 0; i < length; i++) {
        sum += array[i];
    }

    result->mean = (double)sum / length;

    return NO_ERRORS; // Return NO_ERRORS if calculation is successful
}

代码被编译成一个名为libtest_ctypes.so的共享库.

我有以下ctype接口:

import ctypes
import enum

import numpy as np
import enum

lib_path = "./libtest_ctypes.so" 
lib = ctypes.CDLL(lib_path)

# The Result structure
class Result(ctypes.Structure):
    _fields_ = [("mean", ctypes.c_double)]

# The Errors enum
class Errors(enum.IntEnum):
    NO_ERRORS = 0
    EMPTY_ARRAY = 1

# Defining a signature for `calculateMean`
calculate_mean = lib.calculateMean
calculate_mean.argtypes = [ctypes.POINTER(Result), ctypes.POINTER(ctypes.c_int), ctypes.c_int]
calculate_mean.restype = Errors


# Runs `calculateMean`
def calculate_mean_interface(array):
    result = Result()
    length = len(array)

    c_array = array.ctypes.data_as(ctypes.POINTER(ctypes.c_int))

    error = calculate_mean(ctypes.byref(result), c_array, length)
    return error, result


if __name__ == "__main__":
    array = np.array([1, 2, 3, 4, 5])

    error, result = calculate_mean_interface(array)

    if error == Errors.EMPTY_ARRAY:
        print("Error: Empty array!")
    elif error == Errors.NO_ERRORS:
        print("Mean:", result.mean)

运行Python接口会给出错误的结果1.2. 据我所知,这是由于我机器上的NumPy数组(64位整数)和C的int之间的类型不同. 我可以得到正确的结果3.0,通过NumPy的.astype()将数组转换为ctype.c_int:

def calculate_mean_interface(array):
    result = Result()
    length = len(array)

    #** CAST ARRAY TO CTYPES.C_INT**
    array = array.astype(ctypes.c_int)
    c_array = array.ctypes.data_as(ctypes.POINTER(ctypes.c_int))

    error = calculate_mean(ctypes.byref(result), c_array, length)
    return error, result

然而,NumPy的选角需要额外的内存和时间. 什么是最好的方法,以达到正确的结果,而不是铸造? 我希望这是可移植的,如果可能的话,我不想在初始化NumPy数组时指定数据类型.

推荐答案

根据[NumPy]: Scalars - class numpy.int_(emphasis是我的):

有符号整数类型,与Python intC long兼容.

所以,你必须在所有地方都使用它(并保持一致),否则你会得到100ndefined 101ehavior分.

在您的示例中(Little Endian Nix OS(和Python build)),内存布局为(以HEX表示(每Byte两位数)):

NumPy array - 5 064bit (long) items: (1 + 2 + 3 + 4 + 5) / 5 = 3.0
╔══════L0══════╗╔══════L1══════╗╔══════L2══════╗╔══════L3══════╗╔══════L4══════╗
10000000000000002000000000000000300000000000000040000000000000005000000000000000
╚══I0══╝╚══I1══╝╚══I2══╝╚══I3══╝╚══I4══╝ ...
C array (same memory) - 5 032bit (int) items: (1 + 0 + 2 + 0 + 3) / 5 = 1.2

有关整数类型的更多详细信息,请查看[SO]: Maximum and minimum value of C types integers from Python (@CristiFati's answer).

以下是一个有效的版本.

  • dll00.c:

    #include <stdio.h>
    
    #if defined(_WIN32)
    #  define DLL00_EXPORT_API __declspec(dllexport)
    #else
    #  define DLL00_EXPORT_API
    #endif
    
    
    typedef enum {
        SUCCESS = 0,
        EMPTY_ARRAY,
        NULL_POINTER,
        UNSPECIFIED,
    } Errors;
    
    
    typedef struct {
        double mean;
    } Result;
    
    
    #if defined(__cplusplus)
    extern "C" {
    #endif
    
    DLL00_EXPORT_API Errors calculateMean(Result *result, long *array, int length);
    
    #if defined(__cplusplus)
    }
    #endif
    
    
    Errors calculateMean(Result *result, long *array, int length)
    {
        printf("From C - element size: %zu\n", sizeof(long));
        if (length == 0) {
            return EMPTY_ARRAY;
        }
        if ((result == NULL) || (array == NULL)) {
            return NULL_POINTER;
        }
        long sum = 0;
        for (int i = 0; i < length; ++i) {
            sum += array[i];
        }
        result->mean = (double)sum / length;
        return SUCCESS;
    }
    
  • code00.py:

    #!/usr/bin/env python
    
    import ctypes as cts
    import enum
    import sys
    
    import numpy as np
    
    
    class Result(cts.Structure):
        _fields_ = (
            ("mean", cts.c_double),
        )
    
    
    class Errors(enum.IntEnum):
        SUCCESS = 0,
        EMPTY_ARRAY = 1,
        NULL_POINTER = 2,
        UNSPECIFIED = 3,
    
    
    DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
    
    
    calculate_mean = None  # Initialize it in main
    
    
    def calculate_mean_interface(array):
        result = Result()
        length = len(array)
    
        c_array = array.ctypes.data_as(cts.POINTER(cts.c_long))
        error = calculate_mean(cts.byref(result), c_array, length)
        return error, result
    
    
    def main(*argv):
    
        dll = cts.CDLL(DLL_NAME)
        global calculate_mean
        calculate_mean = dll.calculateMean
        calculate_mean.argtypes = (cts.POINTER(Result), cts.POINTER(cts.c_long), cts.c_int)
        calculate_mean.restype = Errors
    
    
        array = np.array([1, 2, 3, 4, 5])
        print(f"NP array type: {array.dtype}")
    
        error, result = calculate_mean_interface(array)
        if error != Errors.SUCCESS:
            print(f"Error: {error}")
        else:
            print(f"Mean: {result.mean}")
    
    
    if __name__ == "__main__":
        print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                       64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        rc = main(*sys.argv[1:])
        print("\nDone.\n")
        sys.exit(rc)
    

Output:

  • Nix (Linux):

    [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackExchange/StackOverflow/q077965251]> . ~/sopr.sh 
    ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###
    
    [064bit prompt]> 
    [064bit prompt]> ls
    code00.py  dll00.c
    [064bit prompt]> 
    [064bit prompt]> gcc -fPIC -shared -o dll00.so dll00.c
    [064bit prompt]> ls
    code00.py  dll00.c  dll00.so
    [064bit prompt]> 
    [064bit prompt]> /home/cfati/Work/Dev/VEnvs/py_pc064_03.10_test0/bin/python ./code00.py
    Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] 064bit on linux
    
    NP array type: int64
    From C - element size: 8
    Mean: 3.0
    
    Done.
    
  • Win:

    [cfati@CFATI-W10PC064:e:\Work\Dev\StackExchange\StackOverflow\q077965251]> sopr.bat
    ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###
    
    [prompt]>
    [prompt]>  "c:\Install\pc032\Microsoft\VisualStudioCommunity\2019\VC\Auxiliary\Build\vcvarsall.bat" x64 > nul
    
    [prompt]> dir /b
    code00.py
    dll00.c
    dll00.so
    
    [prompt]> cl /nologo /MD /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
    dll00.so
    
    [prompt]>
    [prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py
    Python 3.10.10 (tags/v3.10.10:aad5f6a, Feb  7 2023, 17:20:36) [MSC v.1929 64 bit (AMD64)] 064bit on win32
    
    NP array type: int32
    From C - element size: 4
    Mean: 3.0
    
    Done.
    

您可能还想判断一下:

Python相关问答推荐

我在使用fill_between()将最大和最小带应用到我的图表中时遇到问题

我从带有langchain的mongoDB中的vector serch获得一个空数组

如何使用html从excel中提取条件格式规则列表?

删除字符串中第一次出现单词后的所有内容

如何调整QscrollArea以正确显示内部正在变化的Qgridlayout?

如果条件不满足,我如何获得掩码的第一个索引并获得None?

如何在Python中找到线性依赖mod 2

如何找出Pandas 图中的连续空值(NaN)?

Numpyro AR(1)均值切换模型抽样不一致性

计算空值

从一个df列提取单词,分配给另一个列

使用tqdm的进度条

为什么在Python中00是一个有效的整数?

仅取消堆叠最后三列

如何在Python中创建仅包含完整天数的月份的列表

遍历列表列表,然后创建数据帧

高效地计算数字数组中三行上三个点之间的Angular

如何在Python中实现高效地支持字典和堆操作的缓存?

ValueError:必须在Pandas 中生成聚合值

Pandas 数据框自定义排序功能