Рефлексия в Си или вызов функции по имени в переменной

JonHappy1
Offline
Зарегистрирован: 11.06.2018

как это делается в Си для ардуино?

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

Из до-диеза пришёл, видать? :) Нету reflection в С как класс, нету. Делать надо ручками, типа такого (и то - это будет не совсем кошерный reflection):

typedef void (*PFunc)(void);

typedef struct
{
	const char* mnemonicName;
	PFunc handler;
} Reflect;

void func1()
{
	Serial.println("func1");
}
void func2()
{
	Serial.println("func2");
}

Reflect reflections[] = {
	{"func1", func1},
	{"func1", func2},
};


void call(const char* funcName)
{
	size_t sz = sizeof(reflections)/sizeof(reflections[0]);
	for(size_t i=0;i<sz;i++)
	{
		if(!strcmp(reflections[i].mnemonicName,funcName) && reflections[i].handler)
		{
			reflections[i].handler();
			break;
		}
	}
}

Изложен только принцип, не более того. Без претензий на уникальность и вообще.

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

Подпишусь ка на тему. Всяко интересно почитать чего напишут рефлексирующие С'онисты :)

JonHappy1
Offline
Зарегистрирован: 11.06.2018

пришёл из java.
в  си буквально неделю. поэтому не сочти за нахальство и
либо прокомментируй более доходчиво или где почитать
вот про эти куски

typedef void (*PFunc)(void);

typedef struct
{
	const char* mnemonicName;
	PFunc handler;
} Reflect;


{
	reflections[i].handler();
			
}

насколько я понял сам вызов это  reflections[i].handler();

всё остальное  подготовка
а как передать параметры?

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

A я думал, что ТС крышей поехал, в рефлексию ударился. Да и код #1 немного PROGMEM добавить надо. Ну нет столько памяти для нормально упасть в рефлексию.

b707
Offline
Зарегистрирован: 26.05.2017

JonHappy1 пишет:

как это делается в Си для ардуино?

Джон, ерундой не майтесь. Рефлексия в Си - это помесь ежа с ужом, в реальных программах она не живет.

Опишите задачу = может другие варианты есть? Обязательно ли функцию по имени вызывать, может по ссылке подойдет? ссылки на функции в Си можно хранить в переменных, складывать в массивы, передавать в другие функции и тд.

JonHappy1
Offline
Зарегистрирован: 11.06.2018

qwone пишет:

A я думал, что ТС крышей поехал, в рефлексию ударился. Да и код #1 немного PROGMEM добавить надо. Ну нет столько памяти для нормально упасть в рефлексию.

PROGMEM это да.
пока проверить работоспособность можно и без PROGMEM.

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

JonHappy1 пишет:
пока проверить работоспособность можно и без PROGMEM.
А что там ее проверять загрузитьи готово

/**/
typedef void (*PFunc)(void);// тип указатель на функцию

typedef struct { // структура
  const char* mnemonicName;
  PFunc handler;
} Reflect;
void func1() {
  Serial.println("func1");
}
void func2()
{
  Serial.println("func2");
}

Reflect reflections[] = {
  {"func1", func1},
  {"func2", func2},
};

void call(const char* funcName) { // вызов функцию по имени
  size_t sz = sizeof(reflections) / sizeof(reflections[0]);
  for (size_t i = 0; i < sz; i++)
  {
    if (!strcmp(reflections[i].mnemonicName, funcName) && reflections[i].handler)
    {
      reflections[i].handler();
      break;
    }
  }
}



//---------------------------------------------
void setup() {
  Serial.begin(9600);
  call("func1");
  call("func2");
}

void loop() {
}

 

JonHappy1
Offline
Зарегистрирован: 11.06.2018

b707 пишет:

Джон, ерундой не майтесь. Рефлексия в Си - это помесь ежа с ужом, в реальных программах она не живет.

Опишите задачу = может другие варианты есть? Обязательно ли функцию по имени вызывать, может по ссылке подойдет? ссылки на функции в Си можно хранить в переменных, складывать в массивы, передавать в другие функции и тд.

есть связь с сервером по ws. приходит сообщение вида xxx03|лалала
где
xxx03 - это имя функции, которую надо выполнить
лалала это данные для выполнения

таких функций множество, они буквально отличаются только номером в имени

в java это делается просто.

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

