I am working on an embedded application where the device is controlled through a command interface. I mocked the command dispatcher in VC and had it working to my satisfaction; but when I then moved the code over to the embedded environment, I found out that the compiler has a broken implementation of pointer-to-func’s.
Here’s how I originally implemented the code (in 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); }
Without the use of function pointers, it seems like my only hope is to use switch()/case statements to call functions. But I’d like to avoid having to manually maintain a large switch() statement.
What I was thinking of doing is moving all the COMMAND_ENTRY lines into a separate include file. Then wraps that include file with varying #define and #undefines. Something like:
/* 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 }
Does anyone see a better way to handle this situation? Sadly, ‘get another compiler’ is not a viable option. 🙁
— Edit to add: Just to clarify, the particular embedded environment is broken in that the compiler is supposed to create a ‘function-pointer table’ which is then used by the compiler to resolve calls to functions through a pointer. Unfortunately, the compiler is broken and doesn’t generate a correct function-table.
So I don’t have an easy way to extract the func address to invoke it.
— Edit #2: Ah, yes, the use of void *(set|get)_dispatcher was my attempt to see if the problem was with the typedefine of the func pointers. Originally, I had
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;
You should try changing your
struct commandso the function pointers have the actual type:Unfortunately, function pointers are not guaranteed to be able to convert to/from void pointers (that applies only to pointers to objects).
What’s the embedded environment?
Given the information posted in the updates to the question, I see that it’s really a bugged compiler.
I think that your proposed solution seems pretty reasonable – it’s probably similar to what I would have come up with.