Я пытаюсь реализовать привязку 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),
...
В какой-то момент во время сеанса отладки это выглядит так:

Проблема здесь в том, что я хочу, чтобы 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),
...
Что мне здесь не хватает?
Кроме того, можете ли вы поместить фактические значения в текст вместо снимка экрана? Было бы полезно иметь возможность, например, скопировать это огромное число, чтобы я мог преобразовать его в шестнадцатеричный и посмотреть, просто ли оно заменено порядковым номером или отключено на один байт или что-то подобное.
Думаю, проблема здесь в упаковке. Когда вы помещаете поле uint8 * 8, оно выравнивается как uint8, так что вы получаете байты 3-11; когда вы помещаете size_t, он выравнивается как size_t, поэтому вы получаете байты 8–16. Попробуйте добавить _pack_=1 в класс Structure и посмотрите, что он делает. (Вероятно, это на самом деле неправильный ответ, если только это не структура C в памяти, а блок сетевого пакета или формата файла, но, по крайней мере, он проверит или исключит проблему.)
@abarnert 1. Спасибо - работает! 2. Я не понимаю, зачем нужен pack: структура определена здесь в строке 185: github.com/nfc-tools/libnfc/blob/master/include/nfc/… Насколько я могу судить, упаковки нет. 3. - работает только при пакет = 1 и не работает, если 2 и более. Почему? В любом случае, не могли бы вы скопировать свои комментарии к ответу, чтобы я мог его принять? Спасибо!
@abarnert Я могу быть слепым. # pragma pack(1) есть.
Похоже, вы довольно четко понимаете проблему, но кто-то другой, ищущий аналогичную проблему, может не понять. Вы хотите написать ответ сами, закрыть вопрос как опечатку или попросить меня написать ответ?






Проблема здесь в том, что структура 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_t (и ctypes).
Примечание:(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)
Можете ли вы скопировать определение структуры C в свой вопрос или хотя бы ссылку на файл где-нибудь?