Дак джава ваша и ворочается еле-еле из-за всех этих рефлексий и прочего непотребства. Вы бы еще на остро наточенном острие ассемблера искали какой-нить ООП.

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

JonHappy1 пишет:
в java это делается просто.
А за деньги вообще быстро. Может Ардуина не ваша путь.

ПС:

/**/
typedef void (*PFunc)(void);// тип указатель на функцию
enum {cFunc1 = 0, cFunc2};
typedef struct { // структура
  const byte num;
  PFunc handler;
} Reflect;

void func1() {
  Serial.println("func1");
}
void func2()
{
  Serial.println("func2");
}

Reflect reflections[] = {
  {cFunc1, func1},
  {cFunc2, func2},
};

void call(const byte num) { // вызов функцию по имени
  size_t sz = sizeof(reflections) / sizeof(reflections[0]);
  for (size_t i = 0; i < sz; i++) {
    if (reflections[i].num == num && reflections[i].handler) {
      reflections[i].handler();
      break;
    }
  }
}
//---------------------------------------------
void setup() {
  Serial.begin(9600);
  call(cFunc1);
  call(cFunc2);
}

void loop() {
}
/*Скетч использует 1524 байт (4%) памяти устройства. Всего доступно 30720 байт.
  Глобальные переменные используют 198 байт (9%) динамической памяти, оставляя 1850 байт для локальных переменных. Максимум: 2048 байт.
*/

 

JonHappy1
Offline
Зарегистрирован: 11.06.2018

sadman41 пишет:

Дак джава ваша и ворочается еле-еле из-за всех этих рефлексий и прочего непотребства. Вы бы еще на остро наточенном острие ассемблера искали какой-нить ООП.

это ты про рефлексию 15летней давности говоришь. счас она быстрая. а и сама java счас очень быстрая, с ассемблером кноечно не сравнить, но всё же.

b707
Offline
Зарегистрирован: 26.05.2017

JonHappy1 пишет:

xxx03 - это имя функции, которую надо выполнить
лалала это данные для выполнения

таких функций множество, они буквально отличаются только номером в имени

в java это делается просто.

а что-то простое типа,

if (funcname == xxxx03) Func3(лалала);

не прокатит? :)

Если кажется горомоздко под каждую функцию свой IF заводить - то тогда организовать ссылки на функции в массив, индексированный по номеру в имени xxx03

JonHappy1
Offline
Зарегистрирован: 11.06.2018

2qwone
во втором варианте где переменная содержащая имя функции?

b707
Offline
Зарегистрирован: 26.05.2017

JonHappy1 пишет:

2qwone
во втором варианте где переменная содержащая имя функции?

она там не нужна

Отвыкайте от явы. Ява - суксь :)

JonHappy1
Offline
Зарегистрирован: 11.06.2018

2b707

if и case конечно можно , но это как-то не комильфо
 

а вот

организовать ссылки на функции в массив, индексированный по номеру в имени xxx03

может и будет нормально, вот только на такое я пока не способен, подскажи как это делается

b707
Offline
Зарегистрирован: 26.05.2017

JonHappy1 пишет:

организовать ссылки на функции в массив, индексированный по номеру в имени xxx03

может и будет нормально, вот только на такое я пока не способен, подскажи как это делается

см у qwone - он уже меня опередил и даже код написал - у него там как раз массив из указателей на функции, индексированный по номеру в твоем параметре ххх03.

JonHappy1
Offline
Зарегистрирован: 11.06.2018

b707 пишет:

JonHappy1 пишет:

2qwone
во втором варианте где переменная содержащая имя функции?

она там не нужна

Отвыкайте от явы. Ява - суксь :)

на ардуинках портал сделать :)

у меня сервер на java.

если у меня имя функции в char[5] как мне её вызвать?

т.е. cFunc1 это номер функции?

b707
Offline
Зарегистрирован: 26.05.2017

JonHappy1 пишет:

если у меня имя функции в char[5] как мне её вызвать?

В ОБЩЕМ СЛУЧАЕ НИКАК.

Затянуть правое ухо под левое колено, наверно, можно, но стоит ли?

 

JonHappy1
Offline
Зарегистрирован: 11.06.2018

тут

enum {cFunc1 = 0, cFunc2};


всё правильно?

b707
Offline
Зарегистрирован: 26.05.2017

JonHappy1 пишет:

т.е. cFunc1 это номер функции?

