如果我的编译器对函数指针的支持被破坏,我该如何编写调度程序?
我正在开发一个嵌入式应用程序,其中设备通过命令接口进行控制。 我嘲笑了 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
您应该尝试更改您的 struct 命令,以便函数指针具有实际类型:
不幸的是,不能保证函数指针能够与 void 指针相互转换(仅适用于指向对象的指针)。
嵌入式环境是什么?
鉴于问题更新中发布的信息,我发现这确实是一个有缺陷的编译器。
我认为你提出的解决方案似乎相当合理 - 它可能与我想出的类似。
You should try changing your
struct command
so 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.
实际上并不需要函数指针适合 void*。 您可以检查以确保您调用的值实际上是函数的地址。 如果不是,请在结构中使用函数指针类型:get_fn_t 或 IIRC void(*)(void) 保证与任何函数指针类型兼容。
编辑:好的,假设按值调用无法工作,我想不出比自动生成 switch 语句更简洁的方法来完成您需要的操作。 在 C 预处理器之前,您可以对 ruby/python/perl/php/任何其他语言使用现成的 ASP 样式预处理器模式。 像这样的东西:
可能比宏/包含技巧更具可读性,但是对于如此少量的代码来说,引入新工具并设置 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:
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.
你能让供应商修复编译器吗?
Can you get the vendor to fix the compiler?
函数指针被破坏到什么程度?
如果编译器允许您获取函数的地址(我来自 C++,但
&getenv
就是我的意思),您可以将调用约定内容包装到汇编程序中。如前所述,我是 C++ 爱好者,但如果即使
这样被破坏,您也可以定义一个
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
If even that is broken, you could define an array of
extern void*
pointers which you define, again, in assembly.尝试以下语法:
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.
您可以访问链接地图吗?
如果是这样,也许您可以绕过这个奇怪的函数指针表:
现在编译,从链接映射中获取相关地址,替换常量,然后重新编译。 任何东西都不应移动,因此地图应保持不变。 (使原始常量唯一应该可以防止编译器将相同的值折叠到一个存储位置。您可能需要一个很长的值,具体取决于体系结构)
如果这个概念有效,您可能可以添加一个运行脚本来执行的链接后步骤自动更换。 当然,这只是一个理论,它可能会失败得很惨。
Do you have access to the link map?
If so, maybe you can hack your way around the wonky function-pointer table:
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.
也许,您需要再次查看该结构:
假设您的调度程序具有以下类似的函数定义:
假设调度程序如下所示:
因此,修改后的结构将是:
您的宏将是:
然后,您可以设置数组像往常一样:
如果我在某个地方错了,请纠正我......;)
Maybe, you need to look into the structure again:
Let say your dispatchers have the following similar function definition:
Assume that the dispatchers are like below:
So, the revised structure will be:
Your macro will be:
Then, you can set your array as usual:
Correct me, if I am wrong somewhere... ;)