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

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

Кому-то - баян, кому-то, возможно, покажется достойным применения. Итак: некоторые библиотеки (боже, как бесит это слово!) работают в поле событийного интерфейса путём назначения коллбэков, простейший пример тому - 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. Что, кстати, даёт нам ещё один инструмент - обработка событий по умолчанию ;)

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

 

 

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 пишет:

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

)))

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

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 надо обязательно определить, иначе не компилируется, но в базовом он не определен, хотя и использован.

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

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 

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

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();
}

 

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

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

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

src\plugin.h 

#pragma once

extern "C" void userBlink();

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" для функций, которые описаны в аналогичных блоках заголовочного файла.