Форум   Статьи   Новости   Файлы   Bugtraq   Сниффер   Друзья   О Клубе
  , 12:59   #1
Местный
 
Аватар для _PixeL_
 
Регистрация: 16.03.2012
Сообщений: 264

Репутация: 0 / 0
По умолчанию Работа с Bluetooth в Delphi

Введение

Наконец, после долгого перерыва я добрался и до заключительной, как я надеюсь, части статьи про Bluetooth.

Здесь я постараюсь изложить в доступной форме, как же все-таки передавать данные через Bluetooth. Я не буду приводить здесь каких-либо готовых примеров приложений. Дам только теорию. К практике, я думаю, вы перейдете сами.

Как вы помните из предыдущих моих статей, мы используем исключительно Windows API для работы с Bluetooth. Сразу хочу оговориться, что описанные здесь способы не будут работать с драйверами BlueSoliel и VIDCOMM. В конце статьи я расскажу, как установить драйвера от Microsoft, если вы это еще не сделали.

И так, приступаем.

Что вы должны знать

Прежде чем начать излагать основной материал, я хочу сформулировать требования к вашим знаниям.

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

Вы также должны более или менее разбираться в технологии Winsock.

Bluetooth и Winsock

Как не странно это звучит, но Microsoft решила реализовать всю функциональность по передаче данных посредством Windows Socket Model. Тем, кто писал что-либо для IrDA это должно показаться знакомым.

На мой взгляд – правильное решение. Зачем огород городить, когда уже есть проверенные средства.

Я не буду описывать здесь все правила применения функций WinSock к работе с Bluetooth. Остановлюсь лишь на практической стороне вопроса. А именно - передача данных.

В статье мы сделаем простенький Bluetooth-клиент, который будет подсоединяться к удаленному устройству как к модему и позволит вам выполнять AT-команды. Весьма полезная вещь. Учтите, что данный клиент будет требовать авторизации устройств и не будет требовать наличия в системе каких-либо виртуальных COM-портов.

Сервисы и профили

Сервисы и профили... Это два краеугольных понятия Bluetooth. В некотором смысле – они идентичны.

Сервис – приложение-сервер, которое регистрирует определенным образом параметры в стеке протоколов Bluetooth. Наименование (GUID) всех сервисов строго определены Bluetooth.org.

Профиль – соглашения и стандарты работы сервиса. Понятнее объяснить не смогу.

Начало

И так, прежде чем можно будет использовать библиотеку WinSock, ее необходимо инициализировать. Делается это вызовом функции WSAStartup. Вот как она выглядит:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.




Не буду описывать все параметры, так как они есть в любой справочной системе (MSDN, Delphi). Скажу только, что для использования WinSock с Bluetooth необходимо указаь в качестве параметра wVersionRequired номер версии $0202.

Вот как выглядит вызов этой функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.

По окончанию работы с WinSock библиотеку необходимо освободить. Для этого существует функция WSACleanup.
function WSACleanup: Integer; stdcall;
Вызывается она просто, без всяких параметров. Возвращаемое значение, в принципе, можно не проверять:
WSACleanup;



Создание клиента

После того, как библиотека инициализирована, мы можем вызывать функции WinSock. Давайте создадим простой сокет, для работы с Bluetooth устройствами. Для этого необходимо вызвать функцию socket.

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Вот как это делается:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.

Функция вернет корректный описатель сокета, либо INVALID_SOCKET в случае ошибки. Запомните, что Bluetooth поддерживает только потоковые сокеты (SOCK_STREAM).


Далее нам необходимо заполнить структуру SOCKADDR_BTH. В эту структуру записывается информация о сервере, к которому нам нужно подключиться (адрес, сервис и т.п.). Делается это следующим образом:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Здесь в переменной ADeviceAddress должен быть адрес устройства (Int64), присоединяемся к любому порту (BT_PORT_ANY) сервиса SerialPortServiceClass.


Далее вызываем функцию connect, которая имеет вид:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Делается это вот так:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Если функция выполнится успешно, вернет 0, в противном случае отличное от нуля значение.


После того, как соединение установлено, можно передавать и принимать данные через сокет функциями send и recv.

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Функции возвращают количество переданных или принятых байт в случае успеха и отрицательное число в случае ошибки. Количество переданных или принятых байт может быть меньше, чем указанная в параметре len длина буфера. Тогда вам нужно повторить передачу/прием оставшихся байт.


Ну и закрытие сокета осуществляется вызовом функции closesocket:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Опять же, возвращаемое значение можно проигнорировать (если вы знаете, что делаете).


В общем то, вышеуказанный материал не представляет ничего нового для тех, кто хоть раз программировал под WinSock. Единственное, на что следует обратить внимание, это новые константы AF_BTH и BTHPROTO_RFCOMM.

Создание сервера

Как и создание клиента, создание сервера ничем не отличается от создания сервера для любой службы WinSock.

И так, начнем. Сокет создается также как и в приведенном выше примере для клиента. Точно также заполняем структуру Addt: SOCKADDR_BTH. Только в качестве адреса устройства указываем 0. Далее, необходимо привязать сокет к адресу. Делается это функцией bind:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Далее вызываем функцию listen, для того чтобы сервер начал прослушивать сокет на предмет подключения клиентов и функцию accept для приема входящего подключения:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Делается это вот так:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


После подключения клиента можно работать с AClientSocket – передавать и принимать данные.


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

Что осталось за кадром

