Событийное программирование при помощи "слабых" функций (позднего связывания)

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Кому-то - баян, кому-то, возможно, покажется достойным применения. Итак: некоторые библиотеки (боже, как бесит это слово!) работают в поле событийного интерфейса путём назначения коллбэков, простейший пример тому - Wire.onReceive(handler). Этот подход к реализации событий многим известен и многими практикуется, однако, наряду с достоинствами - у этого подхода есть и определённые недостатки, хотя бы тот факт, что указатель на функцию-обработчик надо где-то хранить (а это место в оперативной памяти).

В дополнение к этому - существует ещё один вполне себе годный подход, который применяется при объявлении той же функции yield, а именно - позднее связывание. Если совсем короче, то - любой функции можно назначить альяс (синоним, заглушку, если хотите), и если функция не определена - то на стадии линковки все вызовы такой функции заменятся на вызовы заглушки. Что нам это даёт? Как минимум - упрощение кода, вместо:

1if(onReceiveHandler) onReceiveHandler(bla, bla,bla);

Достаточно написать просто

1onReceiveHandler(bla,bla,bla);

Из ограничений, навскидку - тот факт, что таким образом реализованная событийность будет всегда ссылаться на одну функцию, вне зависимости от экземпляра класса. Но и это - не всегда минус, скажем так.

Как это работает и как это сделать? Всё просто - объявляете функцию:

1extern "C" {
2  void MY_RECEIVE(int bytesReceived);
3}

 

Потом - делаете её альяс:
1extern "C" {
2static void __nohandler(int dummy){}
3}

Потом - указываете, что функция - позднего связывания и имеет заглушку:

1void MY_RECEIVE(int) __attribute__ ((weak, alias("__nohandler")));

Всё - теперь вы в любом месте проекта сможете позвать MY_RECEIVE (например, с параметром 10 - MY_RECEIVE(10); ) - и не париться, куда попадёт вызов: если желающий принимать это событие определит функцию, а именно - напишет в исходном коде:

1void MY_RECEIVE(int bytesReceived)
2{
3Serial.print(F("bytes received: "));
4Serial.println(bytesReceived);
5}

то ваш вызов функции MY_RECEIVE  попадёт именно в реализованный подписчиком обработчик. Если же функции MY_RECEIVE нигде не определено - её вызов переадресуется на заглушку, а именно - на функцию __nohandler. Что, кстати, даёт нам ещё один инструмент - обработка событий по умолчанию ;)

Надеюсь, данная поверхностная заметка будет кому-либо полезна.

 

 

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

А я, дэбил, целую очередь сообщений напридумывал. А все так просто....  Спасибо, плюсую.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ща набегут! Ждите :))) Дет уже попробовал писать что-то общее, теперь вон просит тему снести, у меня тоже опыт был.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

ЕвгенийП пишет:

Ща набегут! Ждите :))) Дет уже попробовал писать что-то общее, теперь вон просит тему снести, у меня тоже опыт был.

Да пофик, собсно. Это я больше для себя, чтоб от маразму-то не забыть. Главное потом - вспомнить, где лежит это напоминание, а то и это забыть - невелико дело :) :) :)

Так шо пущай пинають :)

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Мне то чо, я на вечерний пузырь сёдня уже наразгружался ящегов. Но красивые решения оценить смогу.  Клапа не паймёть, а Логика бойтесь.  

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

ЕвгенийП пишет:

Ща набегут! Ждите :))) Дет уже попробовал писать что-то общее, теперь вон просит тему снести

Пока Вы спали мня Клапа убедил, что я полный кретин.  Он лучше знает. 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Вот только, что такое событийное программирование, и как правильно его организовать на Ардуине, не все знают. И чаще всего получается событийное программирование на костылях.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

qwone пишет:

Вот только, что такое событийное программирование, и как правильно его организовать на Ардуине, не все знают. И чаще всего получается событийное программирование на костылях.

Лично у меня нет цели - дать всем узнать. Тут всё проще - я просто разместил объяву :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

DetSimen пишет:

Пока Вы спали мня Клапа убедил, что я полный кретин. 

Значит, я в хорошей компании!

Надо нам с Вами по такому слуаю накатить за ... ну, хоть за денадцатый пин!

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

ЕвгенийП пишет:

Надо нам с Вами по такому слуаю накатить за ... ну, хоть за двенадцатый пин!

От такова я никада не отказывалса.

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

DetSimen пишет:

ЕвгенийП пишет:

Надо нам с Вами по такому слуаю накатить за ... ну, хоть за двенадцатый пин!

От такова я никада не отказывалса.

А jeka_tm давно уже всех звал собраться.

http://arduino.ru/forum/otvlechennye-temy/sobiraemsya-v-pivnushke

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

ну да, к чёрту подробности, город какой?

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

DetSimen пишет:

ну да, к чёрту подробности, город какой?

Москва.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Как бы это не странно для некоторых звучало , но событийное программирование полностью похоже на  программирование с помощью прерываний. И там и там используются обработчики. Но для событийного можно делать не только аппаратное прерывание, но и программное.

Logik
Offline
Зарегистрирован: 05.08.2014

DetSimen пишет:

  Клапа не паймёть, а Логика бойтесь.  

)))

