ctypes python/C 中的结构数组

问题描述 投票:0回答:1

运行代码后,我只能访问数组的第一个结构,该结构在 c 中定义 外部文件中定义的函数。 我认为错误在于这一行:

engineLIB.get_files.restype = ctypes.POINTER(file)
其中仅将 restype 定义为“文件”而不是文件数组。 我无法找到此语法,所以我向你们寻求帮助。 提前谢谢了。 这是我的Python源代码:

import ctypes

engineLIB = ctypes.CDLL('./engine.so')

class file(ctypes.Structure):
    _fields_ = [
        ("name", ctypes.c_char * 250),
        ("path", ctypes.c_char * 250),
        ("fileCnt", ctypes.c_int),
    ]

engineLIB.get_files.restype = ctypes.POINTER(file)
engineLIB.get_files.argtype = ctypes.c_char_p

path = input("Input path: ")
files = engineLIB.get_files(path.encode("utf-8"))

for i in range(2):
    print(files[i].name)
    print(files[i].path)


我尝试将 restype 定义为顶部定义的文件类的结构数组

python arrays c struct ctypes
1个回答
0
投票

正如我的评论中所述,看起来像是[SO]的重复:使用ctypes传递和获取C函数数组的问题(@CristiFati的答案)(尽管它是针对double而不是结构,并且还包含(额外的)双指针**)内容),所以请务必阅读第 1st

我还想强调[SO]:通过ctypes从Python调用的C函数返回不正确的值(@CristiFati的答案)(你有一个打字错误argtypes)。

我准备了一个我猜你正在尝试的例子。

dll00.c:

#include <stdlib.h>
#include <string.h>

#if defined(_WIN32)
#  define DLL00_EXPORT_API __declspec(dllexport)
#else
#  include <dirent.h>
#  define DLL00_EXPORT_API
#endif

#define MAXPATH 250


typedef struct {
    char name[MAXPATH];
    char path[MAXPATH];
    int cnt;
} File, *PFile;


#if defined(__cplusplus)
extern "C" {
#endif

DLL00_EXPORT_API PFile getFiles(const char *path, int *pCount);
DLL00_EXPORT_API void dealloc(File *ptr);

#if defined(__cplusplus)
}
#endif


#if !defined(_WIN32)
PFile getFiles(const char *path, int *pCount)
{
    DIR *pDir = NULL;
    struct dirent *pEntry = NULL;
    PFile ret = NULL;
    int idx = 0;
    if ((!pCount) || (!path)) {
        return ret;
    }
    if (!(pDir = opendir(path))) {
        *pCount = -1;
        return ret;
    }
    *pCount = 0;
    while ((pEntry = readdir(pDir))) {
        ++(*pCount);
    }
    closedir(pDir);

    if (!(pDir = opendir(path))) {
        *pCount = -1;
        return ret;
    }
    *pCount += idx;
    ret = calloc(*pCount, sizeof(File));
    while ((pEntry = readdir(pDir))) {
        strcpy(ret[idx].name, pEntry->d_name);
        strcpy(ret[idx].path, path);
        ret[idx].cnt = idx;
        ++idx;
    }
    closedir(pDir);
    return ret;
}
#endif


void dealloc(File *ptr)
{
    free(ptr);
}

code00.py

#!/usr/bin/env python

import ctypes as cts
import sys


DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")

MAXPATH = 250  # @TODO - cfati: definitions must match the C ones

class File(cts.Structure):
    _fields_ = (
        ("name", cts.c_char * MAXPATH),
        ("path", cts.c_char * MAXPATH),
        ("cnt", cts.c_int),
    )

FilePtr = cts.POINTER(File)


def main(*argv):
    dll = cts.CDLL(DLL_NAME)
    getFiles = dll.getFiles
    getFiles.argtypes = (cts.c_char_p, cts.POINTER(cts.c_int))
    getFiles.restype = FilePtr
    dealloc = dll.dealloc
    dealloc.argtypes = (FilePtr,)
    dealloc.restype = None


    #path = input("Input path: ")
    path = "."
    count = cts.c_int(0)
    files = getFiles(path.encode(), cts.byref(count))
    print("Path [{:s}] has {:d} entries:".format(path, count.value))
    for idx in range(count.value):
        file = files[idx]
        print("\nName: [{:s}]\nPath: [{:s}]\nCnt: {:d}\n".format(file.name.decode(), file.path.decode(), file.cnt))
    dealloc(files)


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)

注释

  • 当以指针形式返回数组(因为这就是函数返回的内容:结构数组)(缺少大小信息)时,有多种方法向调用者发出大小信号。我选择了最简单的一个:函数的附加(输出)参数。

    • 另一种方法是在数组末尾有一个额外的条目,其中包含一些字段的哨兵值(有点像 NUL 用于标记 char* 的结尾),但我不太喜欢其中
  • C代码效率低下,它遍历目录两次(一次获取文件计数,第二次nd实际获取数据)。还有其他选择(在需要时增加数组大小),但代码会变得非常复杂,并且超出了问题范围

  • 更多的错误处理不会有什么坏处

输出

[cfati@cfati-5510-0:/mnt/e/Work/Dev/StackExchange/StackOverflow/q077368808]> . ~/sopr.sh
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[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]> python ./code00.py 
Python 3.10.12 (main, Jun 11 2023, 05:26:28) [GCC 11.4.0] 064bit on linux

Path [.] has 5 entries:

Name: [.]
Path: [.]
Cnt: 0

Name: [..]
Path: [.]
Cnt: 1

Name: [code00.py]
Path: [.]
Cnt: 2

Name: [dll00.c]
Path: [.]
Cnt: 3

Name: [dll00.so]
Path: [.]
Cnt: 4

Done.
© www.soinside.com 2019 - 2024. All rights reserved.