使用 ctypes 遍历指针

loop through a pointer with ctypes

我需要在我的 python 应用程序中使用现有的库。这是一个用于读取特定数据文件的库。如果您好奇,可以从这里下载 https://www.hbm.com/en/2082/somat-download-archive/ (somat libsie)。

这个问题跟进了我的 。我能够读取文件,现在我尝试读取的数据是顺序嵌套的……好吧,我不确定我在说什么。 这是头文件的一部分:

# ifdef _WIN32
typedef double sie_float64;
typedef unsigned int sie_uint32;
# else
typedef @sie_float64_type@ sie_float64;
typedef @sie_uint32_type@ sie_uint32;
# endif

[...]

/* At a library user level the internals of these objects are
 * unimportant, so we just make them generic pointers. */
typedef void sie_Context;
typedef void sie_File;
typedef void sie_Iterator;
typedef void sie_Test;
typedef void sie_Tag;
typedef void sie_Spigot;
typedef void sie_Output;
typedef void sie_Channel;
typedef void sie_Dimension;
typedef void sie_Histogram;
typedef void sie_Exception;
typedef void sie_Stream;

[...]

#define SIE_OUTPUT_NONE    0
#define SIE_OUTPUT_FLOAT64 1
#define SIE_OUTPUT_RAW     2

SIE_DECLARE(sie_float64 *) sie_output_get_float64(sie_Output *output, size_t dim);
/* > Returns a pointer to an array of float64 (double) data for the
 * > specified dimension.  This array has a size equal to the number
 * > of scans in the output.  This is only valid if the type of the
 * > dimension is `SIE_OUTPUT_FLOAT64`.  The lifetime of the return
 * > value is managed by the `sie_Output` object. */

typedef struct _sie_Output_Raw {
    void *ptr;
    size_t size;
    int reserved_1;
} sie_Output_Raw;

SIE_DECLARE(sie_Output_Raw *) sie_output_get_raw(sie_Output *output, size_t dim);
/* > As `sie_output_get_float64`, but for raw data; returns a pointer
 * > to an array of sie_Output_Raw for the specified dimension.  This
 * > array has a size equal to the number of scans in the output.  The
 * > `ptr` member of the `sie_Output_Raw` struct is a pointer to the
 * > actual data, `size` is the size of the data pointed at by `ptr`,
 * > in bytes.  The lifetime of the return value is managed by the
 * > `sie_Output` object. */

typedef struct _sie_Output_Dim {
    int type;
    sie_float64 *float64;
    sie_Output_Raw *raw;
} sie_Output_Dim;

typedef struct _sie_Output_Struct {
    size_t num_dims;
    size_t num_rows;
    size_t reserved_1;
    size_t reserved_2;
    sie_Output_Dim *dim;
} sie_Output_Struct;

SIE_DECLARE(sie_Output_Struct *) sie_output_get_struct(sie_Output *output);
/* > Returns an `sie_Output_Struct` pointer containing information
 * > about the `sie_Output` object.  This struct can be used in
 * > C-compatible languages to access all data in the `sie_Output`
 * > object.  The lifetime of the return value is managed by the
 * > `sie_Output` object.  */

这是他们提供的示例 C 代码的一部分:

#include <sie.h>

[...]

sie_Output_Struct* os;

[...]

/* There are several accessor functions to pull
 * properties out of an sie_Output object. */
num_dims = sie_output_get_num_dims(output);
num_rows = sie_output_get_num_rows(output);
printf("      Data block %lu, %lu dimensions, %lu rows:\n",
    (unsigned long)sie_output_get_block(output),
    (unsigned long)num_dims,
    (unsigned long)num_rows);

/* libsie offers several ways to get the data out of
 * the output object.  One way, more suitable for
 * languages that don't have easy access to C structs,
 * is to use sie_output_get_float64() and
 * sie_output_get_raw() to retrieve an array
 * containing one dimension's data.
 * sie_output_get_type() returns what kind of data a
 * column/dimension contains.
 *
 * However, in languages that can interpret C structs,
 * we can pull out one struct that contains all of the
 * data: */
os = sie_output_get_struct(output);
/* Note that this is simply a struct, not an SIE
 * object, so we can't call sie_release on it.  It is
 * owned by the output object and will go away when
 * that object does. */

 /* Now we can iterate through this C struct, printing
  * all the data. */