Как и обещал, я коротко описал процедуры, необходимые для построения простого клиента и сервера, которые будут работать с Bluetooth через WinSock. Однако, здесь я не рассматривал вопросы регистрации сервисов и протоколы верхнего уровня.

Приведенной здесь информации достаточно для того, что бы вы могли создать приложение "клиент", которое соединится с ваши телефоном по Bluetooth и сможет выполнять AT-команды.

Более полную информацию и рабочие примеры можно найти здесь:
Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.
. Там же приведено решение по установке драйверов от Microsoft.


Работа с Bluetooth в Delphi. Часть 1

Вступление

Этой статьей хочу начать серию по работе с Bluetooth в Delphi под Microsoft Windows XP. Так как тема весьма сложная, прошу внимательно читать. Повторяться не буду.

Все программы написаны на Delphi 6 и тестировались со стандартным стеком Bluetooth от Microsoft под Windows XP + SP2.

Все необходимые библиотеки прилагаются. Так что дополнительно ничего качать не нужно. При разработке использовал только API функции с JEDI.

Описание функций будут даны в стиле Object Pascal. Сионистов просьба обращаться к MSDN и Microsoft Platform SDK.

Получение списка установленных радиомодулей Bluetooth

Итак, для начала попробуем получить список установленных на компьютере радиомодулей Bluetooth.

BluetoothFindFirstRadio - начинает перечисление локальных радиомодулей Bluetooth.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



pbtfrp

указатель на структуру BLUETOOTH_FIND_RADIO_PARAMS. Член dwSize этой структуры должен содержать размер структуры (устанавливается посредством SizeOf(BLUETOOTH_FIND_RADIO_PARAMS)).



phRadio

описатель (Handle) найденного устройства.

Возвращаемые значения: •В случае успешного выполнения функция вернет корректный описатель в phRadio и корректный описатель в качестве результата
•В случае ошибки будет возвращен 0. Для получения кода ошибки используйте функцию GetLastError



BluetoothFindNextRadio - находит следующий установленный радиомодуль Bluetooth.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hFind

Описатель, который вернула функция BluetoothFindFirstRadio



phRadio

Сюда будет помещен описатель следующего найденного радиомодуля

Возвращаемые значения: •Вернет TRUE, если устройство найдено. В phRadio корректный описатель на найденный радиомодуль.
•Вернет FALSE в случае отсутствия устройства. phRadioсодержит некорректный описатель. Используйте GetLastError для получения кода ошибки.



BluetoothFindRadioClose - закрывает описатель перечисления радиомодулей Bluetooth.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hFind

Описатель, который вернула функция BluetoothFindFirstRadio

Возвращаемые значения: •Вернет TRUE если описатель успешно закрыт.
•Вернет FALSE в случае ошибки. Для получения кода ошибки используйте GetLastError.



Теперь у нас достаточно знаний, чтобы получить список установленных радиомодулей Bluetooth.

Напишем вот такую процедуру:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Это, конечно, все здорово, но в принципе бесполезно. Давайте что-нибудь сделаем еще. Например, получим информацию о радиомодуле Bluetooth.


Получение информации о радиомодуле Bluetooth

Для получения информации о радиомодуле Bluetooth используется функция

BluetoothGetRadioInfo - возвращает информацию о радиомодуле, который представлен описателем.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hRadio

Описатель локального радиомодуля, который получен функцией BluetoothFindRadioFirst или BluetoothFindRadioNext



pRadioInfo

Структура, в которую записывается информация об указанном радиомодуле. Член dwSize должен быть равен размеру структуры

Возвращаемые значения: •Вернет ERROR_SUCCESS если информация получена, в противном случае код ошибки.
Структура BLUETOOTH_RADIO_INFO выгляди вот так:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.





dwSize

Размер структуры в байтах



address

Адрес локального радиомодуля



szName

Имя радиомодуля



ulClassofDevice

Класс устройства



lmpSubversion

Устанавливается производителем



manufacturer

Код производителя (константы BTH_MFG_Xxx). Для получения новых кодов обратитесь к сайту спецификаций Bluetooth

Это уже что-то. Воспользуемся этой информацией и напишем вот такую процедуру.

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.



Работа с Bluetooth в Delphi. Часть 2

Вступление

В первой части статьи мы научились получать список локальных радиомодулей Bluetooth и узнавать их свойства.

Теперь пришло время получить список устройств Bluetooth, которые подключены к нашим локальным радиомодулям.

Получение списка устройств Bluetooth

Для получения списка устройств Bluetooth нам понадобятся следующие функции (они очень похожи на функции, используемые для получения списка локальных радиомодулей).

BluetoothFindFirstDevice - начинает перечисление устройств Bluetooth.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



Pbtsp

Указатель на структуру BLUETOOTH_DEVICE_SEARCH_PARAMS. Член dwSize этой структуры должен содержать размер структуры (устанавливается посредством SizeOf(BLUETOOTH_DEVICE_SEARCH_PARAMS)). Член hRadioдолжен содержать описатель локального радиомодуля, полученный вызовом функции BluetoothFindFirstRadio.



Pbtdi

Структура BLUETOOTH_DEVICE_INFO в которую будет возвращена информации об устройстве Bluetooth.

Возвращаемые значения: •В случае успешного выполнения функция вернет корректный описатель в качестве результата.
•В случае ошибки будет возвращен 0. Для получения кода ошибки используйте функцию GetLastError



BluetoothFindNextDevice - находит следующее устройство Bluetooth.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hFind

Описатель, который вернула функция BluetoothFindFirstDevice



pbtdi

Структура BLUETOOTH_DEVICE_INFO, в которую будет помещена информацию об устройстве

