如果编译器对指针到函数的支持被破坏,我该如何编写调度程序?

时间:2008-11-12 18:54:19

标签: c function-pointers dispatcher

我正在开发一个嵌入式应用程序,通过命令界面控制设备。我嘲笑VC中的指挥调度员,让我满意;但是当我将代码移到嵌入式环境中时,我发现编译器的指针到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
}

有没有人看到更好的方法来处理这种情况?可悲的是,“获得另一个编译器”不是一个可行的选择。 :(

---编辑添加: 只是为了澄清,特定的嵌入式环境被打破,因为编译器假设创建一个“函数指针表”,然后编译器使用它来解析通过指针调用函数。不幸的是,编译器坏了,并且没有生成正确的函数表。

所以我没有一种简单的方法来提取func地址来调用它。

---编辑#2: 啊,是的,使用void *(set | get)_dispatcher是我试图查看问题是否与func指针的typedefine有关。最初,我有

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;

7 个答案:

答案 0 :(得分:7)

您应该尝试更改struct command,以便函数指针具有实际类型:

typedef struct command {
  const char *code;
  set_fn_t set_dispatcher;
  get_fn_t get_dispatcher;
  const char *_description;
} command_t;

不幸的是,函数指针不能保证能够转换为/从void指针转换(仅适用于指向对象的指针)。

什么是嵌入式环境?


鉴于问题更新中发布的信息,我发现它确实是一个错误的编译器。

我认为您提出的解决方案似乎非常合理 - 它可能与我想出的相似。

答案 1 :(得分:3)

实际上不需要函数指针来填充void *。您可以检查以确保您调用的值实际上是函数的地址。如果没有,请在结构中使用函数指针类型:get_fn_t或IIRC void(*)(void)保证与任何函数指针类型兼容。

编辑:好的,假设无法按值调用,我想不出比你自动生成switch语句更简洁的方法来做你需要的东西。您可以在C预处理器之前使用现成的ASP样式的预处理器模式,用于ruby / python / perl / php /。像这样:

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

可能比宏/包技巧更具可读性,但是引入一个新工具并设置makefile对于这么少量的代码来说可能是不值得的。调试信息中的行号与您认为是源文件的文件无关,除非您在预处理器中执行额外的工作来指定它们。

答案 2 :(得分:2)

您能让供应商修复编译器吗?

答案 3 :(得分:1)

指针到函数的破坏程度如何?

如果编译器允许你获取函数的地址(我来自C ++,但我的意思是&getenv),你可以将调用约定东西包装成汇编程序。

如上所述,我是一个C ++ ssie,但有点像

; function call
push [arg1]
push [arg2]
call [command+8] ; at the 4th location, the setter is stored
ret

如果这个被破坏了,你可以定义一个extern void*指针数组,你可以再次在程序集中定义它们。

答案 4 :(得分:0)

尝试这种语法:

return(*((get_fn_t)commands [i] .get_dispatcher))(pc);

自从我做完C&amp; C已经有一段时间了。函数指针,但我相信原始的C语法在解除引用函数指针时需要*,但大多数编译器会让你在没有它的情况下离开。

答案 5 :(得分:0)

您是否可以访问链接图? 如果是这样的话,也许你可以在糟糕的函数指针表中解决问题:

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} 

现在编译,从链接映射中获取相关地址,替换常量,然后重新编译。什么都不应该移动,所以地图应该保持不变。 (使原始常量唯一应该阻止编译器将相同的值折叠到一个存储位置。您可能需要很长的时间,具体取决于架构)

如果这个概念有效,你可以添加一个运行脚本的后链接步骤来自动进行替换。当然,这只是一个理论,它可能会失败。

答案 6 :(得分:0)

也许,你需要再次研究一下这个结构:

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

纠正我,如果我在某处错了......;)