/*
* CLID_AddSingleNPANXX(npa, nxx)
*/
int CLID_AddSingleNPANXX(int npa, int nxx) {
checkAttach();
CLID_IPCData.npa = npa;
CLID_IPCData.nxx = nxx;
CLID_IPC(CLID_MsgAddSingleNPANXX);
return (CLID_IPCData.returnValue);
}
/*
* CLID_IPC(номер_сообщения_IPC)
*
* Эта подпрограмма вызывает сервер с глобальным буфером
* CLID_IPCData и заносит в него номер сообщения,
* переданный ей в качестве аргумента.
*
* Если сервера нет, эта подпрограмма установит
* поле returnValue в CLID_NoServer. Остальные
* поля остаются как есть.
*/
void CLID_IPC(int IPCMessage) {
if (clid_pid == -1) {
CLID_IPCData.returnValue = CLID_NoServer;
return;
}
CLID_IPCData.serverFunction = IPCMessage;
CLID_IPCData.type = 0x8001;
CLID_IPCData.subtype = 0;
if (Send(clid_pid, &CLID_IPCData, &CLID_IPCData,
sizeof(CLID_IPCData), sizeof(CLID_IPCData))) {
CLID_IPCData.returnValue = CLID_IPCError;
return;
}
}
void checkAttach() {
if (clid_pid == -1) {
CLID_Attach(NULL);
}
}
Как вы видите, функция checkAttach() применяется для проверки существования соединения с сервером CLID. Если бы соединения не было, это было бы подобно запросу read() по несуществующему дескриптору файла. В моем варианте программы функция checkAttach() создает соединение автоматически. Это как если бы функция read() определила, что дескриптор файла некорректен, и сама создала бы корректный. Еще один вопрос стиля.
Обмен специализированными сообщениями происходит в функции CLID_IPC(). Она берет значение глобальной переменной CLID_IPCData и пробует переслать его серверу, используя функцию QNX4 Send().
Специализированные сообщения могут быть обработаны о из двух способов. Можно:
1. транслировать их в стандартные вызовы функций POSIX основанные на файловых дескрипторах;
2. инкапсулировать их в сообщение типа devctl(), либо в специализированное сообщение, используя тип _IO_MSG.
В обоих случаях вы перестраиваете клиента на обмен сообщениями стандартными для администраторов ресурсов средствами. Как? У вас нет файлового дескриптора? Есть только идентификатор соединения? Или наоборот? Ну, это как раз не проблема. В QNX/Neutrino дескриптор файла в действительности является идентификатором соединения!
В случае CLID-сервера это не вариант. Не существует стандартного POSIX-вызова на основе файлового дескриптора, который мог бы «добавить к администратору ресурса CLID пару NPA/NXX». Однако, существует стандартный механизм devctl(), так что если ваши отношения клиент/сервер требуют такой формы, смотрите ниже.
Прежде чем броситься реализовывать этот подход (трансляцию в стандартные сообщения на основе файловых дескрипторов), давайте остановимся и подумаем, где это может оказаться полезным. В аудиодрайвере QNX4 вы могли бы использовать нестандартные сообщения для передачи аудиоданных администратору и от него. При ближайшем рассмотрении здесь, для задачи блочной передачи данных, вероятно, наиболее бы подошли функции read() и write(). Установку частоты оцифровки, с другой стороны, можно было бы гораздо удачнее реализовать с применением функции devctl().
Хорошо, но ведь не каждое взаимодействие клиент/сервер сводится к блочной передаче данных (тот же сервер CLID — тому пример).
Итак, возник вопрос — как выполнять операции управления? Самый простой способ состоит в применении POSIX-вызова devctl(). Наш пример из библиотеки CLID примет вид:
/*
* CLID_AddSingleNPANXX(npa, nxx)
*/
int CLID_AddSingleNPANXX(int npa, int nxx) {
struct clid_addnpanxx_t msg;
checkAttach(); // Оставить или нет — дело вкуса
msg.npa = npa;
msg.nxx = nxx;
return
(devctl(clid_pid, DCMD_CLID_ADD_NPANXX,
&msg, sizeof(msg), NULL));
}
Как видите, операция относительно безболезненная. (Для тех, кто не любит devctl() за то, что приходится отправлять в обе стороны блоки данных одного и того же размера, см. ниже обсуждение сообщений _IO_MSG.). Опять же, если вы пишете программу, которая должна работать в обеих операционных системах, вам следует выделить функцию обмена сообщениями в отдельный библиотечный модуль и предоставить несколько вариантов реализации, в зависимости от применяемой операционной системы.
Реально мы убили двух зайцев:
1. отказались от глобальной переменной и стали собирать сообщения на основе стековой переменной — это делает нашу программу безопасной в многопоточной среде (thread- safe);
2. передали структуру данных нужного размера вместо структуры данных максимального размера, как мы это делали в предыдущем примере с QNX4.
Заметьте, что нам пришлось определить константу DCMD_CLID_ADD_NPANXX — в принципе, мы могли бы для этих же целей применить константу CLID_MsgAddSingleNPANXX (сделав соответствующее изменение в заголовочном файле), но я просто хотел подчеркнуть тот факт, что эти две константы не являются одинаковыми.
Второй убитый заяц заключался в том, что мы передали «структуру данных нужного размера». На самом деле, мы тут немножко приврали. Обратите внимание на то, что функция devctl() имеет только один параметр размера (четвертый, который мы установили в sizeof(msg)
). Как на самом деле происходит пересылка данных? Второй параметр функции devctl() содержит команду для устройства (поэтому и «DCMD»). Двумя старшими битами команды кодируется направление, которое может быть одним из четырех:
1. «00» — передачи данных нет;
2. «01» — передача от драйвера клиенту;
3. «10» — передача от клиента драйверу;
4. «11» — двунаправленная передача.
Если вы не передаете данные (то есть достаточно просто команды) или передаете их в одном направлении, то применение функции devctl() — прекрасный выбор. Интересен тот вариант, когда вы передаете данные в обоих направлениях. Интересен он тем, что, поскольку у функции devctl() только один параметр размера, обе пересылки данных (как драйверу, так и от драйвера) передадут весь буфер данных целиком! Это хорошо в том частном случае, когда размеры буферов «ввода» и «вывода» одинаковы, но представьте себе, что буфер принимаемых драйвером данных имеет размер в несколько байт, а буфер передаваемых данных гораздо больше. Поскольку у нас есть только один параметр размера, мы вынуждены будем каждый раз передавать драйверу полный буфер данных, хотя требовалось передать всего несколько байт!