Возвращаемые значения: •Вернет TRUE, если устройство найдено.
•Вернет FALSE в случае отсутствия устройства. Используйте GetLastError для получения кода ошибки.



BluetoothFindDeviceClose - закрывает описатель перечисления устройств Bluetooth.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hFind

Описатель, который вернула функция BluetoothFindFirstDevice

Возвращаемые значения: •Вернет TRUE если описатель успешно закрыт.
•Вернет FALSE в случае ошибки. Для получения кода ошибки используйте GetLastError.



BluetoothGetDeviceInfo - возвращает информацию об указанном устройстве Bluetooth.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hRadio

Описатель локального радиомодуля Bluetooth



pbtdi

Структура BLUETOOTH_DEVICE_INFO, в которую возвразается информация об устройстве. dwSize должен быть равен размеру структуры. addreess должен содержать адрес устройства, о котором хотим получить информацию.

Возвращаемые значения: •Вернет ERROR_SUCCESS если выполнено успешно и информация занесена в структуру pbtdi. Остальные значения – код ошибки.



Обладая этими знаниями, можно написать процедуру получения информации об устройствах Bluetooth:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Работа с Bluetooth в Delphi. Часть 3

Вступление

И так, в предыдущих частях мы научились получать список локальных радиомодулей Bluetooth и удаленных устройств Bluetooth.

Нам осталось научиться получать список сервисов, предоставляемых удаленным устройством и управлять локальными радиомодулями.

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

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

Прежде, чем мы приступим, давайте определимся в терминах. Microsoft в своей документации вводит два термина: Radio и Device. Radio – это локальный радиомодуль Bluetooth (USB-брелок, интегрированное решение, в общем то, что установлено на вашем компьютере). Device – это то устройство Bluetooth с которым вы хотите обмениваться информацией. Будь то телефон, КПК, гарнитура или еще что-то. Важно понимать, что если мы пишем программу для PDA, то когда она работает на PDA - его модуль Bluetooth будет Radio, а компьютер - Device. Если же она работает на компьютере, то компьютерный модуль – Radio, а PDA – Device.

Что мы знаем

К сожалению, документация Microsoft по Bluetooth API и работе с Bluetooth настолько скудна (у меня получилось 50 страниц в Word с оформлением), а примеров они вообще не предоставляют, что из нее очень трудно понять, как же все-таки работает эта технология.

Когда я только начинал изучать этот предмет, я перерыл весь Internet, но так ничего вразумительного не нашел.

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

И так, приступим.

Создание проекта

Давайте создадим в Delphi новый проект и сохраним его под именем BTWork, а модуль – под именем Main.

Главную и пока единственную форму, назовем fmMain. Заголовок BTWork.

Теперь нам понадобятся файл JwaBluetoothAPI.pas, JwaBtHDef.pasи JwaBthSdpDef.pas. Их можно найти в примерах из предыдущих частей или в библиотеке BTClasses.

Для того, чтобы не тянуть с собой все остальные файлы из JWA, давайте эти чуть-чуть исправим. Найдите в них строку

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


и замените JwaWindows на Windows.


Далее удалить из них строки

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.




И в файле JwaBluetoothAPI удалите все, что находится между {$IFDEF DYNAMIC_LINK} и {$ELSE} вместе с этими DEF. И в конце этого файле удалите {$ENDIF}.

Далее, в JwaBluetoothAPI.pas после

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.




Да простят нас ребята, которые эту библиотеку писали!

Все эти действия я делал для того, что бы уменьшить архив примера. Да и не нужно тянуть за собой много лишнего. Хотя сама библиотека весьма полезна. Один модуль JwaWindows чего стоит. Там очень много интересного есть. Ну да ладно – что-то я отвлекся.

После того, как мы кастрировали эти модули, давайте добавим их в наш проект. Готово?

В этом приложении мы будем получать список локальных радиомодулей, устройств, к ним присоединенных, список сервисов, предоставляемых устройствами. Также мы должны управлять радиомодулями и научиться проходить авторизацию.

Приступаем.

Оформление главной формы

На главную форму поместим компонент TPanel и установите следующие свойства:





Свойство Значение

Align alTop

Caption

Name Panel


Далее поместим компонент TTreeView и установите свойства как в таблице:





Свойство Значение

Align alLeft
Cursor crHandPoint
HideSelection False
HotTrack True
Name TreeView
ReadOnly True


Правее TTreeView поместим TSplitter и установим следующие его свойства:





Свойство Значение
Name Splitter
Width 5


И, наконец, помещаем компонент TListView еще правее TSplitter. Устанавливаем его свойства как в таблице:





Свойство Значение
Align alClient
ColumnClick False
Cursor crHandPoint
GridLines True
HideSelection False
HotTrack True
Name ListView
ReadOnly True
RowSelect True
ShowWorkAreas True
ViewStyle vsReport


На TPanel поместим кнопку TButton.





Свойство Значение
Caption Refresh
Name btRefresh


Теперь мы готовы писать программу.

Пишем код

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

Для этого в обработчике OnCreate формы fmMain напишем такой код:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


А в обработчике OnClick кнопки btRefresh напишем следующее:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


В uses нашего модуля, который относится к главной форме, допишем:
Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.




Здесь хочу привести описание используемых структур, так как ранее я их не описывал:

BLUETOOTH_DEVICE_SEARCH_PARAMS

Объявление:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.

Члены:



dwSize

Входной параметр. Должен быть равен размеру структуры (dwSize := SizeOf(BLUETOOTH_DEVICE_SEARCH_PARAMS))



