На базарной площади довольно часто можно слышать высказывания об
Обероне. Мне кажется, что на базарной площади пора появиться ветке об
этой системе и языке, что-то вроде "Мысли об Обероне". Что это такое, перспективы
этой системы, что
полезного можно извлечь из него для программирования на Дельфи
(например) и др.
"... и тут выхожу я. Весь в белом..."
Командировка завершилась. В общем (в смысле успешности всех и всего) - середина на половину. В частности - в применении libao на практике - успех 100%! Пока не выложил на qnxclub.net - можете спрашивать у меня по
wwlos[пэсык]hotmail[крапка]com
Предупреждаю сразу - писАл и буду писАть и сопровождать libao тольки для POSIX-систем. У кого будет желание переписать её для винды - милости просим. Даже на копирайт на вариант для этой каличности претендовать не буду!
Для тех, кто не в курсе или подзабыл, речь идёт об "эмуляции" синтаксиса и семантики Active Oberon-а в С++
текст программ будет выглядеть примерно так:
(как пример, здесь приведён класс "стопора" в конце функции main(), когда все активные объекты проинициализированы и запущены и начинается "жизнь" программы. Смысла в едином "главном цикле приложения" нет – объекты вполне самостоятельны, но "проваливаться" ниже по тексту программы нельзя – завершится процесс и все помруть. Было "принято решение" сделать примитивный AWAIT, пока где-нибудь, кто-нибудь из активных объектов (имеющих на то полномочия) не вызовет метод Exit_t::SetTrue(), тем самым "открывая кессоны". В состоянии ожидания выполнения условия поток активности объекта практически не потребляет системные ресурсов (в смысле тактов и байтов), зато логика становится совершенно прозрачной).
class Exit_t
{ DECL_X; // теперь можно пользоваться X-блоками в экземплярах класса
bool value;
public:
Exit_t (bool a_value) : value(a_value) { };
void WaitForExit();
void SetTrue();
};
//------------------------------------------------------------------------------
void Exit_t::WaitForExit()
{
X_BEGIN
X_AWAIT(value); // объект засыпает, пока кто-нить не вызовет Set()
X_END
};
//------------------------------------------------------------------------------
void Exit_t::SetTrue()
{
X_BEGIN
value = true;
X_END // по выходу текущего потока из этого блока выполнение главного потока приложения возобновится после X_AWAIT(value) в методе WaitForExit();
};
===================
Более реалистичный пример – "читатель" из rs232.
class RS232RX_t : public InputInterface_t
{
DECL_X; // у нас есть EXCLUSIVE блоки...
DECL_ACTIVITY(aRX); // активность, собственно, читающая из 232
DECL_ACTIVITY(aMsgTX); // активность поставляющая буферы потребителю
int rxfd; // file ident файла, за которым "прячется" rs232
Buffers_t buffers; // место для хранения ссылок на буферы
Buffers_t free_buffers; // место для хранения ссылок на пустые буферы
Buffers_t filled_buffers; // место для хранения ссылок на заполненные буферы
int RX (Buffer_t*);
static void* RXA(void* pA); // статическая функция потока активности
static void* MsgTXA(void* pA); // то же самое
public:
RS232RX_t(Log_t*, char* cfg_file, char* cfg_file_sec, Stub_t* psch);
~RS232RX_t();
/*virtual*/int Accept (Buffer_t*, bool/*use_filled_if_no_free = false*/){return -1;};
/*virtual*/void AcceptError(Buffer_t*, int/*error_code*/) { };
}
//----------------------------------------------------------------
// пример запуска активности в конструкторе объекта
RS232RX_t::RS232RX_t(Log_t* a_plog, char* cfg_file, char* cfg_file_sec, Stub_t* a_psch)
: InputInterface_t(a_plog, a_psch),
buffers(true) // коллекция-владелец своих буферов, в деструкторе она их "почикает"
{
...
if(ACTIVITY_START(aMsgTX, &MsgTXA, NULL) != EOK)
throw E_EXCH_START_MsgTX;
if(ACTIVITY_START(aRX, &RXA, NULL) != EOK)
throw E_EXCH_START_RX;
...
}
//------------------------------------------------------------------------------
// деструктор
RS232RX_t::~RS232RX_t()
{
char* func_name = "RS232RX_t::~RS232RX_t :";
...
if(ACTIVITY_STOP(aRX) != EOK)
plog->Error("%s останов активности aRX", func_name);
if(ACTIVITY_STOP(aMsgTX) != EOK)
plog->Error("%s останов активности aMsgTX", func_name);
...
}
//-------------------------------------------------------------
// пример функции потока активности объекта
void*
RS232RX_t::RXA(void* pA)
{
DECL_THIS_(RS232RX_t*, ((A_t*)pA)->powner); // это – статическая функция, а нам нужен this (здесь, по соглашению это THIS). Извлекаем его из поля владельца активности...
ActivityStartConfirm((A_t*)pA, THIS->plog, "RS232RX_t", "RX"); // подтверждаем запуск активности. Эти две строчки (с соответствующими аргументами) встречаются практически во всех активностях... Это - плата...
LOOP // while(1) :o)
{ Buffer_t* pb = THIS->free_buffers.Get(true); // берём незаполненный буфер. Аргумент true говорит, что нужно ждать пока не появится свободный буфер, а не возвращать NULL...
int err_code = THIS->RX(pb); // заполнякем его
if(err_code != EOK) // если была ошибка от аппаратуры,
Create232ErrorBuffer(pb, err_code); // то сформировать специальный буфер с информацией об ошибке rs232
THIS->filled_buffers.Add(pb); // передать только что заполненный буфер в список таковых
} // и начать всё заново
}
//-------------------------------------------------
void*
RS232RX_t::MsgTXA(void* pA)
{
DECL_THIS_(RS232RX_t*, ((A_t*)pA)->powner);
ActivityStartConfirm((A_t*)pA, THIS->plog, "RS232RX_t", "MsgTX");
LOOP
{
Buffer_t* pb = THIS->filled_buffers.Get(true);
if(THIS->pconsumer != NULL)
if((err_code = THIS->pconsumer->Accept(pb)) != EOK)
THIS->plog->Error("RS232RX_t::MsgTXA : ошибка (%d) при передаче буфера", err_code);
THIS->free_buffers.Add(pb);
}
}
Практически, потоки активностей rs232 большую часть своей жизни проводят в методах очередей буферов Get(), где ждут очередной буфер. Два потока введены для развязки по скорости порта rs232 и потребителя буферов.
Ниже приведены методы класса очереди буферов:
(Обратите внимание на методы Add() и Get(): там используется макрос X_RETURN_R(). Он необходим, потому, что нам нужно "выбираться из...", покидать EXCLUSIVE-блоки. Аналогичные макросы введены для continue, break, goto...
приведённый код взят из одной из первых реализаций.
Поле items первоначально было объявлено, как:
std::queue<Buffer_t*, std::list<Buffer_t*> > items;
сейчас код stl постепенно "вымывается" из модулей программы для снижения расходов по памяти и повышения быстродействия.
Например один из модулей программы, после переделки из варианта с STL на "естественные" средства, "похудел" со 130кб до 29кб, а его быстродействие возросло на порядок. Для нашего класса систем это очень существенно...)
//--------------------------------------------------
Buffers_t::Buffers_t(bool a_is_owner = false) : is_owner(a_is_owner) { }
//--------------------------------------------------------
Buffers_t::~Buffers_t()
{
if(is_owner)
while(!items.empty())
delete Get();
}
//------------------------------------------------------------------------------
int
Buffers_t::Add(Buffer_t* item)
{
X_BEGIN
try { items.push(item); }
catch(...) { X_RETURN_R(int, E_BUFFERS_ADD); }
X_RETURN_R(int, EOK);
X_END
}
//------------------------------------------------------------------------------
//если нет буферов, то сразу возвращать NULL или ждать пока в очереди не появится хотя бы один буфер?
Buffer_t*
Buffers_t::Get(bool wait_until_not_empty = false)
{
X_BEGIN
if(items.empty())
{ if(wait_until_not_empty)
X_AWAIT(!items.empty());
else
X_RETURN_R(Buffer_t*, NULL);
}
Buffer_t* item = items.front();
items.pop();
X_RETURN_R(Buffer_t*, item);
X_END
}
======================
Вот, собсна и всё.
Никаких упоминаний pthread и мьютексов с кондварами или пресловутых иных низкоуровневых средств... :о)
После переделки код "похудел" процентов на 40, стал прозрачнее и понятней, не нагружает мышление лишними сущностями. Код, как бы "поднялся" и "выровнялся" в пределах "предметки", снизилась его "понятийная амплитуда". Введение активных объектов позволило более естественным образом выражать в коде понятия предметной области (задача состоит в ОЧЕНЬ точном и оперативном управлении (в РВ!!!) железякой под 80 тонн + оперативное отображение в интерфейсе состояния объекта управления).
Противников и оппонентов применения Оберонов не должно вводить в заблуждение, что библиотека написана на (и для) Си++. За год толдычанья на семинарах приверженцы Си++, даже после "многосерийного" растолковывания семантики АО, так и не родили чего-нибудь внятного. Постоянно предлагались очередные "обёртки" над уже имеющимися в ОС механизмами многозадачности и синхронизации... Ничего нового, помимо дополнительной нагрузки на интеллект, они не вводили...
Уже просто таки захотелось наконец показать "что имелось в виду"...
Поневоле начинаешь верить словам Дейкстры о влиянии языка на мышление... :о)))