【问题标题】:Passing nested ctypes structure with array to C library function not as expected未按预期将带有数组的嵌套 ctypes 结构传递给 C 库函数
【发布时间】:2019-07-20 03:47:22
【问题描述】:

我有以下 C 代码,我将从 python 脚本中使用。

这只是一个自动生成的大型库的摘录,很遗憾,我无法更改。在这里,我只是想将结构元素打印到控制台以演示出了什么问题。


// CFunc.h
#include <stdio.h>

typedef struct
{
    int npar;
    struct
    {
        int id;
        int value;
    } params[10];
} Data_t;

void Cfunc( const Data_t * d);


// CFunc.c

#include "CFunc.h"

void Cfunc( const Data_t * d)
{
    int inpar = 0;
    int maxnpar = 0;

    printf("%d:\n", d->npar);
    maxnpar = d->npar;

    inpar=0;
    while (maxnpar > inpar)
    {
        printf("  %d: %08x %08x\n", inpar, d->params[inpar].id, *(int*)&d->params[inpar].value);
        inpar++;
    }
}

它被编译并链接到一个共享库:

gcc -fPIC -c CFunc.c -o CFunc.o
gcc -shared -lrt -Wl,-soname,libCFunc.so.1 -o libCFunc.so CFunc.o

所以我使用 ctypes 做了以下实现:

from ctypes import *

lib = CDLL('./libCFunc.so')


class Data_2(Structure):
    pass
class Data_t(Structure):
    def __init__(self, list):
        self.npar = len(list)
        self.params = (Data_2 * self.npar)(*list)

Data_2._fields_ = [
    ('id', c_int),
    ('value', c_int),
]
Data_t._fields_ = [
    ('npar', c_int),
    ('params', POINTER(Data_2)),
]

def pyFunc(d):
    lib.Cfunc.argtypes = (POINTER(Data_t),)

    lib.Cfunc(byref(d))

    return

所以我从给定元组列表中初始化结构,在本例中只有 2 个并调用 C 函数来查看其输出。

paramlist = ( 
    ( 0x050000000, 0x00000000 ),  
    ( 0x050000001, 0x447a0000 ) )
temp = Data_t(paramlist)

pyFunc(temp)

很遗憾输出不符合预期:

2:
0: 00000000 79948ef0
1: 00007fe5 00000000

任何想法我错过了什么?

【问题讨论】:

  • 关于:typedef struct { int npar; struct { int id; int value; } params[10]; } Data_t; 1) 始终在每个结构上使用“标签”名称,因为这是大多数调试器用来访问结构中各个字段的方法。 2)为了灵活性,将结构的定义与结构的typedef分开
  • 我发布的代码只是一个更复杂库的示例,您的顾虑已涵盖...谢谢您的评论。

标签: python c shared-libraries ctypes


【解决方案1】:

[Python 3]: ctypes - A foreign function library for Python.

  • CPython 的结构不匹配
    • params一个 数组 而不是 指针
  • 由于上述不一致,您在 Python 中使事情过于复杂:
    • 您没有不完整的类型,因此您的结构可以静态定义

我对您的代码进行了一些重组。

dll.h

#pragma once


typedef struct Data_ {
    int npar;
    struct
    {
        int id;
        int value;
    } params[10];
} Data;


void test(const Data *d);

dll.c

#include "dll.h"
#include <stdio.h>


void test(const Data *d) {
    int inpar = 0;
    int maxnpar = 0;

    printf("%d:\n", d->npar);
    maxnpar = d->npar;

    inpar = 0;
    while (inpar < maxnpar)
    {
        printf("  %d: %08x %08x\n", inpar, d->params[inpar].id, *(int*)&d->params[inpar].value);
        inpar++;
    }
}

code.py

#!/usr/bin/env python3

import sys
import ctypes


DLL = "./libdll.so"


class DataInner(ctypes.Structure):
    _fields_ = [
        ("id", ctypes.c_int),
        ("value", ctypes.c_int),
    ]


DataInnerArr10 = DataInner * 10


class Data(ctypes.Structure):
    _fields_ = [
        ("npar", ctypes.c_int),
        ("params", DataInnerArr10),
    ]

    def __init__(self, data):
        self.npar = len(data)
        self.params = DataInnerArr10(*data)


def main():
    dll_dll = ctypes.CDLL(DLL)
    test_func = dll_dll.test
    test_func.argtypes = [ctypes.POINTER(Data)]

    param_list = (
        (0x050000000, 0x00000000),
        (0x050000001, 0x447a0000),
    )

    d = Data(param_list)

    test_func(ctypes.byref(d))
    print("Done.")


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

输出

[cfati@cfati-5510-0:/cygdrive/e/Work/Dev/StackOverflow/q054888242]> ls
code.py  dll.c  dll.h
[cfati@cfati-5510-0:/cygdrive/e/Work/Dev/StackOverflow/q054888242]> gcc -shared -fPIC -o libdll.so dll.c
[cfati@cfati-5510-0:/cygdrive/e/Work/Dev/StackOverflow/q054888242]> ls
code.py  dll.c  dll.h  libdll.so
[cfati@cfati-5510-0:/cygdrive/e/Work/Dev/StackOverflow/q054888242]> python3 code.py
Python 3.6.4 (default, Jan  7 2018, 15:53:53)
[GCC 6.4.0] on cygwin

2:
  0: 50000000 00000000
  1: 50000001 447a0000
Done.

【讨论】:

  • 非常感谢,这行得通!我被说服了,数组初始化的完成类似于self.params = (Data_2 * self.npar)(*list) ...至少这是我对 SE 的交叉阅读的理解。我也完全错过了self.params 需要具有固定元素大小并且不能使用...* self.npar 动态初始化。当然,我也想到了self.params = Data_2 * self.npar(*list),但这不是合法的语法。您介意详细说明与 DataInnerArr10 = DataInner * 10 + self.params = DataInnerArr10(*data) 的区别吗?我还不清楚:-/
  • 不客气! DataInnerArr10(顾名思义)是一个 type,一个由 10 个 DataInne 结构组成的数组。如有疑问,请始终使用 print (print(type(DataInnerArr10), dir(DataInnerArr10)))。第二个是初始化器,它是一个 tuple unpacking:基本上它将 2 个值元组从主矩阵 (2D) 传递给 DataInner 构造函数,以初始化它的 2 个成员。
猜你喜欢
  • 1970-01-01
  • 2015-05-27
  • 2015-09-24
  • 2011-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-05
  • 1970-01-01
相关资源
最近更新 更多