C 使用编译时选项设计API,以删除大多数函数的第一个参数并使用全局
我正试图在ANSI C89/ISO C90中设计一个可移植的API,以便通过串行接口访问无线网络设备。该库将有多个网络层,各种版本需要在嵌入式设备上运行小到一个8位micro,包含32K代码和2K数据,最多可在包含一兆字节或更多代码和数据的嵌入式设备上运行 在大多数情况下,目标处理器将有一个单一的网络接口,我希望使用一个单一的全局结构,包含该设备的所有状态信息。我不想通过网络层传递指向该结构的指针 在少数情况下(例如,需要在两个网络上使用更多资源的设备),我将连接到多个设备,每个设备都有自己的全局状态,并且需要通过层传递指向该状态的指针(或状态数组的索引) 我提出了两种可能的解决方案,但都不是特别好的。请记住,完整的驱动程序可能有20000行或更多行,覆盖多个文件,并包含数百个函数 第一种解决方案需要一个宏,该宏为需要访问全局状态的每个函数丢弃第一个参数:C 使用编译时选项设计API,以删除大多数函数的第一个参数并使用全局,c,api,embedded,C,Api,Embedded,我正试图在ANSI C89/ISO C90中设计一个可移植的API,以便通过串行接口访问无线网络设备。该库将有多个网络层,各种版本需要在嵌入式设备上运行小到一个8位micro,包含32K代码和2K数据,最多可在包含一兆字节或更多代码和数据的嵌入式设备上运行 在大多数情况下,目标处理器将有一个单一的网络接口,我希望使用一个单一的全局结构,包含该设备的所有状态信息。我不想通过网络层传递指向该结构的指针 在少数情况下(例如,需要在两个网络上使用更多资源的设备),我将连接到多个设备,每个设备都有自己的全
// network.h
typedef struct dev_t {
int var;
long othervar;
char name[20];
} dev_t;
#ifdef IF_MULTI
#define foo_function( x, a, b, c) _foo_function( x, a, b, c)
#define bar_function( x) _bar_function( x)
#else
extern dev_t DEV;
#define IFACE (&DEV)
#define foo_function( x, a, b, c) _foo_function( a, b, c)
#define bar_function( x) _bar_function( )
#endif
int bar_function( dev_t *IFACE);
int foo_function( dev_t *IFACE, int a, long b, char *c);
// network.c
#ifndef IF_MULTI
dev_t DEV;
#endif
int bar_function( dev_t *IFACE)
{
memset( IFACE, 0, sizeof *IFACE);
return 0;
}
int foo_function( dev_t *IFACE, int a, long b, char *c)
{
bar_function( IFACE);
IFACE->var = a;
IFACE->othervar = b;
strcpy( IFACE->name, c);
return 0;
}
第二种解决方案定义了要在函数声明中使用的宏:
// network.h
typedef struct dev_t {
int var;
long othervar;
char name[20];
} dev_t;
#ifdef IF_MULTI
#define DEV_PARAM_ONLY dev_t *IFACE
#define DEV_PARAM DEV_PARAM_ONLY,
#else
extern dev_t DEV;
#define IFACE (&DEV)
#define DEV_PARAM_ONLY void
#define DEV_PARAM
#endif
int bar_function( DEV_PARAM_ONLY);
// I don't like the missing comma between DEV_PARAM and arg2...
int foo_function( DEV_PARAM int a, long b, char *c);
// network.c
#ifndef IF_MULTI
dev_t DEV;
#endif
int bar_function( DEV_PARAM_ONLY)
{
memset( IFACE, 0, sizeof *IFACE);
return 0;
}
int foo_function( DEV_PARAM int a, long b, char *c)
{
bar_function( IFACE);
IFACE->var = a;
IFACE->othervar = b;
strcpy( IFACE->name, c);
return 0;
}
访问任一方法的C代码保持不变:
// multi.c - example of multiple interfaces
#define IF_MULTI
#include "network.h"
dev_t if0, if1;
int main()
{
foo_function( &if0, -1, 3.1415926, "public");
foo_function( &if1, 42, 3.1415926, "private");
return 0;
}
// single.c - example of a single interface
#include "network.h"
int main()
{
foo_function( 11, 1.0, "network");
return 0;
}
有没有一种更干净的方法我还没有想出?我倾向于第二个,因为它应该更容易维护,而且更清楚的是,函数的参数中有一些宏魔法。另外,当我想将函数名用作函数指针时,第一种方法要求在函数名前面加上“ux”
我确实希望删除“single interface”情况下的参数,以消除将参数推送到堆栈上的不必要代码,并允许函数访问寄存器中的第一个“real”参数,而不是从堆栈中加载它。如果可能的话,我不想维护两个独立的代码库
想法?思想?现有代码中类似的示例
(注意使用C++不是一种选择,因为一些计划的目标没有可用的C++编译器)
如果你有线程(或者重新进入或类似的切换接口),这是一个不可行的解决方案,但是它是一个干净的接口,它可能对你有用。 您可以使用全局DEV
,让您的单实例函数,让您的多接口函数设置此全局并调用它们的单实例对应项
例如:
dev_t *DEV;
int foo_function(int x, int y)
{
/* DEV->whatever; */
return DEV->status;
}
int foo_function_multi(dev_t *IFACE, int x, int y)
{
DEV = IFACE;
return foo_function(x, y);
}
另一种选择是使用可变参数,并传递和获取一个额外的参数(其中包含要使用的接口)#ifdef MULTI,但这很可怕,因为您失去了类型安全性,并且会阻止在您的平台上可能非常关心的寄存器中传递参数。此外,所有带有可变参数的函数必须至少有一个命名参数,您的问题都是关于避免参数!但无论如何:
#ifndef MULTI
dev_t *DEV;
#endif
int foo(int x, int y, ...)
{
#ifdef MULTI
va_list args;
va_start(args, y);
dev_t *DEV = va_arg(args, (dev_t*));
va_end(args);
#endif
/* DEV->whatever */
return DEV->status;
}
// call from single
int quux()
{
int status = foo(23, 17);
}
// call from multi
int quux()
{
int status = foo(23, 17, &if0);
}
就我个人而言,我更喜欢您的第一个解决方案:-)这将适用于gcc:
#ifdef TOMSAPI_SMALL
#define TOMSAPI_ARGS( dev, ...) (__VA_ARGS__)
#else // ! TOMSAPI_SMALL
#define TOMSAPI_ARGS( dev, ...) (dev, ## __VA_ARGS__)
#endif // TOMSAPI_SMALL
#ifdef TOMSAPI_SMALL
#define TOMSAPI_DECLARE_DEVP(local_dev_ptr) device_t * local_dev_ptr = &global_dev; NULL
// The trailing NULL is to make the compiler make you put a ; after calling the macro,
// but without allowing something that would mess up the declaration if you forget the ;
// You can't use the do{...}while(0) trick for a variable declaration.
#else // ! TOMSAPI_SMALL
#define TOMSAPI_DECLARE_DEVP(local_dev_ptr) device_t * local_dev_ptr = arg_dev; NULL
#endif // TOMSAPI_SMALL
然后
int tomsapi_init TOMSAPI(device_t *arg_dev, void * arg_for_illustration_purposes ) {
TOMSAPI_DECLARE_DEVP( my_dev );
my_dev->stuff = arg_for_illustration_purposes;
return 0;
}
int tomsapi_init TOMSAPI(void * arg_for_illustration_purposes ) {
dev->stuff = arg_for_illustration_purposes;
return 0;
}
使用此方法,您必须确保所有API函数对设备指针使用相同的名称,但所有函数定义和声明看起来都需要完整数量的参数。如果这对您不重要,您可以:
#ifdef TOMSAPI_SMALL
#define TOMSAPI_ARGS(...) (__VA_ARGS__)
#else // ! TOMSAPI_SMALL
#define TOMSAPI_ARGS(...) (device_t *dev, ## __VA_ARGS__)
#endif // TOMSAPI_SMALL
#ifdef TOMSAPI_SMALL
#define TOMSAPI_DECLARE_DEVP() device_t * dev = &global_dev; NULL
#else // ! TOMSAPI_SMALL
#define TOMSAPI_DECLARE_DEVP(local_dev_ptr) NULL
#endif // TOMSAPI_SMALL
然后
int tomsapi_init TOMSAPI(device_t *arg_dev, void * arg_for_illustration_purposes ) {
TOMSAPI_DECLARE_DEVP( my_dev );
my_dev->stuff = arg_for_illustration_purposes;
return 0;
}
int tomsapi_init TOMSAPI(void * arg_for_illustration_purposes ) {
dev->stuff = arg_for_illustration_purposes;
return 0;
}
但这最终看起来好像dev从未向阅读您的代码的人声明过
尽管如此,您可能会发现在单设备小型平台上,使用全局设备结构最终的成本比传递指针要高,因为该结构的地址需要重新加载的次数太多。如果API是堆叠的(某些函数调用其他函数并将dev指针传递给它们),使用大量尾部递归,和/或平台使用寄存器传递大多数参数而不是堆栈,则更可能出现这种情况
编辑:
我刚刚意识到,如果你的api函数不带任何附加参数,那么这个方法可能会有问题,即使你的编译器想强迫你对不带参数的函数说
int foo(void)
。我喜欢你的第二个解决方案。我只是喜欢将每个函数声明两次,而不是将PARAM宏放在public头中。我更喜欢将宏hijinks放在隐藏的C文件中
// common header
#ifdef IF_MULTI
int foo_func1(dev_t* if, int a);
int foo_func2(dev_t* if, int a, int b);
int foo_func3(dev_t* if);
#else
int foo_func1(int a);
int foo_func2(int a, int b);
int foo_func3();
#endif
// your C file
#ifdef IF_MULTI
#define IF_PARM dev_t* if,
#define GET_IF() (if)
#else
dev_t global_if;
#define IF_PARM
#define GET_IF() (&global_if)
#endif
int foo_func1(IF_PARM int a)
{
GET_IF()->x = a;
return GET_IF()->status;
}
int foo_func2(IF_PARM int a, int b)
int foo_func3(IF_PARM);
感谢您的反馈--今天我将尝试一些其他方法,在发布问题之后,我也开始倾向于我的第一个解决方案。我喜欢您使用该解决方案的方式,但我知道我要针对的编译器中至少有一个不支持宏中的变量参数。我已经尝试了更多的方法,包括我的两个发布方法的混合,但是还没有找到任何令人满意的方法。感谢您的投入——我可能会将所有发布的解决方案整合到我的最终设计中。