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

发布于 2024-07-08 23:34:26 字数 2812 浏览 10 评论 0原文

我正在开发一个嵌入式应用程序,其中设备通过命令接口进行控制。 我嘲笑了 VC 中的命令调度程序,并让它工作得令我满意; 但是当我将代码移至嵌入式环境时,我发现编译器的函数指针实现有问题。

下面是我最初实现代码的方式(在 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;

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;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

抠脚大汉 2024-07-15 23:34:26

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

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

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

嵌入式环境是什么?


鉴于问题更新中发布的信息,我发现这确实是一个有缺陷的编译器。

我认为你提出的解决方案似乎相当合理 - 它可能与我想出的类似。

You should try changing your struct command so the function pointers have the actual type:

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

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.

你没皮卡萌 2024-07-15 23:34:26

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

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

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 可能不值得。 并且调试信息中的行号不会与您认为是源文件的文件相关,除非您在预处理器中进行额外的工作来指定它们。

A function pointer isn't actually required to fit in a void*. You could check to make sure that the value you're calling is actually the address of the function. If not, use a function pointer type in the struct: either get_fn_t, or IIRC void(*)(void) is guaranteed to be compatible with any function pointer type.

Edit: OK, assuming that calling by value can't be made to work, I can't think of a neater way to do what you need than auto-generating the switch statement. You could maybe use an off-the-shelf ASP-style preprocessor mode for ruby/python/perl/php/whatever prior to the C preprocessor. Something like this:

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

might be a bit more readable than the macro/include trick, but introducing a new tool and setting up the makefiles is probably not worth it for such a small amount of code. And the line numbers in the debug info won't relate to the file you think of as the source file unless you do extra work in your preprocessor to specify them.

公布 2024-07-15 23:34:26

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

Can you get the vendor to fix the compiler?

ぽ尐不点ル 2024-07-15 23:34:26

函数指针被破坏到什么程度?

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

如前所述,我是 C++ 爱好者,但如果即使

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

这样被破坏,您也可以定义一个 extern void* 指针数组,再次在汇编中定义它。

To what extent is the pointer-to-function broken?

If the compiler allows you to get the address of a function (I'm from C++, but &getenv is what I mean), you could wrap the calling convention stuff into assembler.

As said, I'm a C++ssie, but something in the way of

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

If even that is broken, you could define an array of extern void* pointers which you define, again, in assembly.

夏九 2024-07-15 23:34:26

尝试以下语法:

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

自从我完成 C & 以来已经有一段时间了。 函数指针,但我相信原始的 C 语法在取消引用函数指针时需要 *,但大多数编译器会让您摆脱它。

try this syntax:

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

It's been awhile since I've done C & function pointers, but I believe the original C syntax required the * when dereferencing function pointers but most compilers would let you get away without it.

↙温凉少女 2024-07-15 23:34:26

您可以访问链接地图吗?
如果是这样,也许您可​​以绕过这个奇怪的函数指针表:

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} 

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

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

Do you have access to the link map?
If so, maybe you can hack your way around the wonky function-pointer table:

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} 

Now compile, grab the relevant addresses from the link map, replace the constants, and recompile. Nothing should move, so the map ought to stay the same. (Making the original constants unique should prevent the compiler from collapsing identical values into one storage location. You may need a long long, depending on the architecture)

If the concept works, you could probably add a post-link step running a script to do the replacement automagically. Of course, this is just a theory, it may fail miserably.

痞味浪人 2024-07-15 23:34:26

也许,您需要再次查看该结构:

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

如果我在某个地方错了,请纠正我......;)

Maybe, you need to look into the structure again:

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;

Let say your dispatchers have the following similar function definition:

//a function pointer type definition
typedef int (*genericDispatcher)(int data);

Assume that the dispatchers are like below:

int set_DhcpDispatcher(int data) { return data; }
int get_DhcpDispatcher(int data) { return 2*data; }

So, the revised structure will be:

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

Your macro will be:

#define COMMAND_ENTRY(label,dispatcher,description) \
{   (const char*)label, \
    set_##dispatcher##Dispatcher, \
    get_##dispatcher##Dispatcher, \
    (const char*)description } 

Then, you can set your array as usual:

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

Correct me, if I am wrong somewhere... ;)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文