fReturnAuthenticated

Входной параметр. Функция будет возвращать устройства, прошедшие авторизацию.



fReturnRemembered

Входной параметр. Функция будет возвращать устройства, уже запомненные раннее.



fReturnUnknown

Входной параметр. Функция будет возвращать новые либо неизвестные устройства.



fReturnConnected

Входной параметр. Функция будет возвращать подключенные устройства.



fIssueInquiry

Входной параметр. Заставляет функцию проверять устройства.



cTimeoutMultiplier

Входной параметр. Тайм-аут для проверки устройства.



hRadio


Handle радиомодуля, для которого проводится поиск устройств. Если 0, то проверяются все радиомодули.




BLUETOOTH_DEVICE_INFO

Объявление:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Члены:



dwSize

Входной параметр. Должен быть равен размеру структуры (dwSize := SizeOf(BLUETOOTH_DEVICE_INFO))



Address

Адрес устройства Bluetooth.



ulClassofDevice

Класс устройства. Подробнее по классам смотрите в JwaBluetoothAPIs. Константы с префиксом COD_xxx.



fConnected

Если TRUE, то устройство подключено/используется



fRemembered

Если TRUE, то устройство ранее уже было найдено (запомнено)



fAuthenticated

Если TRUE, то устройство прошло авторизацию (авторизированно)



stLastSeen

Дата и время последнего обнаружения устройства



stLastUsed

Дата и время последнего использования устройства



szName

Название устройства (имя)




BLUETOOTH_RADIO_INFO

Объявление:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.



Члены:



dwSize

Должен быть равен размеру структуры (dwSize := SizeOf(BLUETOOTH_RADIO_INFO))



Address


Адрес радиомодуля Bluetooth



szName


Имя радиомодуля



ulClassofDevice

Класс устройства (см. выше)


lmpSubversion

Зависит от производителя



Manufacturer

Код производителя. Определяется константами BTH_MFG_Xxx. Более полную информацию о производителях можно получить на сайте поддержки Bluetooth.




Далее напишем вот такой обработчик события OnChange для TreeView:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


В этом коде появилось три новые функции, которые выделены жирным шрифтом. Вот они

BluetoothIsConnectable - определяет, возможно ли подключение к указанному радиомодулю.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hRadio

Handle радиомодуля, который мы проверяем. Если 0, то проверяются все радиомодули.

Возвращаемые значения: •Вернет TRUE, если указанный радиомодуль разрешает входящие подключения. Если hRadio=0, то вернет TRUE, если хотя бы один радиомодуль разрешает входящие подключения.
•Если входящие подключения запрещены, то вернет FALSE.



BluetoothIsDiscoverable - определяет, будет ли виден указанный радиомодуль другим при поиске. Если просматриваются все радиомодули, то вернет TRUE если хотя бы один разрешает обнаружение.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hRadio

Handle радиомодуля, который мы проверяем. Если 0, то проверяются все радиомодули.

Возвращаемые значения: •Вернет TRUE, если указанный радиомодуль разрешает обнаружение. Если hRadio=0, то вернет TRUE, если хотя бы один радиомодуль разрешает обнаружение.
•Если обнаружение запрещено, то вернет FALSE.



BluetoothEnumerateInstalledServices - получает список GUID сервисов, предоставляемых устройством. Если параметр hRadio=0, то просматривает все радиомодули.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hRadio

Handle радиомодуля, который мы проверяем. Если 0, то проверяются все радиомодули.



pbtdi

Указатель на структуру BLUETOOTH_DEVICE_INFO, в которой описано проверяемое устройство. Необходимо заполнить поля dwSizeи Address.



pcServices

При вызове – количество записей в массиве pGuidServices, возвращает в этом параметре реальное количество сервисов, предоставляемых устройством.



pGuidServices

Указатель на массив TGUID, в который будут записаны GUID предоставляемых устройством сервисом. Если nil и pcServices=0, то в pcServices будет записано количество сервисов. Необходимо выделить для pGuidServices память размером не менее pcServices*SizeOf(TGUID).

Возвращаемые значения: •Вернет ERROR_SUCCESS, если вызов успешен и количество сервисов в pcServices соответствует реальности.
•Вернет ERROR_MORE_DATA, если вызов успешен, но выделенное количество памяти (pcServices при вызове) меньше, чем количество предоставляемых сервисов.
•В случае ошибки – другие коды ошибок Win32.



Примечания:

Посмотрите на код получения списка сервисов:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.




Сначала мы вызываем функцию с pcServices=0 и pGuidServices=nilдля того, чтобы получить количество сервисов, предоставляемых устройством.

Потом выделяем память (SetLength()) и только затем вызываем функцию с реальными параметрами и получаем список сервисов.

Еще важное замечание. В файле JwaBluetoothAPIs.pas параметр pbtdi имеет тип PBLUETOOTH_DEVICE_INFO, который раскрывается в BLUETOOTH_DEVICE_INFO. Заметьте, что это НЕ УКАЗАТЕЛЬ. Это не верно, так как в исходном виде функция требует именно указатель. По-этому, я ввел тип

type
__PBLUETOOTH_DEVICE_INFO = ^PBLUETOOTH_DEVICE_INFO




Так что ИСПОЛЬЗУЙТЕ файл из примера, а не из исходной библиотеки. Иначе получите нарушение доступа к памяти.

Комментарий к коду: Мы перечитываем информацию об устройстве, так как за время, пока мы любуемся программой, могли произойти различные события: устройство отключили, отменили авторизацию и т. п. А мы хотим иметь самую свежую информацию об устройстве.

