Ctypes: структура, поле size_t

Я пытаюсь реализовать привязку python-to-c через ctypes для libnfc. У меня есть структура, вот неправильный вариант:

class nfc_iso14443a_info(Structure):
    _fields_ = [
        ('abtAtqa',  c_uint8 * 2),
        ('btSak',    c_uint8),
        ('szUidLen', c_uint8 * 8),
        ('abtUid',   c_uint8 * 10),
        ...

В какой-то момент во время сеанса отладки это выглядит так: Ctypes: структура, поле size_t

Проблема здесь в том, что я хочу, чтобы szUidLen был 64-битным целым числом без знака, равным 7. Точнее, он должен соответствовать size_t szUidLen; из nfc-types.h. Итак, я попробовал очевидный вариант и заменил c_uint8 * 8 на c_size_t, но он не работает:

class nfc_iso14443a_info(Structure):
    _fields_ = [
        ('abtAtqa',  c_uint8 * 2),
        ('btSak',    c_uint8),
        ('szUidLen', c_size_t),
        ('abtUid',   c_uint8 * 10),
        ...

Ctypes: структура, поле size_t

Что мне здесь не хватает?

Можете ли вы скопировать определение структуры C в свой вопрос или хотя бы ссылку на файл где-нибудь?

abarnert 27.03.2018 04:45

Кроме того, можете ли вы поместить фактические значения в текст вместо снимка экрана? Было бы полезно иметь возможность, например, скопировать это огромное число, чтобы я мог преобразовать его в шестнадцатеричный и посмотреть, просто ли оно заменено порядковым номером или отключено на один байт или что-то подобное.

abarnert 27.03.2018 04:47

Думаю, проблема здесь в упаковке. Когда вы помещаете поле uint8 * 8, оно выравнивается как uint8, так что вы получаете байты 3-11; когда вы помещаете size_t, он выравнивается как size_t, поэтому вы получаете байты 8–16. Попробуйте добавить _pack_=1 в класс Structure и посмотрите, что он делает. (Вероятно, это на самом деле неправильный ответ, если только это не структура C в памяти, а блок сетевого пакета или формата файла, но, по крайней мере, он проверит или исключит проблему.)

abarnert 27.03.2018 04:54

@abarnert 1. Спасибо - работает! 2. Я не понимаю, зачем нужен pack: структура определена здесь в строке 185: github.com/nfc-tools/libnfc/blob/master/include/nfc/… Насколько я могу судить, упаковки нет. 3. - работает только при пакет = 1 и не работает, если 2 и более. Почему? В любом случае, не могли бы вы скопировать свои комментарии к ответу, чтобы я мог его принять? Спасибо!

Grief 27.03.2018 12:02

@abarnert Я могу быть слепым. # pragma pack(1) есть.

Grief 27.03.2018 12:36

Похоже, вы довольно четко понимаете проблему, но кто-то другой, ищущий аналогичную проблему, может не понять. Вы хотите написать ответ сами, закрыть вопрос как опечатку или попросить меня написать ответ?

abarnert 27.03.2018 19:26
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
6
1 386
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Проблема здесь в том, что структура C, которую вы пытаетесь сопоставить, упакована, как (кратко) объяснено в разделе Выравнивание структуры / объединения и порядок байтов документации:

By default, Structure and Union fields are aligned in the same way the C compiler does it. It is possible to override this behavior be specifying a _pack_ class attribute in the subclass definition. This must be set to a positive integer and specifies the maximum alignment for the fields. This is what #pragma pack(n) also does in MSVC.

Это имеет смысл только в том случае, если вы уже знаете об упаковке и выравнивании в C, но это не так уж сложно.

По умолчанию элементы структуры C выровнены так, чтобы начинаться с хороших границ. Например, 32-битное int, следующее за 8-битным int, запускается не из байтов 1-4, а из байтов 4-7 (а байты 1-3 не используются для заполнения). Итак, ctypes следует тем же правилам.

Это означает, что, хотя szUidLen работает с байтами 3-10, когда он определен как массив 8-битных целых чисел, он выравнивается по байтам 8-15 (или 4-11, в зависимости от вашего компилятора), когда он определен как 64- бит int. Вы можете убедиться в этом, распечатав nfc_iso14443a_info.szUidLen.offset.

Итак, первый получает байты 7, 0, 0, 0, 0, 0, 0, 0, что является прямым порядком байтов int64 для 7, а второй получает байты 0, 0, 0, a, b, c, d, e, где abcde - это первые 5 байтов следующего поля, которое является прямым порядком байтов int64 для некоторого огромного числа. (если в следующем поле не окажется 0).

Конечно, вы не хотите просто догадываться, что в этом проблема. Если вы основали свой Structure на struct из заголовка C, это может быть правдой только в том случае, если заголовок или флаги компиляции указывают некоторую нестандартную упаковку, например #pragma pack(1), используемый MSVC. Если вы основали свой Structure на чем-то вроде описания пакета RFC, выравнивание даже не соответствует правилам C, а определено где-то в документации, которую вы читаете (хотя протоколы RFC почти всегда используют 1-байтовое выравнивание).

В любом случае, документы не очень хорошо объясняют проблему, но они объясняют решение:

class nfc_iso14443a_info(Structure):
    _pack_ = 1
    _fields_ = [
        ('abtAtqa',  c_uint8 * 2),
        ('btSak',    c_uint8),
        ('szUidLen', c_size_t),
        ('abtUid',   c_uint8 * 10),
        ...

Теперь szUidLen работает с байтами 3-10, но интерпретируется как 64-битное int вместо массива 8-битных int.

from ctypes import *
c_size_t = c_unit64

и вперед Вам май также необходимо указать ._pack_=1 (если ваш компилятор генерирует код таким образом) перед, определяющий _fields_.

Обновлять: В c_size_t есть готовый тип c_ssize_tctypes).

Примечание:(c_char * 8) является нет равным c_int64 или c_long из-за возможных проблем с выравниванием (поля c_char не выровнены). ctypes.alignment(c_type) может дать вам подсказку о том, как выравнивается c_type:

In [7]: c.alignment(c.c_char * 8), c.alignment(c.c_size_t)
Out[7]: (1, 8)

Другие вопросы по теме