В коде Квона? cFunc1 - это число, которым заказчивается имя функции, например для  xxx03 это "3"

JonHappy1
Offline
Зарегистрирован: 11.06.2018

b707 пишет:

JonHappy1 пишет:

если у меня имя функции в char[5] как мне её вызвать?

В ОБЩЕМ СЛУЧАЕ НИКАК.

Затянуть правое ухо под левое колено, наверно, можно, но стоит ли?

 

а тут

void setup() {
	  Serial.begin(9600);

	  call("func1");

	  call("func2");

	}

разве нельзя заменить стрку на имя переменной?

b707
Offline
Зарегистрирован: 26.05.2017

JonHappy1 пишет:

а тут

void setup() {
	  Serial.begin(9600);

	  call("func1");

	  call("func2");

	}

разве нельзя заменить стрку на имя переменной?

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

Это ведь не прямой вызов функции по имени, разве вы не видите? Здесь строка "func1" не более чем метка, по которой в заранее составленном массиве ищется указатель на нужную функцию. В принципе, это тоже самое, как массив ссылок по номеру, только искать геморройнее.

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

Нельзя. Компилятор не сохраняет имена функций, он оперирует их адресами.

JonHappy1
Offline
Зарегистрирован: 11.06.2018
JonHappy1
Offline
Зарегистрирован: 11.06.2018

sadman41 пишет:

 Компилятор не сохраняет имена функций, он оперирует их адресами.

самое ценное замечание, просто и очень доходчиво.

b707
Offline
Зарегистрирован: 26.05.2017

JonHappy1 пишет:

вот тут http://forum.arduino.cc/index.php?topic=40842.msg298098#msg298098

вроде проще

ровно то же самое, как код 2 квона - массив сссылок на функции с числовым индексом.

Прямой вызов по имени все равно не найдете - нет такого в Си.

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

JonHappy1, а может Вам взять вот такую разновидность ардуины? Так в JS этой рефлексии хоть залейся. И много чего другого.

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

А это так наметки

/**/
/**/
typedef void (*PFunc)(void);// тип указатель на функцию

typedef struct { // структура
  const char* mnemonicName;
  PFunc handler;
} Reflect;
void func1() {
  Serial.println("packet1");
}
void func2()
{
  Serial.println("packet2");
}
void func3()
{
  Serial.println("packet3");
}
Reflect reflections[] = {
  {"111", func1},
  {"222", func2},
  {"333", func3},
};
void call(const char* funcName) { // вызов функцию по имени
  size_t sz = sizeof(reflections) / sizeof(reflections[0]);
  for (size_t i = 0; i < sz; i++)
  {
    if (!strcmp(reflections[i].mnemonicName, funcName) && reflections[i].handler)
    {
      reflections[i].handler();
      break;
    }
  }
}
//--------------------------------
char buff[10];
byte iBuff = 0;
//---Компоновка-----------------------------
//---main-----------------------------
void setup() {
  Serial.begin(9600);
}

void loop() {
  if (Serial.available() > 0) {
    char c = Serial.read();
    if (c == '\n') { // конец пакета
      buff[iBuff] = 0;
      iBuff = 0;
      call(buff);//<--обрабатываете весь пакет
    }
    else {
      buff[iBuff++] = c;
    }
  }
}
/**/

Отправляете сообщения 111,222,333 и вызываете нужные функции

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

Вызов по именам - дурная идея. Бывает что приходится, если выбора нет. Например сообщения от модема текстовые. Тогда приходится делать по типу приведеного кода.  Но если протокол, который поверх WS, пишем сами - зачем себе гадости делать. Передавайте ИД функции. Если их много и все сложно - сложный составной идентификатор в котором скрываются подробности вызова и передачи параметров, тех которые "лалала это данные для выполнения".. Например первый байт - номер функции, второй байт - количество байт передаваемых ей как параметры. Или первый байт - модуль внутри устройства к которому обращаемся, второй байт -ИД функции этого модуля, третий байт параметр. И т.д. Тут много выдумать можна. Но строковые имена функций излишни, прежде всего потому, как писали уже, что посли компиляции они просто отсутствуют.

JonHappy1
Offline
Зарегистрирован: 11.06.2018

2Logik
конечно такой вариант имеет право на жизнь
просто у меня сквозное именование функций для "рефлексии" в java и javascript в браузере, поэтому по имени более наглядно