В принципе то, что описано выше, мы уже знали, кроме двух указанных функций.

Давайте расширим возможности нашего приложения. Добавим функции запрета/разрешения обнаружения радиомодуля и запрета/разрешения подключения к нему.

BluetoothEnableIncomingConnections и BluetoothEnableDiscoverable

Поместим на форму компонент TactionList и изменим его свойства как показано в таблице.



Свойство Значение
Name ActionList


Теперь два раза щелкнем по ActionList и в появившемся окне редактора свойств добавим две TAction со следующими свойствами:



Свойство Значение
Caption Connectable
Name acConnectable




Свойство Значение
Caption Discoverable
Name acDiscoverable


На панель Panel добавим две TButton и установим свойства:



Свойство Значение
Action acConnectable
Name btConnectable




Свойство Значение
Action acDiscoverable
Name btDiscoverable


Напишем вот такой обработчик события OnUpdate у acConnectable:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


И то же самое напишем для обработчика события OnUpdate - acDiscoverable:
Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Такой же обработчик напишем и для OnExecute - acDiscoverable:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.




Вывод окна свойств устройства

Важно: Если Windows сам использует радиомодуль, то он не даст поменять статус, хотя и функция выполнится без ошибок!

Здесь мы ввели две новые функции (выделены жирным):

BluetoothEnableInfomingConnection - функция разрешает/запрещает подключения к локальному радиомодулю Bluetooth.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hRadio

Handle радиомодуля, статус которого мы хотим изменить. Если 0, то меняем у всех.



fEnabled

TRUE – разрешаем подключения; FALSE – запрещаем.

Возвращаемые значения: •TRUE - если вызов успешен и статус изменен,
•FALSE - в противном случае.



BluetoothEnableDiscovery - функция разрешает/запрещает обнаружение локального радиомодуля Bluetooth

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hRadio

Handle радиомодуля, статус которого мы хотим изменить. Если 0, то меняем у всех.



fEnabled

TRUE – разрешаем обнаружение; FALSE – запрещаем.

Возвращаемые значения: •TRUE - если вызов успешен и статус изменен,
•FALSE - в противном случае.



Теперь давайте научимся выводить системное окно свойств устройства Bluetooth. Для этого добавим к ActionList еще один TAction вот с такими свойствами:



Свойство Значение
Caption Property
Name acProperty


Добавим на Panel кнопку TButton с такими свойствами:



Свойство Значение
Action acProperty
Name btProperty


Теперь напишем такой обработчик событий OnUpdate у acProperty:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


И обработчик OnExecute для нее же:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.




Важно: В исходном виде в файле JwaBluetoothAPIs функция BluetoothDisplayDeviceProperties объявлена не верно. Второй параметр должен быть указателем, а там он передается как структура. Я исправил функцию так, чтобы он передавался как var-параметр (по ссылке). Используйте модуль JwaBluetoothAPIs из этого примера, чтобы не возникало ошибок доступа к памяти.

Важно: Ни в этой процедуре, ни ранее, ни далее я не провожу проверку ошибок, чтобы не загромождать код лишними подробностями. В реальном приложении НЕОБХОДИМО проверять возвращаемые функциями значения и указатели.

И так, в этом коде есть новая функция, выделенная жирным шрифтом.

BluetoothDisplayDeviceProperty - функция выводит стандартное окно свойств устройства Bluetooth.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Важно: В оригинале (см. примечание выше) функция выглядит вот так:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Это не верно, так как в документации Microsoft указано, что параметр pbtdiдолжен передаваться как указатель (что подразумевает запись PBLUETOOTH_DEVICE_INFO), но как я писал выше, этот тип ошибочен. Он не является указателем. Я изменил функцию так, как показано выше (так она и должна быть, если не менять определение типа).

Параметры:



hwndParent

Handle родительского окна, которому будет принадлежать диалог свойств. Может быть 0, тогда родительским выбирается окно Desktop.



pbtdi

Указатель на структуру BLUETOOTH_DEVICE_INFO в которой содержится адрес требуемого устройства.


Возвращаемые значения: •TRUE - если вызов успешен
•FALSE - в противном случае (код ошибки можно узнать вызовом функции GetLastError).



Выбор устройства

Рассмотрим, как вызвать окно диалога выбора устройства.

Добавим в наш проект на Panel еще одну кнопку TButton и установите ее свойства как в таблице:



Свойство Значение
Caption Select
Name btSelect


Напишем вот такой обработчик события OnClick у этой кнопки:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.




В этой части кода две новые функции.

BluetoothSelectDevices - функция разрешает/запрещает обнаружение локального радиомодуля Bluetooth.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



pbtsdp

Описание смотрите ниже в описании структуры.




Возвращаемые значения:

Если функция вернула TRUE, то пользователь выбрал устройства. Pbtsdp^.pDevices будет указывать на корректные данные. После вызова необходимо проверить флаги fAuthenticated и fRemembered, что бы удостовериться в корректности данных. Для освобождения памяти используйте функцию BluetoothSelectDevicesFree, только если функция вернет TRUE.

Вернет FALSE если вызов прошел не удачно. Используйте GetLastErrorдля получения дополнительных сведений. Возможные ошибки:




ERROR_CANCELLED

Пользователь отменил выбор устройства.



ERROR_INVALID_PARAMETER


Параметр pbsdp равен nil.



ERROR_REVISION_MISMATCH

Структура, переданная в pbsdp неизвестного или неверного размера.