Это атрибут функции, так понимаю, из свежих расширений языка? Любопытно, никогда такого не встречал на практике.  Но не думаю что сильно полезно, в ООП методы при наследовании аналогично позволяют реализовать.  И если не требуется иметь совсем чистый код на Си, то проще (и сильно гибче ) делать класс. Ну к примеру в либке базовый класс (надергаю немного из подвернувшегося кода, его Клапа сильно мечтал видеть ;)

01class BaseKBD
02{
03    virtual void SetStrob(byte n, byte s);
04 
05protected:
06  byte Strob;
07  void SelectLongPress(byte evnt, byte key){KeyEvent(evnt, key); }
08 
09public:
10......
11   
12  byte Process()
13  {
14        
15    SetStrob(Strob, HIGH);
16    Strob++;
17    if(Strob>StrobCount)
18     Strob=0;
19    SetStrob(Strob, LOW);
20     
21     
22    SelectLongPress(0,1);
23  }
24};

А в скетче наследуем класс от библиотечного

1class KBD : public BaseKBD
2{
3 // работаем напрямую с портом
4  void SetStrob(byte n, byte s)  { s?sbi(PORTC,n):cbi(PORTC,n);         }
5..............
6};

Получим что SelectLongPress можна свою задать, а можна и нет. А SetStrob надо обязательно определить, иначе не компилируется, но в базовом он не определен, хотя и использован.

ПС. Такое ощущение, что учу рыбу плавать. Кто сюда заглянет - и так знают это, а кто не знает - в эту тему и не глянет )))

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Logik пишет:
ПС. Такое ощущение, что учу рыбу плавать. Кто сюда заглянет - и так знают это, а кто не знает - в эту тему и не глянет )))
Ну ТС сказал, что это скорее так как записная книжка. Да и атрибуты они скорее для чистых сишников, которые ковыряюся в микроконтроллерах. Для нормально спроектированой программы это костыль.

Logik
Offline
Зарегистрирован: 05.08.2014

Не, не костыль, просто фича. И еще один аргумент за то что на Си можна сделать все что и на плюсах. Нынче в контролерах не только чистые сишники колупаются, но и нечистые ;)

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

нихрена не понял, но чую, что СИЁ можно использовать как обработчик ошибок )))

sadman41
Offline
Зарегистрирован: 19.10.2016

Прилеплюсь со своим вариантом, который кажется мне попроще в понимании.

В src\plugin.* расположен код заглушек функций, в plugin.ino -  перекрывающий их код. Захотели свою плагин-функцию сделать - написали и подкинули plugin.ino к основному файлу проекта, разонравилась функцональность - стерли или заменили на другой. Планирую использовать эту штуку для сетевого агента, к которому пользователь может добавить свою небольшую функциональность (отбить SOS, например), не залезая в основной код. За идею - благодарю.

src\plugin.h 

1#pragma once
2void __attribute__((weak)) blinkLed();

src\plugin.cpp

1#include <Arduino.h>
2 
3void __attribute__((weak)) blinkLed() {
4  digitalWrite(13, HIGH);
5  delay(100);
6  digitalWrite(13, LOW);
7  delay(100);
8}

plugin.ino 

1void blinkLed(void) {
2  digitalWrite(13, HIGH);
3  delay(1000);
4  digitalWrite(13, LOW);
5  delay(1000);
6}

plugin_test.ino 

1#include "src/plugin.h"
2 
3void setup() {
4  pinMode(13, OUTPUT);
5}
6 
7void loop() {
8  blinkLed();
9}

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Перекомпиливал старый код в новом Arduino IDE 1.8.9 и обнаружил, что мой простой способ (тот, что выше) более не хочет работать. 

Случайным образом (как те сто тыщ обезъян) сгенерировал новый пример (из plugin.cpp и plugin.c необходимо выбрать только один).

src\plugin.h 

1#pragma once
2 
3extern "C" void userBlink();

src\plugin.c

01#include <Arduino.h>
02 
03static void __defaultBlink() {
04  digitalWrite(13, HIGH);
05  delay(100);
06  digitalWrite(13, LOW);
07  delay(100);
08 
09}
10 
11void userBlink() __attribute__ ((weak, alias("__defaultBlink")));

src\plugin.cpp

01#include <Arduino.h>
02 
03extern "C" {
04  void userBlink(int) __attribute__ ((weak, alias("__defaultBlink")));
05  static void __defaultBlink();
06}
07 
08static void __defaultBlink() {
09  digitalWrite(13, HIGH);
10  delay(100);
11  digitalWrite(13, LOW);
12  delay(100);
13}

plugin.ino 

1// userBlink() function can be omitted, and defaultBlink() will be used
2void userBlink() {
3  digitalWrite(13, HIGH);
4  delay(1000);
5  digitalWrite(13, LOW);
6  delay(1000);
7}

plugin_test.ino 

1#include "src/plugin.h"
2 
3void setup() {
4  pinMode(13, OUTPUT);
5}
6 
7void loop() {
8  userBlink();
9}

Если кто-то расскажет, почему в arduino\hardware\arduino\avr\cores\arduino\hooks.c связывание и реализация прокатывает без extern "C", а у меня - нет (хотя я уже докатился до прямой копипасты оттуда и переименовывания своего plugin.cpp в plugin.c) - облагодарю "поклоном до земли (R)" 

P.S. с 10050-го раза IDE сдалось копипасте, пример подправил. Файл реализации должен или иметь расширение .c или иметь внутри себя блоки extern "C" для функций, которые описаны в аналогичных блоках заголовочного файла.