Как мне написать диспетчер, если поддержка моим компилятором указателей на функции нарушена?
-
08-07-2019 - |
Вопрос
Я работаю над встроенным приложением, в котором управление устройством осуществляется через командный интерфейс.Я поиздевался над диспетчером команд в VC, и он заработал к моему удовлетворению;но когда я затем перенес код во встроенную среду, я обнаружил, что компилятор имеет неработающую реализацию функции pointer-to-func.
Вот как я изначально реализовал код (в VC):
/* Relevant parts of header file */
typedef struct command {
const char *code;
void *set_dispatcher;
void *get_dispatcher;
const char *_description;
} command_t;
#define COMMAND_ENTRY(label,dispatcher,description) {(const char*)label, &set_##dispatcher, &get_##dispatcher, (const char*)description}
/* Dispatcher data structure in the C file */
const command_t commands[] = {
COMMAND_ENTRY("DH", Dhcp, "DHCP (0=off, 1=on)"),
COMMAND_ENTRY("IP", Ip, "IP Address (192.168.1.205)"),
COMMAND_ENTRY("SM", Subnet, "Subunet Mask (255.255.255.0)"),
COMMAND_ENTRY("DR", DefaultRoute, "Default router (192.168.1.1)"),
COMMAND_ENTRY("UN", Username, "Web username"),
COMMAND_ENTRY("PW", Password, "Web password"),
...
}
/* After matching the received command string to the command "label", the command is dispatched */
if (pc->isGetter)
return ((get_fn_t)(commands[i].get_dispatcher))(pc);
else
return ((set_fn_t)(commands[i].set_dispatcher))(pc);
}
Без использования указателей на функции, похоже, моя единственная надежда - использовать операторы switch() / case для вызова функций.Но я бы хотел избежать необходимости вручную поддерживать большой оператор switch().
То, что я думал сделать, - это переместить все строки COMMAND_ENTRY в отдельный включаемый файл.Затем обертывает, которые включают файл с различными #define и #undefines.Что -то вроде:
/* Create enum's labels */
#define COMMAND_ENTRY(label,dispatcher,description) SET_##dispatcher, GET_##dispatcher
typedef enum command_labels = {
#include "entries.cinc"
DUMMY_ENUM_ENTRY} command_labels_t;
#undefine COMMAND_ENTRY
/* Create command mapping table */
#define COMMAND_ENTRY(label,dispatcher,description) {(const char*)label, SET_##dispatcher, GET_##dispatcher, (const char*)description}
const command_t commands[] = {
#include "entries.cinc"
NULL /* dummy */ };
#undefine COMMAND_ENTRY
/*...*/
int command_dispatcher(command_labels_t dispatcher_id) {
/* Create dispatcher switch statement */
#define COMMAND_ENTRY(label,dispatcher,description) case SET_##dispatcher: return set_##dispatcher(pc); case GET_##dispatcher: return get_##dispatcher(pc);
switch(dispatcher_id) {
#include "entries.cinc"
default:
return NOT_FOUND;
}
#undefine COMMAND_ENTRY
}
Кто-нибудь видит лучший способ справиться с этой ситуацией?К сожалению, "получить другой компилятор" не является жизнеспособным вариантом.:(
--- Отредактируйте, чтобы добавить:Просто чтобы уточнить, конкретная встроенная среда нарушена в том смысле, что компилятор предполагаемый создать "таблицу указателей функций", которая затем используется компилятором для разрешения вызовов функций через указатель.К сожалению, компилятор сломан и не генерирует корректную таблицу функций.
Поэтому у меня нет простого способа извлечь адрес функции, чтобы вызвать его.
--- Правка №2:Ах, да, использование void *(set | get)_dispatcher было моей попыткой выяснить, не связана ли проблема с определением типа указателей функций.Первоначально у меня было
typedef int (*set_fn_t)(cmdContext_t *pCmdCtx);
typedef int (*get_fn_t)(cmdContext_t *pCmdCtx);
typedef struct command {
const char *code;
set_fn_t set_dispatcher;
get_fn_t get_dispatcher;
const char *_description;
} command_t;
Решение
Попробуйте изменить struct command
, чтобы указатели на функции имели фактический тип:
typedef struct command {
const char *code;
set_fn_t set_dispatcher;
get_fn_t get_dispatcher;
const char *_description;
} command_t;
К сожалению, указатели на функции не гарантируют возможность преобразования в / из пустых указателей (это относится только к указателям на объекты). Р>
Что такое встроенная среда?
<Ч>Учитывая информацию, размещенную в обновлениях к вопросу, я вижу, что это действительно ошибочный компилятор.
Я думаю, что предложенное вами решение кажется довольно разумным - оно, вероятно, похоже на то, что я бы придумал.
Другие советы
Указатель на функцию на самом деле не требуется помещать в пустоту *.Вы могли бы проверить, чтобы убедиться, что значение, которое вы вызываете, на самом деле является адресом функции.Если нет, используйте тип указателя на функцию в структуре:либо get_fn_t, либо IIRC void(*)(void) гарантированно совместимы с любым типом указателя на функцию.
Редактировать:Хорошо, предполагая, что вызов по значению нельзя заставить работать, я не могу придумать более аккуратного способа сделать то, что вам нужно, чем автоматическая генерация инструкции switch.Возможно, вы могли бы использовать готовый режим препроцессора в стиле ASP для ruby / python / perl / php / чего угодно, предшествующего препроцессору C.Что - то вроде этого:
switch(dispatcher_id) {
<% for c in commands %>
case SET_<% c.dispatcher %>: return set_<% c.dispatcher %>(pc);
case GET_<% c.dispatcher %>: return get_<% c.dispatcher %>(pc);
<% end %>
default:
return NOT_FOUND;
}
возможно, это немного более читабельно, чем трюк с макросом / включением, но вводить новый инструмент и настраивать make-файлы, вероятно, не стоит для такого небольшого объема кода.И номера строк в отладочной информации не будут относиться к файлу, который вы считаете исходным, если вы не проделаете дополнительную работу в своем препроцессоре, чтобы указать их.
Можете ли вы заставить поставщика исправить компилятор?
Насколько нарушен указатель на функцию? Р>
Если компилятор позволяет вам получить адрес функции (я из C ++, но я имею в виду & getenv
), вы можете заключить соглашение о вызовах в ассемблер. р>
Как уже говорилось, я - специалист по C ++, но что-то вроде
; function call
push [arg1]
push [arg2]
call [command+8] ; at the 4th location, the setter is stored
ret
Если даже это не работает, вы можете определить массив указателей extern void *
, которые вы снова определите в сборке.
попробуйте этот синтаксис:
return (* ((get_fn_t) команды [i] .get_dispatcher)) (pc);
Прошло немного времени с тех пор, как я сделал C & amp; указатели на функции, но я полагаю, что оригинальный синтаксис C требовал * при разыменовании указателей на функции, но большинство компиляторов позволили бы вам обойтись без него.
У вас есть доступ к карте ссылок? Если это так, возможно, вы сможете взломать таблицу указателей функций Wickky:
unsigned long addr_get_dhcp = 0x1111111;
unsigned long addr_set_dhcp = 0x2222222; //make these unique numbers.
/* Relevant parts of header file */
typedef struct command {
const char *code;
unsigned long set_dispatcher;
unsigned long get_dispatcher;
const char *_description;
} command_t;
#define COMMAND_ENTRY(label,dispatcher,description) {(const char*)label,
addr_set_##dispatcher, addr_get_##dispatcher, (const char*)description}
Теперь скомпилируйте, извлеките соответствующие адреса из карты ссылок, замените константы и перекомпилируйте. Ничто не должно двигаться, поэтому карта должна оставаться прежней. (Создание оригинальных констант уникальными должно препятствовать тому, чтобы компилятор свернул идентичные значения в одно место хранения. Вам может понадобиться long long, в зависимости от архитектуры)
Если концепция работает, вы можете добавить шаг после ссылки, запустив сценарий, чтобы выполнить замену автоматически. Конечно, это всего лишь теория, она может с треском провалиться.
Может быть, вам нужно снова посмотреть на структуру:
typedef struct command {
const char *code;
void *set_dispatcher; //IMO, it does not look like a function pointer...
void *get_dispatcher; //more like a pointer to void
const char *_description;
} command_t;
Допустим, у ваших диспетчеров есть следующее похожее определение функции:
//a function pointer type definition
typedef int (*genericDispatcher)(int data);
Предположим, что диспетчеры похожи на приведенные ниже:
int set_DhcpDispatcher(int data) { return data; }
int get_DhcpDispatcher(int data) { return 2*data; }
Итак, пересмотренная структура будет:
typedef struct command {
const char *code;
genericDispatcher set_dispatcher;
genericDispatcher get_dispatcher;
const char *_description;
} command_t;
Ваш макрос будет:
#define COMMAND_ENTRY(label,dispatcher,description) \
{ (const char*)label, \
set_##dispatcher##Dispatcher, \
get_##dispatcher##Dispatcher, \
(const char*)description }
Затем вы можете установить свой массив как обычно:
int main(int argc, char **argv)
{
int value1 = 0, value2 = 0;
const command_t commands[] = {
COMMAND_ENTRY("DH", Dhcp, "DHCP (0=off, 1=on)")
};
value1 = commands[0].set_dispatcher(1);
value2 = commands[0].get_dispatcher(2);
printf("value1 = %d, value2 = %d", value1, value2);
return 0;
}
Поправь меня, если я где-то ошибаюсь ...;)