Другие ошибки Win32






BLUETOOTH_SELECT_DEVICE_PARAMS

Объявление:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Члены:



dwSize

Должен быть равен размеру структуры (dwSize := SizeOf(BLUETOOTH_RADIO_INFO))



cNumOfClasses

Входной параметр. Количество записей в массиве prgClassOfDevice. Если 0, то ищутся все устройства.



prgClassOfDevices

Входной параметр. Массив COD (классов устройств), которые необходимо искать.


pszInfo

Входной параметр. Если не nil, то задает текст заголовка окна выбора устройства.



hwndParent

Входной параметр. Handle родительского окна для диалога выбора устройства. Если 0, то родителем будет Desktop.



fForceAuthentication

Входной параметр. Если TRUE, то требует принудительной авторизации устройств.



fShowAuthenticated

Входной параметр. Если TRUE, то авторизованные устройства будут доступны для выбора.



fShowRemembered

Входной параметр. Если TRUE, то запомненные устройства будут доступны для выбора.



fShowUnknown

Входной параметр. Если TRUE, то неизвестные (неавторизованные и не запомненные) устройства будут доступны для выбора.



fAddNewDeviceWizard

Входной параметр. Если TRUE, то запускает мастер добавления нового устройства.



fSkipServicesPage

Входной параметр. Если TRUE, то пропускает страницу Сервисы в мастере.



pfnDeviceCallback

Входной параметр. Если не nil, то является указателем на функцию обратного вызова, которая вызывается для каждого найденного устройства. Если функция вернет TRUE, то устройства добавляется в список, если нет, то устройство игнорируется.



pvParam

Входной параметр. Его значение будет передано функции pfnDeviceCallback в качестве параметра pvParam.



cNumDevices

Как входной параметр – количество устройств, которое требуется вернуть. Если 0, то нет ограничений. Как выходной параметр – количество возвращенных устройств (выбранных).



pDevices

Выходной параметр. Указатель на массив структур BLUETOOTH_DEVICE_INFO. Для его освобождения используйте функцию BluetoothSelectDevicesFree.
Важно: В оригинале этот параметр объявлен как PBLUETOOTH_DEVICE_INFO. По этому поводу здесь много комментариев.




BluetoothSelectDevicesFree - функция должна вызываться, только если вызов BluetoothSelectDevices был успешен. Эта функция освобождает память и ресурсы, задействованные функцией BluetoothSelectDevices в структуре BLUETOOTH_SELECT_DEVICE_PARAMS.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



pbtsdp

Описание смотрите выше в описании структуры.

Возвращаемые значения: •TRUE - если вызов успешен,
•FALSE - нечего освобождать.



Управление сервисами

Для управления сервисами Microsoft Bluetooth API предоставляет функцию:

BluetoothSetServiceState - включает или выключает указанный сервис для устройства Bluetooth. Система проецирует сервис Bluetooth на соответствующий драйвер. При отключении сервиса – драйвер удаляется. При его включении – драйвер устанавливается. Если выполняется включение не поддерживаемого сервиса, то драйвер не будет установлен.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hRadio

Описатель радиомодуля.



pbtdi

Указатель на структуру BLUETOOTH_DEVICE_INFO.



pGuidService

GUID сервиса, который необходимо включить/выключить.



dwServiceFlags

Флаги управления сервисом:

BLUETOOTH_SERVICE_DISABLE – отключает сервис;
BLUETOOTH_SERVICE_ENABLE – включает сервис.


Возвращает ERROR_SUCCESS если вызов прошел успешно. Если вызов не удался вернет один из следующих кодов:



ERROR_INVALID_PARAMETER

Неверные флаги в dwServiceFlags


ERROR_SERVICE_DOES_NOT_EXIST

Указанный сервис не поддерживается



Другие ошибки Win32





Важно: В оригинале (см. примечание выше) функция выглядит вот так:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Это не верно, так как в документации Microsoft указано, что параметр pbtdiдолжен передаваться как указатель (что подразумевает запись PBLUETOOTH_DEVICE_INFO), но как я писал выше, этот тип ошибочен. Он не является указателем. Я изменил функцию так, как показано выше (так она и должна быть, если не менять определение типа).


Как использовать функцию? Давайте добавим к ActionList еще одну TAction с такими свойствами:



Свойство Значение
Caption Disable
Name acEnable


И добавим на Panel еще одну кнопку TButton, установив у нее следующие свойства:



Свойство Значение
Action acEnable
Name btEnable

В обработчике события OnUpdate для acEnable напишем вот такой код:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


А в обработчике OnExecute для acEnable вот такой код:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.




Важно: После нажатия на кнопку btEnableсервис будет удален из системы. Включить его можно будет через окно свойств устройства Bluetooth.

Как определять отключенные сервисы рассмотрим в серии про передачу данных через Bluetooth.

Удаление устройств

Для удаления устройств используется функция:

BluetoothRemoveDevice - функция удаляет авторизацию между компьютером и устройством Bluetooth. Так же очищает кэш-записи об этом устройстве.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.
;

Параметры:



hAddress

Адрес устройства, которое удаляется.

Возвращаемые значения:



ERROR_SUCCESS

устройство удалено



ERROR_NOT_FOUND

устройство не найдено




Давайте попробуем. Добавим в ActionList TAction со следующими свойствами:



Свойство Значение
Caption Remove
Name acRemove


И на Panel кнопку TButton со свойствами:



Свойство Значение
Action acRemove
Name btRemove


В обработчике OnUpdate для acRemove напишем следующий код:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


