Swift конвертирует uint64_t в C, отличный от своего собственного типа UInt64
Я нахожусь в процессе переноса приложения из (Objective-) C в Swift, но должен использовать стороннюю платформу, написанную на C. Есть несколько несовместимостей, таких как typedef, которые интерпретируются как Int, но должны быть переданы в функции фреймворка как UInts или тому подобное. Поэтому, чтобы избежать постоянных операций приведения во всем приложении Swift, я решил перенести заголовочные файлы C в Swift, имея все типы, так как мне нужно, чтобы они были в одном месте.
Я смог перенести почти все и преодолел множество препятствий, но вот этот:
Заголовок C определяет структуру, которая среди прочего содержит переменную uint64_t. Эта структура используется для передачи данных в функцию обратного вызова в качестве указателя. Функция обратного вызова принимает указатель void в качестве аргумента, и я должен привести его с помощью операции UnsafeMutablePointer к типу структуры (или другой структуре заголовка, если необходимо). Все приведение и доступ к памяти работают нормально, если я использую исходную структуру из заголовка C, которая автоматически преобразуется Swift при импорте.
Однако репликация структуры вручную в Swift не "вписывается в байты".
Позвольте мне показать вам уменьшенный пример этой ситуации:
Внутри файла CApiHeader.h есть что-то вроде
typedef struct{
uint32_t var01;
uint64_t var02;
uint8_t arr[2];
}MyStruct, *MyStructPtr;
Насколько я понимаю, здесь должен быть эквивалент Swift
struct MyStruct{
var01: UInt32
var02: UInt64
arr: (UInt8, UInt8)
}
Или что также должно работать, это запись кортежа
typealias MyStruct = (
var01: UInt32,
var02: UInt64,
arr: (UInt8, UInt8)
)
Это работает нормально, но не тогда, когда есть тип UInt64.
Хорошо, что происходит?Приведя указатель на одну из моих собственных реализаций Swift MyStruct, данные дырок сдвигаются на 2 байта, начиная с поля UInt64. Так что в этом примере обаприбытие поля находятся не в правильной позиции, а внутри битов UInt64, число которых должно быть 64. Таким образом, кажется, что поле UInt64 имеет только 48 бит.
Это соответствует моему наблюдению, что если я заменю переменную UIn64 этой альтернативой
struct MyStruct{
var01: UInt32
reserved: UInt16
var02: UInt32
arr: (UInt8, UInt8)
}
или этот
struct MyStruct{
var01: UInt32
var02: (UInt32, UInt32)
arr: (UInt8, UInt8)
}
(или эквивалентное обозначение кортежа) это выравниваетприбытие поля правильно. Но, как вы можете легко догадаться,var02 содержит данные, которые нельзя использовать напрямую, поскольку они разбиты на несколько диапазонов адресов. Еще хуже с первой альтернативой, потому что это швы, что Swift заполняет промежуток междузарезервированный поле иvar02 поле с 16 битами - отсутствующие / сдвинутые 2 байта, о которых я упоминал выше - но они не легко доступны.
Так что я не нашел никакого эквивалентного преобразования структуры C в Swift.
Что именно здесь происходит и как Swift на самом деле преобразует структуру из заголовка C?
Ребята, у вас есть подсказка, объяснение или решение для меня, пожалуйста?
ОбновитьФреймворк C имеет функцию API с этой сигнатурой:
int16_t setHan,dlers(MessageHandlerProc messageHandler);
MessageHandlerProc - это тип процедуры:
typedef void (*messageHandlerProc)(unsigned int id, unsigned int messageType, void *messageArgument);
Так что setHandlers - это процедура на C внутри фреймворка, которая получает указатель на функцию обратного вызова. Эта функция обратного вызова должна предоставлять аргумент пустого указателя, который приводится, например, к.
typedef struct {
uint16_t revision;
uint16_t client;
uint16_t cmd;
int16_t parameter;
int32_t value;
uint64_t time;
uint8_t stats[8];
uint16_t compoundValueOld;
int16_t axis[6];
uint16_t address;
uint32_t compoundValueNew;
} DeviceState, *DeviceStatePtr;
Swift достаточно умен, чтобы импортировать messageHandlerProc с синтаксисом соглашения (c), поэтому тип процедуры доступен напрямую. С другой стороны, невозможно использовать стандартный синтаксис func и передать функцию обратного вызова my messageHandler для этого типа. Поэтому я использовал синтаксис замыкания для определения функции обратного вызова:
let myMessageHandler : MessageHandlerProc = { (deviceID : UInt32, msgType : UInt32, var msgArgPtr : UnsafeMutablePointer<Void>) -> Void in
...
}
Я преобразовал вышеупомянутую структуру в различные структуры моего оригинального сообщения.
И нет! определяющийстатистика как Swift Array не работает. Массив в Swift не эквивалентен массиву в C, поскольку массив Swift является расширенным типом. Запись и чтение из него с указателем вызывает исключение
Только кортежи изначально реализованы в Swift, и вы можете бегать туда-сюда с указателями над ним.
Хорошо ... все работает отлично, и моя функция обратного вызова вызывается всякий раз, когда доступны данные.
Так внутриmyMessageHandler Я хочу использовать хранящиеся данные внутриmsgArgPtr который является пустым указателем и поэтому должен быть приведен вDeviceState.
let state = (UnsafeMutablePointer<MyDeviceState>(msgArgPtr)).memory
Доступ кгосударство это как:
...
print(state.time)
print(state.stats.0)
...
Всякий раз, когда я использую автоматически сгенерированный кулон SwiftDeviceState все работает хорошо. Переменная времени имеет метку времени Unix, и следующие статистические данные (доступные с синтаксисом кортежа !!!) - все, где они принадлежат.
Однако использование моей структуры, реализованной вручную, приводит к совершенно бессмысленному значению метки времени, а поля статистики сдвигаются влево (в направлениивремя поле - возможно, поэтому значение метки времени бесполезно, потому что оно содержит биты из статистического «массива»). Таким образом, в последних двух полях статистики я получаю значения отcompoundValueOld и первыйось поле - со всем переполнением конечно.
Пока я готов пожертвоватьвремя установите значение переменной UInt64 и измените ее, используя кортеж двух типов UInt32 или изменив его на тип UInt32 и добавив вспомогательную переменную типа UInt16 непосредственно передвремяЯ получаюстатистика «массив» с правильным выравниванием.
Хорошего дня! :-)
Мартин