пятница, 16 мая 2014 г.

Qt под Mac OS X: ARC и Valgrind

Для того, чтобы включить Automatic Reference Counting (ARC) для исходников на Objective C / Objective C++, входящих в состав проекта на Qt, достаточно добавить в проекте (файл .pro) следующую строку:

QMAKE_OBJECTIVE_CFLAGS += -fobjc-arc

Встречал упоминание и

QMAKE_OBJECTIVE_CXXFLAGS += -fobjc-arc

(для исходников Objective C++), но в моем случае эта строчка ничего не дает, хотя файл и имеет расширение .mm.

Самый простой способ поставить Valgrind под Mac OS X, похоже, следующий:

  • Ставим Homebrew по инструкции с заглавной страницы сайта (одна команда в терминале)
  • В терминале набираем команду brew install valgrind
Этого оказалось достаточно, чтобы заполучить Valgrind в Mac OS X 10.9, но, как выяснилось, с моей программой он не работает - valgrind: Unrecognised instruction at address... Впрочем, официальной поддержки Mac OS X 10.9 в последнем Valgrind 3.9 нет.

среда, 14 мая 2014 г.

Bluetooth, Mac OS X и ошибки в примере

Потребовалось мне написать модуль для Mac OS X, который общается с одним устройством по Bluetooth, по протоколу RFCOMM. Задача не слишком сложная - устройству посылается команда и принимается ответ. И то, и другое - строки в UTF8. Основная программа кросс-платформенная и написана на С++ (используется Qt 5.2), но модуль для работы по Bluetooth, разумеется, пришлось писать на Objective C.

Документация есть (Bluetooth Device Access Guide), примеров кода - не отдельных строчек, а полноценных примеров, пусть и простейших - нет. После некоторых поисков выяснилось, что когда-то давно они поставлялись с XCode, но потом их почему-то исключили. К счастью, в ответах на вопрос дали ссылку на сохраненный архив примеров.

Наличие примеров - хорошее подспорье, и вскоре у меня уже был работающий код. Точнее, почти работающий - при наличии в строке команды русских букв она до устройства не доходила. Расследование показало, что в исходниках Apple имеется явный баг.

В примере RFCOMMClientSample, на который я ориентировался, есть метод sendData:

(BOOL)sendData:(void*)buffer length:(UInt32)length

Метод этот передает данные из буфера в устройство, подключенное по протоколу RFCOMM, при необходимости разбивая их на порции меньшего размера, если длина буфера больше MTU. Вызывается он в примере следующим образом:

[myBluetoothInterface sendData:(void*)[theString UTF8String] length:[theString length]];

Но метод length класса NSString (theString может быть объектом класса NSString или NSMutableString) возвращает число Юникод-символов в строке, а не число символов типа char, массив которых возвращает UTF8String! В результате при наличии в строке символов, представляемых в UTF8 более чем одним байтом, длина буфера, передаваемая в sendData, оказывается меньше реальной. Похоже, что пример этот со строками, содержащими что-то, отличное от латиницы, в Apple не проверяли... Вместо length нужно использовать lengthOfBytesUsingEncoding:NSUTF8StringEncoding:

[myBluetoothInterface sendData:(void*)[theString UTF8String] length:[theString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];

Вторая проблема - скорее не баг, а наследие чистого C. В методе sendData есть такая строчка:

buffer += numBytesToSend;

Арифметика указателей, давно я ее не встречал. Вот только современные компиляторы C++ применение такой операции к переменной типа void* считают ошибкой. Указателя на void требует метод writeSync класса IOBluetoothRFCOMMChannel, но преобразование типов лучше делать непосредственно при его вызове. Тогда sendData будет выглядеть так:

(BOOL)sendData:(const char*)buffer length:(UInt32)length

Его вызов больше не требует преобразования типа (UTF8String возвращает const char*):

[myBluetoothInterface sendData:[theString UTF8String] length:[theString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];

А вот вызов writeSync требует:

result = [mRFCOMMChannel writeSync:(void*)buffer length:numBytesToSend];

С такими исправлениями код, написанный на основе примера, можно спокойно использовать в качестве модуля в приложении, написанном на C++/Qt.