но можно и тут

typedef void (*FunctionPointer) ();


void xxx01(){Serial.println("xxx01");} ;
void xxx33(){Serial.println("xxx33");};
void xxx05(){Serial.println("xxx05");} ;
void xxx78(){Serial.println("xxx78");} ;

FunctionPointer numeral[4] = {xxx01,xxx33,xxx05,xxx78};

String st="xxx01xxx33xxx05xxx78";

void setup() {
  Serial.begin(9600);

}

void loop() {
  char cc[]="xxx01";
  
  numeral[st.indexOf(cc)/5]();
}

в моём варианте использование имен - вполне , остался вопрос по быстродействию и количеству кода для indexof.

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

То, что в джавах творится не суть важно. Как бы не крутили, а в системе приходится преобразовывать по цепочку "строковое имя"->индекс в таблице->указатель функции. Тут без вариантов. Варианты появляются в вопросе какая часть преобразования будет на сервере, а какая в ардуине. А именно где будет преобразование  "строковое имя"->индекс в таблице: на серваке или в ардуине. Думаю очевидно что на сервере это намного предпочтительней.

ПС. Со стрингами Вы в соседней теме я думал уже наигрались ;) Забудте о них один раз и навсегда.

JonHappy1
Offline
Зарегистрирован: 11.06.2018

2Logik

ссылку на "соседнюю тему" , пожалуйста. для прочтения.

со строками и в java сложности.
но тут строку можно пометить как константа и она будет не в оперативной памяти, что сэкономит её, а ещё и PROGMEM есть.
так что тут остаётся только поиск в строке. он , я думаю, уже оптимизирован на уровне кода.
тут есть два преимущества - более наглядно чем перебор в цикле по структуре, и меньще компилированного кода

ну и строку можно укоротить сделав её такой
"02|78|88|01|55|"

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

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

JonHappy1 пишет:

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

А из того же сообщения выделить номер функции и использовать его как индекс в массиве - это, видимо, ужас-ужас :)

В общем, используйте Iskra JS если Вы так любите рефлексировать - чем не решение? 

JonHappy1
Offline
Зарегистрирован: 11.06.2018

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

А из того же сообщения выделить номер функции и использовать его как индекс в массиве - это, видимо, ужас-ужас :)


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

работаю с тем , что есть.

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

Госспадя, а простейший словарь сложно сделать? 

ключ - имя функции ( а лучше её хэш) 

значение - адрес функции

 

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

JonHappy1
Offline
Зарегистрирован: 11.06.2018

DetSimen пишет:

Госспадя, а простейший словарь сложно сделать? 

ключ - имя функции ( а лучше её хэш) 

значение - адрес функции

 

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


параметр один
идея со словарем устраивает, но в си новичок, покажи пример кода

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

JonHappy1 пишет:

2Logik

ссылку на "соседнюю тему" , пожалуйста. для прочтения.

http://arduino.ru/forum/programmirovanie/konflikt-bibliotek-5#comment-375019

JonHappy1 пишет:

sadman41 пишет:

 Плюс тут есть String, который растет, как хочет. Ставлю на то, что если сбросить стринги и работать с char[] - то всё нормализуется.

скорее всего соглашусь, вот только как ? длина строки заранее не известна.

 

JonHappy1 пишет:

А я вот 90% вероятности даю, что String выжирал всю память на входящем реквесте.

я тоже к этому склоняюсь, вот только как объснить, что в цикле while - нормально, а после - фигфам?

 

JonHappy1
Offline
Зарегистрирован: 11.06.2018

там только предположени, ответа нету

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

JonHappy1 пишет:

со строками и в java сложности.

Так тем более нефиг с ними связыватся.

JonHappy1 пишет:

 я думаю, уже оптимизирован на уровне кода.

Забудте. Любой код будет оптимизирован только если его ктотооптимизирует. Приведеный поиск полным пперебором явно не оптимален.

JonHappy1 пишет:

ну и строку можно укоротить сделав её такой

"02|78|88|01|55|"

Ага :) Даже нужно укоротить и сделать последний шаг - преобразовать числа из строки в байты перед отправкой;)

JonHappy1 пишет:

всё-таки когда есть имена то проще ориентироваться в коде. ради этого приходится чем-то жертвовать.

Так тут просто именованые константы решают все. Мало того, без них хоть строки хоть числа пользовать нехороше вобщем. В магии заподозрят ;)

 

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