for (row = 0; row < num_rows; row++) {
    printf("        Row %lu: ", (unsigned long)row);
    for (dim = 0; dim < num_dims; dim++) {
        if (dim != 0)
            printf(", ");
        switch (os->dim[dim].type) {
            /* The type can be SIE_OUTPUT_FLOAT64, or
             * SIE_OUTPUT_RAW, as described above. */
        case SIE_OUTPUT_FLOAT64:
            printf("%.15g", os->dim[dim].float64[row]);
            break;
        case SIE_OUTPUT_RAW:
            /* Raw data has three parts:  "ptr", which
             * is a pointer to the data; "size", which
             * is the size of the data; and "claimed",
             * which should be set to 1 if you wish to
             * keep the data and clean up the memory
             * associated with it yourself.  If you
             * don't set the "claimed" field, the raw
             * data will be cleaned up with the rest
             * of the output with the output object
             * goes away. */
            uchar_p = os->dim[dim].raw[row].ptr;
            size = os->dim[dim].raw[row].size;
            if (size > 16) {
                printf("(raw data of size %lu.)",
                    (unsigned long)size);
            }
            else {
                for (byte = 0; byte < size; byte++)
                    printf("%02x", uchar_p[byte]);
            }
            break;
        }
    }
    printf("\n");
}

这是我的 python 代码:

import ctypes

# Define structures
# typedef @sie_float64_type@ sie_float64;
# typedef @sie_uint32_type@ sie_uint32;

class sie_Output_Raw(ctypes.Structure):
    _fields_ = [('ptr', ctypes.c_void_p),
                ('size', ctypes.c_ulonglong),
                ('reserved_1', ctypes.c_int)]


class sie_Output_Dim(ctypes.Structure):
    _fields_ = [('type', ctypes.c_int),
                ('sie_float64', ctypes.c_float),
                ('sie_Output_Raw', ctypes.c_void_p)]


class sie_Output_Struct(ctypes.Structure):
    _fields_ = [('num_dims', ctypes.c_ulonglong),
                ('num_rows', ctypes.c_ulonglong),
                ('reserved_1', ctypes.c_ulonglong),
                ('reserved_2', ctypes.c_ulonglong),
                #('raw', sie_Output_Dim)]
                ('sie_Output_Dim', ctypes.c_void_p)]


hllDll = ctypes.WinDLL(r"libsie.dll")

hllDll.sie_context_new.argtypes = ()
hllDll.sie_context_new.restype = ctypes.c_void_p

hllDll.sie_file_open.argtypes = ctypes.c_void_p, ctypes.c_char_p
hllDll.sie_file_open.restype = ctypes.c_void_p

hllDll.sie_get_tests.argtypes = ctypes.c_void_p,
hllDll.sie_get_tests.restype = ctypes.c_void_p

hllDll.sie_iterator_next.argtypes = ctypes.c_void_p,
hllDll.sie_iterator_next.restype = ctypes.c_void_p

hllDll.sie_get_channels.argtypes = ctypes.c_void_p,
hllDll.sie_get_channels.restype = ctypes.c_void_p

hllDll.sie_get_name.argtypes = ctypes.c_void_p,
hllDll.sie_get_name.restype = ctypes.c_char_p

hllDll.sie_spigot_get.argtypes = ctypes.c_void_p,
hllDll.sie_spigot_get.restype = ctypes.c_void_p

hllDll.sie_output_get_struct.argtypes = ctypes.c_void_p,
hllDll.sie_output_get_struct.restype = ctypes.c_void_p

hllDll.sie_output_get_num_dims.argtypes = ctypes.c_void_p,
hllDll.sie_output_get_num_dims.restype = ctypes.c_ulonglong

hllDll.sie_output_get_num_rows.argtypes = ctypes.c_void_p,
hllDll.sie_output_get_num_rows.restype = ctypes.c_ulonglong

hllDll.sie_output_get_float64.argtypes = ctypes.c_void_p, ctypes.c_ulonglong
hllDll.sie_output_get_float64.restype = ctypes.c_ulonglong


context = hllDll.sie_context_new()
file = hllDll.sie_file_open(context, "test.sie".encode('utf-8'))

channel_iterator = hllDll.sie_get_channels(file)
channel = hllDll.sie_iterator_next(channel_iterator)
spigot = hllDll.sie_attach_spigot(channel)
output = hllDll.sie_spigot_get(spigot)
num_dims = hllDll.sie_output_get_num_dims(output)
num_rows = hllDll.sie_output_get_num_rows(output)
os = sie_Output_Struct.from_address(hllDll.sie_output_get_struct(output))
os_dim = sie_Output_Dim.from_address(os.sie_Output_Dim)

这是我得到的,似乎有效:

但是如何获取 C 示例代码中的所有值?
我不是 C 编码员,我不完全理解 os->dim[dim].float64[row] 之类的行中发生的事情,它看起来像是在内存中循环。
我如何在 python 中做到这一点?