А для события OnExecute вот такой код:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.




Процедура выполняется достаточно долго, так что не думайте, что программа зависла.

Важно: Устройство удаляется из списка. Однако, если уже иметь адрес устройства, то можно получить о нем информацию.

Есть еще одно функция, которая связана с BluetoothRemoveDevice. Это:

BluetoothUpdateDeviceRecord - функция обновляет данные об устройстве в кэше.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



pbtdu

Указатель на структуру BLUETOOTH_DEVICE_INFO. В ней должны быть заполнены поля:
dwSize – размер структуры;
Address – адрес устройства;
szName – новое имя устройства.

Возвращаемые значения:



ERROR_SUCCESS

Функция выполнена успешно



ERROR_INVALID_PARAMETER

Указатель pbtdi=nil. (Для варианта в Delphi не реально, так как указатель мы получаем из структуры, передавая ее как var-параметр).



ERROR_REVISION_MISMATCH

Размер структуры в dwSize не правильный



Другие ошибки Win32






Попробуем использовать и ее. Схема стандартная: TAction к ActionList, TButton на Panel:



Свойство Значение
Caption Update
Name acUpdate




Свойство Значение
Action acUpdate
Name btUpdate


Код:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Как видите, все просто.


И так, удалять устройства мы умеем. Давайте теперь научимся добавлять их. Для этого Bluetooth API предоставляет две функции:

BluetoothAuthenticateDevice - отправляет запрос на авторизацию удаленному устройству Bluetooth. Есть два режима авторизации: "Wizrd mode" и "Blind Mode".

"Wizard Mode" запускается, когда параметр pszPasskey = nil. В этом случае открывается окно "Мастера подключения". У пользователя будет запрошен пароль, который будет отправлен в запросе на авторизацию удаленному устройству. Пользователь будет оповещен системой об успешном или не успешном выполнении авторизации и получит возможность попытаться авторизировать устройства еще раз.

"Blind Mode" вызывается, когда pszPasskey <> nil. В этом случае пользователь не увидит никакого мастера. Вам необходимо программно запросить код авторизации (pszPasskey) и уведомить пользователя о результате.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hwndParent

Handle родительского окна. Если 0, то родительским окном станет окно Desktop.



hRadio

Handle локального радиомодуля. Если 0, то авторизация будет проведена на всех радиомодулях. Если хотя бы один пройдет авторизацию, функция выполнится успешно.



pbdti

Информация об устройстве, на котором необходимо авторизироваться.



pszPasskey

PIN для авторизации. Если nil, то вызывается мастер авторизации (описано выше). Важно: pszPasskey не NULL-терминированная строка!



ulPasskeyLength

Длина строки в байтах. Должна быть меньше либо равна BLUETOOTH_MAX_PASSKEY_SIZE * SizeOf(WCHAR).

C++ highlight
Возвращаемые значения:


ERROR_SUCCESS

Функция выполнена успешно



ERROR_CANCELLED

Пользователь отменил процесс авторизации



ERROR_INVALID_PARAMETER

Структура pbtdi не верна



ERROR_NO_MORE_ITEMS

Устройство в pbtdi уже авторизированно



Другие ошибки Win32






Для "Blind Mode" соответствие кодов ошибок Bluetooth кодам ошибок Win32 приведено в таблице:





Bluetooth Win32

BTH_ERROR_SUCCESS ERROR_SUCCESS
BTH_ERROR_NO_CONNECTION ERROR_DEVICE_NOT_CONNECTED
BTH_ERROR_PAGE_TIMEOUT WAIT_TIMEOUT
BTH_ERROR_HARDWARE_FAILURE ERROR_GEN_FAILURE
BTH_ERROR_AUTHENTICATION_FAILURE ERROR_NOT_AUTHENTICATED
BTH_ERROR_MEMORY_FULL ERROR_NOT_ENOUGH_MEMORY
BTH_ERROR_CONNECTION_TIMEOUT WAIT_TIMEOUT
BTH_ERROR_LMP_RESPONSE_TIMEOUT WAIT_TIMEOUT
BTH_ERROR_MAX_NUMBER_OF_CONNECTIONS ERROR_REQ_NOT_ACCEP
BTH_ERROR_PAIRING_NOT_ALLOWED ERROR_ACCESS_DENIED
BTH_ERROR_UNSPECIFIED_ERROR ERROR_NOT_READY
BTH_ERROR_LOCAL_HOST_TERMINATED_CONNECTION ERROR_VC_DISCONNECTED


Аналогичная функция:

BluetoothAuthenticateMultipleDevices - позволяет авторизироваться сразу на нескольких устройствах при помощи одной копии "Мастера авторизации".

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hwndParent

Handle родительского окна. Если 0, то родительским окном станет окно Desktop.



hRadio

Handle локального радиомодуля. Если 0, то авторизация будет проведена на всех радиомодулях. Если хотя бы один пройдет авторизацию, функция выполнится успешно.



cDevices

Количество элементов в массиве rgpbtdi.



rgpbtdi

Массив структур BLUETOOTH_DEVICE_INFO, в котором представлены устройства для авторизации.

Возвращаемые значения:



ERROR_SUCCESS

Функция выполнена успешно. Проверьте флаг fAuthenticatedу каждого устройства, что бы знать, какие прошли авторизацию.



ERROR_CANCELLED

Пользователь отменил процесс авторизации. Проверьте флаг fAuthenticated у каждого устройства, что бы знать, какие прошли авторизацию.



ERROR_INVALID_PARAMETER

