Событийное программирование при помощи "слабых" функций (позднего связывания)
- Войдите на сайт для отправки комментариев
Кому-то - баян, кому-то, возможно, покажется достойным применения. Итак: некоторые библиотеки (боже, как бесит это слово!) работают в поле событийного интерфейса путём назначения коллбэков, простейший пример тому - Wire.onReceive(handler). Этот подход к реализации событий многим известен и многими практикуется, однако, наряду с достоинствами - у этого подхода есть и определённые недостатки, хотя бы тот факт, что указатель на функцию-обработчик надо где-то хранить (а это место в оперативной памяти).
В дополнение к этому - существует ещё один вполне себе годный подход, который применяется при объявлении той же функции yield, а именно - позднее связывание. Если совсем короче, то - любой функции можно назначить альяс (синоним, заглушку, если хотите), и если функция не определена - то на стадии линковки все вызовы такой функции заменятся на вызовы заглушки. Что нам это даёт? Как минимум - упрощение кода, вместо:
if(onReceiveHandler) onReceiveHandler(bla, bla,bla);
Достаточно написать просто
onReceiveHandler(bla,bla,bla);
Из ограничений, навскидку - тот факт, что таким образом реализованная событийность будет всегда ссылаться на одну функцию, вне зависимости от экземпляра класса. Но и это - не всегда минус, скажем так.
Как это работает и как это сделать? Всё просто - объявляете функцию:
extern "C" {
void MY_RECEIVE(int bytesReceived);
}
extern "C" {
static void __nohandler(int dummy){}
}Потом - указываете, что функция - позднего связывания и имеет заглушку:
void MY_RECEIVE(int) __attribute__ ((weak, alias("__nohandler")));
Всё - теперь вы в любом месте проекта сможете позвать MY_RECEIVE (например, с параметром 10 - MY_RECEIVE(10); ) - и не париться, куда попадёт вызов: если желающий принимать это событие определит функцию, а именно - напишет в исходном коде:
void MY_RECEIVE(int bytesReceived)
{
Serial.print(F("bytes received: "));
Serial.println(bytesReceived);
}то ваш вызов функции MY_RECEIVE попадёт именно в реализованный подписчиком обработчик. Если же функции MY_RECEIVE нигде не определено - её вызов переадресуется на заглушку, а именно - на функцию __nohandler. Что, кстати, даёт нам ещё один инструмент - обработка событий по умолчанию ;)
Надеюсь, данная поверхностная заметка будет кому-либо полезна.
А я, дэбил, целую очередь сообщений напридумывал. А все так просто.... Спасибо, плюсую.
Ща набегут! Ждите :))) Дет уже попробовал писать что-то общее, теперь вон просит тему снести, у меня тоже опыт был.
Ща набегут! Ждите :))) Дет уже попробовал писать что-то общее, теперь вон просит тему снести, у меня тоже опыт был.
Да пофик, собсно. Это я больше для себя, чтоб от маразму-то не забыть. Главное потом - вспомнить, где лежит это напоминание, а то и это забыть - невелико дело :) :) :)
Так шо пущай пинають :)
Мне то чо, я на вечерний пузырь сёдня уже наразгружался ящегов. Но красивые решения оценить смогу. Клапа не паймёть, а Логика бойтесь.
Ща набегут! Ждите :))) Дет уже попробовал писать что-то общее, теперь вон просит тему снести
Пока Вы спали мня Клапа убедил, что я полный кретин. Он лучше знает.
Вот только, что такое событийное программирование, и как правильно его организовать на Ардуине, не все знают. И чаще всего получается событийное программирование на костылях.
Вот только, что такое событийное программирование, и как правильно его организовать на Ардуине, не все знают. И чаще всего получается событийное программирование на костылях.
Лично у меня нет цели - дать всем узнать. Тут всё проще - я просто разместил объяву :)
Пока Вы спали мня Клапа убедил, что я полный кретин.
Значит, я в хорошей компании!
Надо нам с Вами по такому слуаю накатить за ... ну, хоть за денадцатый пин!
Надо нам с Вами по такому слуаю накатить за ... ну, хоть за двенадцатый пин!
От такова я никада не отказывалса.
Надо нам с Вами по такому слуаю накатить за ... ну, хоть за двенадцатый пин!
От такова я никада не отказывалса.
А jeka_tm давно уже всех звал собраться.
http://arduino.ru/forum/otvlechennye-temy/sobiraemsya-v-pivnushke
ну да, к чёрту подробности, город какой?
ну да, к чёрту подробности, город какой?
Москва.
Как бы это не странно для некоторых звучало , но событийное программирование полностью похоже на программирование с помощью прерываний. И там и там используются обработчики. Но для событийного можно делать не только аппаратное прерывание, но и программное.
Клапа не паймёть, а Логика бойтесь.
)))
Это атрибут функции, так понимаю, из свежих расширений языка? Любопытно, никогда такого не встречал на практике. Но не думаю что сильно полезно, в ООП методы при наследовании аналогично позволяют реализовать. И если не требуется иметь совсем чистый код на Си, то проще (и сильно гибче ) делать класс. Ну к примеру в либке базовый класс (надергаю немного из подвернувшегося кода, его Клапа сильно мечтал видеть ;)
class BaseKBD { virtual void SetStrob(byte n, byte s); protected: byte Strob; void SelectLongPress(byte evnt, byte key){KeyEvent(evnt, key); } public: ...... byte Process() { SetStrob(Strob, HIGH); Strob++; if(Strob>StrobCount) Strob=0; SetStrob(Strob, LOW); SelectLongPress(0,1); } };А в скетче наследуем класс от библиотечного
class KBD : public BaseKBD { // работаем напрямую с портом void SetStrob(byte n, byte s) { s?sbi(PORTC,n):cbi(PORTC,n); } .............. };Получим что SelectLongPress можна свою задать, а можна и нет. А SetStrob надо обязательно определить, иначе не компилируется, но в базовом он не определен, хотя и использован.
ПС. Такое ощущение, что учу рыбу плавать. Кто сюда заглянет - и так знают это, а кто не знает - в эту тему и не глянет )))
Не, не костыль, просто фича. И еще один аргумент за то что на Си можна сделать все что и на плюсах. Нынче в контролерах не только чистые сишники колупаются, но и нечистые ;)
нихрена не понял, но чую, что СИЁ можно использовать как обработчик ошибок )))
Прилеплюсь со своим вариантом, который кажется мне попроще в понимании.
В src\plugin.* расположен код заглушек функций, в plugin.ino - перекрывающий их код. Захотели свою плагин-функцию сделать - написали и подкинули plugin.ino к основному файлу проекта, разонравилась функцональность - стерли или заменили на другой. Планирую использовать эту штуку для сетевого агента, к которому пользователь может добавить свою небольшую функциональность (отбить SOS, например), не залезая в основной код. За идею - благодарю.
src\plugin.h
src\plugin.cpp
#include <Arduino.h> void __attribute__((weak)) blinkLed() { digitalWrite(13, HIGH); delay(100); digitalWrite(13, LOW); delay(100); }plugin.ino
void blinkLed(void) { digitalWrite(13, HIGH); delay(1000); digitalWrite(13, LOW); delay(1000); }plugin_test.ino
#include "src/plugin.h" void setup() { pinMode(13, OUTPUT); } void loop() { blinkLed(); }Перекомпиливал старый код в новом Arduino IDE 1.8.9 и обнаружил, что мой простой способ (тот, что выше) более не хочет работать.
Случайным образом (как те сто тыщ обезъян) сгенерировал новый пример (из plugin.cpp и plugin.c необходимо выбрать только один).
src\plugin.h
src\plugin.c
#include <Arduino.h> static void __defaultBlink() { digitalWrite(13, HIGH); delay(100); digitalWrite(13, LOW); delay(100); } void userBlink() __attribute__ ((weak, alias("__defaultBlink")));src\plugin.cpp
#include <Arduino.h> extern "C" { void userBlink(int) __attribute__ ((weak, alias("__defaultBlink"))); static void __defaultBlink(); } static void __defaultBlink() { digitalWrite(13, HIGH); delay(100); digitalWrite(13, LOW); delay(100); }plugin.ino
// userBlink() function can be omitted, and defaultBlink() will be used void userBlink() { digitalWrite(13, HIGH); delay(1000); digitalWrite(13, LOW); delay(1000); }plugin_test.ino
#include "src/plugin.h" void setup() { pinMode(13, OUTPUT); } void loop() { userBlink(); }Если кто-то расскажет, почему в arduino\hardware\arduino\avr\cores\arduino\hooks.c связывание и реализация прокатывает без extern "C", а у меня - нет (хотя я уже докатился до прямой копипасты оттуда и переименовывания своего plugin.cpp в plugin.c) - облагодарю "поклоном до земли (R)"
P.S. с 10050-го раза IDE сдалось копипасте, пример подправил. Файл реализации должен или иметь расширение .c или иметь внутри себя блоки extern "C" для функций, которые описаны в аналогичных блоках заголовочного файла.