我希望我提供了足够的细节。 提前谢谢你。

这是一个工作示例,它访问提到的结构,其中包含关于更正结构定义的注释和遍历结构的 C 代码端口。有我用来验证访问是否正确写入的测试代码。

test.py

import ctypes as ct

SIE_OUTPUT_FLOAT64 = 1
SIE_OUTPUT_RAW = 2

class sie_Output_Raw(ct.Structure):
    _fields_ = [('ptr', ct.c_void_p),
                ('size', ct.c_size_t),      # better to use matching type for portability
                ('reserved_1', ct.c_int)]

class sie_Output_Dim(ct.Structure):
    _fields_ = [('type', ct.c_int),
                # This is a pointer type, and c_float is 32-bit so use c_double.
                # Also fixed structure names to match C.
                ('float64', ct.POINTER(ct.c_double)),
                # Use pointer to structure type since it is known instead of c_void_p.
                ('raw', ct.POINTER(sie_Output_Raw))]

class sie_Output_Struct(ct.Structure):
    _fields_ = [('num_dims', ct.c_size_t),
                ('num_rows', ct.c_size_t),
                ('reserved_1', ct.c_size_t),
                ('reserved_2', ct.c_size_t),
                ('dim', ct.POINTER(sie_Output_Dim))] # use defined structure pointer

# CDLL/WinDLL are the same on 64-bit, but WinDLL is for 32-bit C function
# using __stdcall calling convention.
# I made a test library for demonstration. See C code below.
hllDll = ct.CDLL(r'./test')

hllDll.sie_output_get_struct.argtypes = ct.c_void_p,
# use defined structure pointer
hllDll.sie_output_get_struct.restype = ct.POINTER(sie_Output_Struct)

os = hllDll.sie_output_get_struct(None)  # no need for from_address now

# port of the C code to access the structure.
# Use .contents to dereference a pointer and access the structure.
for row in range(os.contents.num_rows):
    for dim in range(os.contents.num_dims):
        if dim != 0:
            print(', ', end='')
        if os.contents.dim[dim].type == SIE_OUTPUT_FLOAT64:
            print(f'{os.contents.dim[dim].float64[row]:.15g}', end='')
        else:
            # Equivalent to casting a C 'void*' to 'unsigned char*'
            uchar_p = ct.cast(os.contents.dim[dim].raw[row].ptr, ct.POINTER(ct.c_ubyte))
            size = os.contents.dim[dim].raw[row].size
            if size > 16:
                print(f'(raw data of size {size}.)', end='')
            else:
                # Slicing a pointer to a particular size creates
                # creates a sized list of the array items the
                # pointer points to.  Result passed to bytes
                # for display.
                print(bytes(uchar_p[:size]), end='')
    print()

test.c - 带有 hard-coded 2x2 输出的测试代码。

#include <stdlib.h>

typedef double sie_float64;
typedef unsigned int sie_uint32;

typedef void sie_Output;

#define SIE_OUTPUT_NONE    0
#define SIE_OUTPUT_FLOAT64 1
#define SIE_OUTPUT_RAW     2

typedef struct _sie_Output_Raw {
    void *ptr;
    size_t size;
    int reserved_1;
} sie_Output_Raw;

typedef struct _sie_Output_Dim {
    int type;
    sie_float64 *float64;
    sie_Output_Raw *raw;
} sie_Output_Dim;

typedef struct _sie_Output_Struct {
    size_t num_dims;
    size_t num_rows;
    size_t reserved_1;
    size_t reserved_2;
    sie_Output_Dim *dim;
} sie_Output_Struct;

__declspec(dllexport)
sie_Output_Struct* sie_output_get_struct(sie_Output* output) {
    sie_Output_Struct* p = malloc(sizeof(sie_Output_Struct));
    p->num_dims = 2;
    p->num_rows = 2;
    p->dim = malloc(2 * sizeof(sie_Output_Dim));
    p->dim[0].type = SIE_OUTPUT_FLOAT64;
    p->dim[0].float64 = malloc(2 * sizeof(sie_float64));
    p->dim[0].raw = NULL;
    p->dim[0].float64[0] = 1.5;
    p->dim[0].float64[1] = 3.75;
    p->dim[1].type = SIE_OUTPUT_RAW;
    p->dim[1].float64 = NULL;
    p->dim[1].raw = malloc(2 * sizeof(sie_Output_Raw));
    p->dim[1].raw[0].ptr = "hello";
    p->dim[1].raw[0].size = 5;
    p->dim[1].raw[1].ptr = "there";
    p->dim[1].raw[1].size = 5;
    return p;
}

输出:

1.5, b'hello'
3.75, b'there'