Виталий Ткаченко - Обратные вызовы в C++
- Название:Обратные вызовы в C++
- Автор:
- Жанр:
- Издательство:Array SelfPub.ru
- Год:2021
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Виталий Ткаченко - Обратные вызовы в C++ краткое содержание
Обратные вызовы в C++ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
{
ALERT_MORE = 0,
ALERT_LESS = 1
};
В строке 1 объявлены определения для экспортируемых функций. Эти объявления необходимы для компиляции динамической библиотеки в среде Windows, для других платформ они неактуальны.
В строках 2–4 объявлены типы, которые будут использоваться для входных параметров интерфейсных функций. Это те же объявления, которые использовались в исходной реализации (SensorDef.h, см. п. 6.2.2).
В строках 5–7 вместо перечислений C++ объявляются простые числовые типы. В экспортируемых функциях нежелательно использовать перечисления как типы входных параметров, потому что размер этих типов в C явно не определен. Вместо этого перечисления используются в качестве числовые констант, они объявлены соответственно в строках 12–14.
В строках 8–11 объявлены типы указателей на функцию для выполнения обратных вызовов. Как видим, в отличие от исходной реализации здесь присутствует дополнительный параметр для указания контекста вызова.
6.3.3. Интерфейс API и обработка ошибок
Исходя из концепции «API как оболочка», сигнатура интерфейсных функций API должна повторять сигнатуру методов интерфейсного класса. Однако здесь мы сталкиваемся с некоторыми проблемами, одна из которых – это обработка ошибок.
В исходной реализации мы обрабатывали ошибки с помощью исключений. Теперь исключения использовать нельзя, в системных API они недопустимы. Тем не менее, вызываемая функция должна как-то уведомить о возникновении ошибки, для чего могут использоваться следующие способы:
1) функция возвращает результат, для которого некоторое предопределенное значение говорит о том, что произошла ошибка. Код ошибки возвращается с помощью отдельного вызова;
2) код ошибки возвращается через дополнительный параметр функции;
3) все функции возвращают результат выполнения, который является кодом ошибки.
Ни один способов не является идеальным, каждый имеет свои достоинства и недостатки. Так, в первом способе возникают сложности, если результат, возвращаемый функцией, не имеет значений, которые недопустимы и могут сигнализировать о возникновении ошибки 36 36 Например, функция возвращает только положительные значения, в этом случае можно считать, что отрицательное значение сигнализирует о возникновении ошибки. Но если функция может возвращать значения с любым знаком, то неясно, какое из них назначить индикатором ошибки.
. Во втором способе для всех вызовов придется использовать дополнительную переменную – код ошибки, даже если он нас не интересует. В третьем способе, если функция возвращает результат, то для него приходится использовать отдельный входной параметр, что не всегда удобно.
В нашем случае мы выберем третий способ, исходя из следующих соображений: объявления функций будут выглядеть единообразно; возникновение ошибки можно узнать непосредственно в момент вызова, (например, в операторе if); если функция не возвращает значений, то ей не нужно передавать никакие дополнительные параметры. Объявления интерфейсных функций с возвратом ошибок представлены в Листинг 104.
typedef unsigned int ErrorCode;
LIB_API ErrorCode initialize();
LIB_API ErrorCode shutDown();
LIB_API ErrorCode assignDriver(DriverType type);
LIB_API ErrorCode getAssignedDriver(DriverType* type);
LIB_API ErrorCode getSensorDriver(SensorNumber number, DriverType* type);
LIB_API ErrorCode addSensor(SensorType type, SensorNumber number);
LIB_API ErrorCode deleteSensor(SensorNumber number);
LIB_API ErrorCode isSensorExist(SensorNumber number, int* isExist);
LIB_API ErrorCode isSensorOperable(SensorNumber number, int* isOperable);
LIB_API ErrorCode getSensorValue(SensorNumber number, SensorValue* value);
LIB_API ErrorCode querySensorValue(SensorNumber number, SensorValueCallback callback, void* pContextData);
LIB_API ErrorCode readSensorValues(SensorValueCallback callback, void* pContextData);
LIB_API ErrorCode getMinValue(SensorNumber first, SensorNumber last, SensorValue* value);
LIB_API ErrorCode getMaxValue(SensorNumber first, SensorNumber last, SensorValue* value);
LIB_API ErrorCode setAlert(SensorNumber number, SensorAlertCallback callback, SensorValue alertValue, AlertRule alertRule, CheckAlertTimeout checkTimeoutSeс, void* pContextData);
LIB_API ErrorCode resetAlert(SensorNumber number);
LIB_API ErrorCode setSimulateReadCallback(OnSimulateReadValue callback, void* pContextData);
LIB_API ErrorCode setSimulateOperableCallback(OnSimulateOperable callback, void* pContextData);
В реализации этих функций мы будем возвращать код ошибки, получая его из перехваченного исключения. В качестве примера рассмотрим реализацию функции для получения значения датчика (Листинг 105).
ErrorCode getSensorValue(SensorNumber number, SensorValue* value)
{
ErrorCode error = ERROR_NO; // (1)
try
{
*value = g_SensorControl->getSensorValue(number); // (2)
}
catch (sensor::sensor_exception& e) // (3)
{
error = e.code(); // (4)
}
return error; // (5)
}
В строке 1 объявляем переменную – код возврата. В строке 2 осуществляем вызов метода класса, который заключен в блок try. В строке 3 осуществляется перехват исключения, в строке 4 присваивается код ошибки, который возвращается в строке 5.
Итак, мы придумали, как в интерфейсных функциях осуществлять обработку ошибок. Теперь перед нами встает следующая проблема: как настраивать типы драйверов, ведь в исходной реализации для этого используются классы? Прежде чем перейти к решению этой задачи, остановимся на реализации многопоточной работы, поскольку используемые там конструкции нам понадобятся в дальнейшем.
6.3.4. Многопоточная работа
В исходной реализации в каждом потоке мы могли создать свой экземпляр класса ISensorControlи работать с ним независимо. В случае API это не работает, потому что экземпляр класса в реализации интерфейса объявляется глобальным, и все интерфейсные функции обращаются к одному и тому же экземпляру класса. Выходом здесь будет выделение отдельной области памяти для экземпляра класса в рамках одного потока, т. е. использование локальной памяти потока.
До появления стандарта C++ 11 использовать локальную память потока было непросто: для этого требовалось явное обращение к функциям операционной системы, что усложняло реализацию и делало код платформенно-зависимым. В C++ 11 появилось ключевое слово thread_local, и это сильно упростило жизнь: если в объявлении переменной добавить указанный спецификатор, то она становится локальной в рамках потока, т. е. каждый новый создаваемый поток будет иметь независимый экземпляр соответствующей переменной. Таким образом, достаточно экземпляр интерфейсного класса ISensorControlобъявить как thread_local, и теперь для каждого потока будет существовать отдельный независимый экземпляр класса (Листинг 106).
using ControlPointer = std::unique_ptr;
Интервал:
Закладка: