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