JonHappy1 пишет:
покажи пример кода

Прямо в первом ответе

http://arduino.ru/forum/programmirovanie/refleksiya-v-si-ili-vyzov-funkt...

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

С хешем - плохая идея. Чтоб небыло хеш-попаданий хеш нужен достаточно длинный. Иначе сюрпрайзы будут возникать - две функции с разными именами тихо и незаметно заимеют совпавшие хеши.

JonHappy1
Offline
Зарегистрирован: 11.06.2018

мой вариант короче

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

Logik пишет:

С хешем - плохая идея. Чтоб небыло хеш-попаданий хеш нужен достаточно длинный. Иначе сюрпрайзы будут возникать - две функции с разными именами тихо и незаметно заимеют совпавшие хеши.

Да какие уж там у Дуни длинные хэши?  Смишно. 

Тем более, у ТС всё это пхается в константный массив на этапе компиляции, ручками.  Кэш-коллизии (если будут), отлавливаюца просто внимательными глазами. :) 

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

А в чем тогда вобще смысл шаманства? Чем считать короткий "какбыхеш", еще и глазками проверять, намного проще иметь на сервере просто таблицу имен функций. При попытке вызова "xxx01" ищем её в массиве. Всеж на сервере это проще, быстрей и т.д. Нашли - индекс  её в этом массиве - шлем его по WS на ардуину. В ардуине по этому индексу из своей таблицы сразу указатель функции обработчика получаем. Все просто, если не плодить лишние сущности. И переименовуй свои функции хоть 3 раза в день если хочится сильно.

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

да я то согласно, теперь надо это до ТС как-то донести.  Он то думает, что в Яву попал.  А тут нихрена не райский остров. 

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

:)

Еще и сборщик мусора искать будет. Там воще беда...

JonHappy1
Offline
Зарегистрирован: 11.06.2018

2Logik
вариант правильный с тчки зрения исполняемого кода, но с точки зрения написания кода....
надо учесть , что пишется система и в ней много разных аспектов
что ечть сейчас и как работает
есть портал написан на java, работает и под окнами и под линуксами,
там N страниц, на каждой странице до 30 вызовов ws на сервер для обработки данных ,
вотправляем сообщение xxx44|data - на сервере (для каждой страницы есть файл jsp,  который обрабатывает команды с этой страницы, но точка входа для всех сообщений с клиентов одна, она и вызывает нужную команду(метод) с передачей данных)  метод с именм xxx44 обрабатывает данные и (если надо) отправляет результат  такого же формата xxx44|newdata
браузер "рефлексией" javascript выполняет функцию xxx44 с данными newdata. все наглядно . прозрачно.

также я хочу и тут, да это будет несколко затратно, но зато я знаю что вызов на сервере приведёт к выполнению нужной мне функции. да я могу по номеру , отправленному с сервера, вызвать функцию. но для этого мне надо на сервере ещё и таблицу соответствия хранить, а если что-то удалилось/добавилось надо ещё и эту таблицу менять (это можно автоматизироватьЮ конечно) но это гемор, если я счас отпправляю простое текстовое сообщение, то тут нужно будет обращаться к таблице...

чем не устраивает мой вариант? http://arduino.ru/forum/programmirovanie/refleksiya-v-si-ili-vyzov-funkt...
 

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

JonHappy1 пишет:

чем не устраивает мой вариант? http://arduino.ru/forum/programmirovanie/refleksiya-v-si-ili-vyzov-funkt...

Оверхед высокий от ваших явовских приемчиков. В  ардуине этой из нормальных струментов - молоток и пассатижи только. То есть - указатель и байтмассив. Сам постоянно мучаюсь, как с перловки назад в Wiring лезу. А что делать... Два кило памяти на все про всё - это даже не 640, которых хватит всем.

JonHappy1
Offline
Зарегистрирован: 11.06.2018

сравнил свой варант с вариантом, где представлена статистика по компиляции

добавление String (в той или иной форме) добавляет 1 кб к коду, т.е. саму либу по работе со строками.

если отказаться от String то да мой код не катит. но достаточно одной из других либ использовать String - эта либа будет подключена. (очень мало вероятно, что String ни одна другая либа не использует)
надо сказать мой вариант короче по коду и намного наглядее. и следовательно имеет право на существование.

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

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