复杂 Ctypes 结构的问题

Posted

技术标签:

【中文标题】复杂 Ctypes 结构的问题【英文标题】:Issues with complex Ctypes Structure 【发布时间】:2021-08-12 16:45:22 【问题描述】:

我一直在使用 ctypes 将一些 C 代码 [FANUC FOCAS 库] 移植到 Python。

在我必须移植的最复杂的结构之一中,我无法捕获所有变量的值,也无法找出原因。

在 C 中(来自 FANUC 的 fwlib32.h)

typedef struct speedelm 
    long    data;
    short   dec;
    short   unit;
    short   disp;
    char    name;
    char    suff;
 SPEEDELM ;

typedef struct odbspeed 
    SPEEDELM    actf;
    SPEEDELM    acts;
 ODBSPEED ;

FWLIBAPI short WINAPI cnc_rdspeed( unsigned short, short, ODBSPEED * );

然后,对于 Python,我写了:

import ctypes

class SpeedElmT(ctypes.Structure):
    pass

SpeedElmT._fields_ = [
    ("data", ctypes.c_long),
    ("dec", ctypes.c_short),
    ("unit", ctypes.c_short),
    ("disp", ctypes.c_short),
    ("name", ctypes.c_char_p),
    ("suff", ctypes.c_char_p)
]

class ODBSpeed_T(ctypes.Structure):
    _fields_ = [
        ("actf", SpeedElmT),
        ("acts", SpeedElmT),
    ]

# import library
fwl = ctypes.cdll.LoadLibrary("/fwlib_path/")

fwl.cnc_rdspeed.argtypes = ctypes.c_ushort, ctypes.c_short, ctypes.POINTER(ODBSpeed_T)
fwl.cnc_rdspeed.restype = ctypes.c_short

在 C 中运行它的示例(可在 inventcom.net 找到)

#include "fwlib32.h"

void example( void )

    ODBSPEED speed;
    short ret = cnc_rdspeed(h, -1, &speed);
    if(!ret) 
        printf("%c = %d\n", speed.actf.name, speed.actf.data);
        printf("%c = %d\n", speed.acts.name, speed.acts.data);
    

而且,我在 Python 中尝试过

speed = ctypes.pointer(ODBSpeed_T())

r = fwl.cnc_rdspeed(h, ctypes.c_short(-1), speed)

if r == 0:
    print(speed[0].actf.data)    # This one returns the correct value
    print(speed[0].acts.data)    # Zero when not Zero

我真的不明白为什么 acts.data 没有返回预期值。

有人可以帮我解决这个问题吗?非常感谢。

【问题讨论】:

使用speed = ODBSpeed_T(), r = fwl.cnc_rdspeed(h, ctypes.c_short(-1), ctypes.pointer(speed)) 感谢您对@CristiFati 的评论。不幸的是,它并没有解决问题。也许,这是机器配置而不是 python ctypes 的东西。感谢分享 机会渺茫。现在是什么行为?请注意,您现在必须使用:speed.acts.data. 行为保持不变:speed.acts.data = 0,此时实际值不应为零。 【参考方案1】:

错误是 C 代码有 char 而不是 char*。 Python 中的字段必须是c_char 而不是c_char_p,所以结构大小是错误的。

这是一个具有相同接口的测试 DLL:

#include <windows.h>

#ifdef _WIN32
#   define FWLIBAPI __declspec(dllexport)
#else
#   define FWLIBAPI
#endif

typedef struct speedelm 
    long    data;
    short   dec;
    short   unit;
    short   disp;
    char    name;
    char    suff;
 SPEEDELM;

typedef struct odbspeed 
    SPEEDELM    actf;
    SPEEDELM    acts;
 ODBSPEED;

FWLIBAPI short WINAPI cnc_rdspeed(unsigned short a, short b, ODBSPEED* p) 
    p->actf.data = a + b;
    p->actf.dec = 1;
    p->actf.unit = 2;
    p->actf.disp = 3;
    p->actf.name = 'A';
    p->actf.suff = 'B';
    p->acts.data = a - b;
    p->acts.dec = 4;
    p->acts.unit = 5;
    p->acts.disp = 6;
    p->acts.name = 'C';
    p->acts.suff = 'D';
    return 7;

test.py:

import ctypes

class SpeedElmT(ctypes.Structure):
    _fields_ = (('data', ctypes.c_long),
                ('dec', ctypes.c_short),
                ('unit', ctypes.c_short),
                ('disp', ctypes.c_short),
                ('name', ctypes.c_char),  # C code had char not char*
                ('suff', ctypes.c_char))  # ditto
    def __repr__(self):
        return f'SpeedElemT(self.data,self.dec,self.unit,self.disp,self.name,self.suff)'

class ODBSpeed_T(ctypes.Structure):
    _fields_ = (('actf', SpeedElmT),
                ('acts', SpeedElmT))
    def __repr__(self):
        return f'ODBSpeed_T(self.actf!r,self.acts!r)'

fwl = ctypes.WinDLL('./test')  # WinDLL for WINAPI, which is __stdcall (matters on 32-bit Python)
fwl.cnc_rdspeed.argtypes = ctypes.c_ushort, ctypes.c_short, ctypes.POINTER(ODBSpeed_T)
fwl.cnc_rdspeed.restype = ctypes.c_short

speed = ODBSpeed_T()                               # Create an instance
r = fwl.cnc_rdspeed(333, 111, ctypes.byref(speed)) # pass instance by reference
print(r)
print(speed)

输出:

7
ODBSpeed_T(SpeedElemT(444,1,2,3,b'A',b'B'),SpeedElemT(222,4,5,6,b'C',b'D'))

【讨论】:

非常感谢!赞!它在 32 位平台上工作(在 64 位上它仍然无法工作)!我在容器中运行它。 @dacori sizeof(long) == 8 在您的平台上吗?您可能需要为您的结构指定_pack_=1 是的。我不知道在大多数 Linux 64 位长类型中的大小为 8 而不是 4。现在它已解决。非常感谢您的宝贵意见。 @dacori 根据 FANUC 网站,他们确实使用了打包,但他们特别提到了 fwlib32 的 pack(4)。检查 fwlib32/64.h 头文件中的打包命令,以确保使用它们指定的正确打包。如果我的回答有用,请考虑支持/接受答案。 非常感谢。它确实非常有用。它拯救了我的一天。但是,我没有投票,因为我不能(我的声誉得分低于 15),但一旦我的声誉得分更高,我就会投票:P

以上是关于复杂 Ctypes 结构的问题的主要内容,如果未能解决你的问题,请参考以下文章

没有为指向结构的指针调用 ctypes.Structure 子类的 ctypes __del__

ctypes 将另一个结构中的结构保存到文件中

使用 ctypes 传递结构指针

有没有办法使用循环分配 ctypes 结构的字段?

在 Python/ctypes 中的结构内取消引用 C 函数指针

如何使用 ctypes 打包和解包(结构 <-> str)