Обратные вызовы в C++ - Виталий Евгеньевич Ткаченко 31 стр.


Итак, класс для анализа готов. Теперь можно вызвать метод для итерации по элементам контейнера, и в качестве обратного вызова передать экземпляр соответствующего вспомогательного класса. Метод будет вызывать перегруженный оператор, и таким образом, мы узнаем минимальное либо максимальное значение (Листинг 101).

Листинг 101. Поиск минимального и максимального значений (SensorControl.cpp)

SensorValue SensorControl::getMinValue(SensorNumber first, SensorNumber last)

{

  checkInitialize();


  FindMinMaxValue fmv(first, last, FindMinMaxValue::MIN_VALUE);

  sensorContainer_->forEachSensor(fmv);

  return fmv.result();

}


SensorValue SensorControl::getMaxValue(SensorNumber first, SensorNumber last)

{

  checkInitialize();


  FindMinMaxValue fmv(first, last, FindMinMaxValue::MAX_VALUE);

  sensorContainer_->forEachSensor(fmv);

  return fmv.result();

}

6.3. Разработка системного API

6.3.1. API как оболочка

Уже после того, как классы модуля были разработаны, протестированы и начали использоваться в системе, появилось новое требование ввести поддержку системного API. Как известно, в интерфейсах системных API можно использовать только внешние функции и простые структуры данных в стиле C; классы и другие специфические конструкции C++ использовать нельзя (см. п. 1.4.2). Так что же, все теперь придется переписывать? Можно предложить следующее решение: использовать интерфейс API как оболочку для вызова методов класса. Концептуальный пример приведен в Листинг 102.

Листинг 102. Концептуальный пример реализации API как оболочки

using ControlPointer = std::unique_ptr<sensor::ISensorControl>;

ControlPointer g_SensorControl(sensor::ISensorControl::createControl());


void initialize () // This function is declared in the header file as part of API interface

{

  g_SensorControl->initialize();

}


Однако не все так просто, перед нами встают следующие проблемы.

1. В исходной реализации мы использовали специфические типы C++, такие, как std::function, smart pointers и т. п., что не допускается в интерфейсах системных API. Какие типы использовать взамен?

2. Для обработки ошибок в исходной реализации мы использовали исключения. Как сейчас обрабатывать ошибки, ведь в интерфейсах API исключения недопустимы?

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

4. В исходной реализации драйвер настраивался путем создания нового класса и передаче его в интерфейсный класс. Как теперь настраивать драйвер, если в интерфейсах API нельзя использовать классы?

5. Как организовать обратные вызовы?


Рассмотрим, как эти проблемы можно решить.

6.3.2. Объявления типов

В исходной реализации общие типы объявлены в SensorDef.h, но мы не можем просто перенести их в интерфейс API из-за использования специфических конструкций С++. Поэтому нам придется повторить эти объявления в стиле C с использованием простых типов, которые можно будет использовать в интерфейсных функциях. Объявления представлены в Листинг 103.

Листинг 103. Объявления типов для интерфейса API (SensorLib.h)

#ifdef _WINDOWS  // (1)

  #ifdef LIB_EXPORTS

    #define LIB_API __declspec(dllexport)

  #else

    #define LIB_API __declspec(dllimport)

  #endif

  #else

    #define LIB_API

#endif


typedef uint32_t SensorNumber;       // (2)

typedef double SensorValue;          // (3)

typedef uint32_t CheckAlertTimeout;  // (4)


typedef uint32_t SensorType;         // (5)

typedef uint32_t DriverType;         // (6)

typedef uint32_t AlertRule;          // (7)


typedef void(*SensorValueCallback)(SensorNumber, SensorValue, void*);               // (8)

typedef CheckAlertTimeout(*SensorAlertCallback)(SensorNumber, SensorValue, void*);  // (9)

typedef SensorValue(*OnSimulateReadValue)(SensorNumber, int, void*);                // (10)

typedef int (*OnSimulateOperable)(SensorNumber, void*);                             // (11)


enum eSensorType  // (12)

{

  SENSOR_SPOT = 0,

  SENSOR_SMOOTH = 1,

  SENSOR_DERIVATIVE = 2,

};


enum eDriverType  // (13)

{

  DRIVER_SIMULATION = 0,

  DRIVER_USB = 1,

  DRIVER_ETHERNET = 2

};


enum  eAlertRule  // (14)

{

  ALERT_MORE = 0,

  ALERT_LESS = 1

{

  DRIVER_SIMULATION = 0,

  DRIVER_USB = 1,

  DRIVER_ETHERNET = 2

};


enum  eAlertRule  // (14)

{

  ALERT_MORE = 0,

  ALERT_LESS = 1

};


В строке 1 объявлены определения для экспортируемых функций. Эти объявления необходимы для компиляции динамической библиотеки в среде Windows, для других платформ они неактуальны.

В строках 24 объявлены типы, которые будут использоваться для входных параметров интерфейсных функций. Это те же объявления, которые использовались в исходной реализации (SensorDef.h, см. п. 6.2.2).

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

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

6.3.3. Интерфейс API и обработка ошибок

Исходя из концепции «API как оболочка», сигнатура интерфейсных функций API должна повторять сигнатуру методов интерфейсного класса. Однако здесь мы сталкиваемся с некоторыми проблемами, одна из которых это обработка ошибок.

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

1) функция возвращает результат, для которого некоторое предопределенное значение говорит о том, что произошла ошибка. Код ошибки возвращается с помощью отдельного вызова;

2) код ошибки возвращается через дополнительный параметр функции;

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

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

В нашем случае мы выберем третий способ, исходя из следующих соображений: объявления функций будут выглядеть единообразно; возникновение ошибки можно узнать непосредственно в момент вызова, (например, в операторе if); если функция не возвращает значений, то ей не нужно передавать никакие дополнительные параметры. Объявления интерфейсных функций с возвратом ошибок представлены в Листинг 104.

Листинг 104. Интерфейс системного API (SensorLib.h)

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).

Листинг 105. Функция для получения значения датчика

ErrorCode getSensorValue(SensorNumber number, SensorValue* value)

Назад Дальше