Один или несколько элементов массива rgpbtdi не верны.



ERROR_NO_MORE_ITEMS

Все устройства в массиве уже авторизированны.



Другие ошибки Win32






Важно: В оригинале функция выглядит вот так:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Это не верно, так как в документации Microsoft указано, что параметр rgpbtdiдолжен передаваться как указатель (что подразумевает запись PBLUETOOTH_DEVICE_INFO), но как я писал выше, этот тип ошибочен. Он не является указателем. Я изменил функцию так, как показано выше. По поводу типа __PBLUETOOTH_DEVICE_INFO я писал выше.


Описывать с примером, как использовать эти функции не буду, так как они тривиальны (если вы прочитали все вышеизложенное). Остались последние три функции, которые мы не рассмотрели:

BluetoothRegisterForAuthentication - регистрирует функцию обратного вызова, которая будет вызываться на запрос устройства об авторизации. Если несколько приложений зарегистрировало такую функцию, то будет вызвана функция в последнем приложении.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



pbtdi

Указатель на BLUETOOTH_DEVICE_INFO. Используется адрес устройства, для которого регистрируется функция. Обратите внимание на параметр. В оригинале он опять передается не как указатель.



phRegHandle

Указатель, куда будет возвращен Handle регистрации, которой потом используется в BluetoothUnregisterAuthentication.



pfnCallback

Функция обратного вызова.



pvParam

Опциональный параметр, который без изменения передается в функцию обратного вызова.

Возвращаемые значения:



ERROR_SUCCESS

Функция выполнена успешно.



ERROR_OUTOFMEMORY

Недостаточно памяти.



Другие ошибки Win32






BluetoothUnregisterAuthentication - удаляет функцию обратного вызова, зарегистрированную функцией BluetoothRegisterForAuthentication и закрывает Handle.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hRegHandle

Handle регистрации, полученный функцией BluetoothRegisterForAuthentication.




Возвращаемые значения:

Вернет TRUE, если вызов успешен и FALSE в случае неудачи. Используйте GetLastError для получения дополнительной информации.

BluetoothSendAuthenticationResponse - эта функция должна вызываться из функции обратного вызова при запросе авторизации удаленным устройством для передачи PIN.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



hRadio

Handle радиомодуля, для которого проводим авторизацию. Если 0, то пытаемся на всех.



pbtdi

Указатель на BLUETOOTH_DEVICE_INFO с данными об устройстве, от которого поступил запрос на авторизацию. Может быть тот же указатель, который передан в функцию обратного вызова.



pszPasskey

Указатель на UNICODE строку, в которой содержится ключ авторизации (PIN).

Возвращаемые значения:



ERROR_SUCCESS

Функция выполнена успешно.



ERROR_CANCELLED

Устройство отвергло авторизационный код (PIN). Так же, возможно, имеются проблемы со связью



E_FAIL

Устройство вернуло ошибку авторизации.



Другие ошибки Win32






И, наконец, функция обратного вызова:

PFN_AUTHENTICATION_CALLBACK

Описание этой функции дано выше. Здесь приведу лишь определеннее.

Объявление функции:

Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.


Параметры:



pvParam

Указатель на параметр, который мы передали в BluetoothRegisterForAuthentication.



pDevice

Указатель на BLUETOOTH_DEVICE_INFO с данными об устройстве, от которого поступил запрос на авторизацию.




Заключение

На этот раз все. Мы рассмотрели все функции Bluetooth API от Microsoft. Также мы научились управлять устройствами и радиомодулями Bluetooth, проводить авторизацию и получать информацию об этих устройствах.

Но актуальным остается вопрос, который мне многие задают. Как же все-таки передавать данные между устройствами Bluetooth?

Ответ на этот вопрос читайте в следующей серии статей "Передача данных через Bluetooth".

Конечно, можно было бы всю эту информацию уместить в эти статьи, но объем ее не сравним с предоставленным здесь. Так что наберитесь терпения. Я постараюсь надолго не задерживать с выходом новой серии.

Полностью рабочий пример, рассмотренный в этой статье, вы можете скачать здесь (300K).

Я буду рад любым замечаниям и пожеланиям по данной теме.

P.S. Внимательно относитесь к сторонним библиотекам. Как видите, в JWALIB оказалось много ошибок, которые порой загоняют в тупик. Я минут 20 смотрел на Access Violation, пока не понял, в чем дело.


Hide:
Чтобы просмотреть данный текст, авторизуйтесь на форуме.
__________________
Чищю стаб - 15$
Пользователь вне форума    
Наши Спонсоры
 

Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
[Delphi/Pascal] Задай вопрос - получи ответ (1 часть) killerloop Delphi 1517 31.01.2014 01:09
[Статья] Delphi: заметки программиста. Часть 1 _PixeL_ Delphi 0 09.06.2012 11:54
Программирование на [ Delphi ] Обучающий курс [ Soft ] Voland-791 Софт для Windows 0 08.02.2010 19:07
Среда программирования Delphi Samikolo Delphi 0 07.12.2009 07:02
Работа с INI файлами из delphi. Nobody4all Delphi 0 07.06.2009 17:30



Часовой пояс GMT +2
Powered by vBulletin® 3.x.x Copyright ©2000 - 2012, Jelsoft Enterprises Ltd.

Copyright © 2008 - 2013 «HPC» Реклама на сайте Правила Форума Пользовательское соглашение Работа на сайте
При копировании материалов ставьте ссылку на источник
Все материалы представлены только в ознакомительных целях, администрация за их использование ответственности не несет.