C 使用编译时选项设计API,以删除大多数函数的第一个参数并使用全局

C 使用编译时选项设计API,以删除大多数函数的第一个参数并使用全局,c,api,embedded,C,Api,Embedded,我正试图在ANSI C89/ISO C90中设计一个可移植的API,以便通过串行接口访问无线网络设备。该库将有多个网络层,各种版本需要在嵌入式设备上运行小到一个8位micro,包含32K代码和2K数据,最多可在包含一兆字节或更多代码和数据的嵌入式设备上运行 在大多数情况下,目标处理器将有一个单一的网络接口,我希望使用一个单一的全局结构,包含该设备的所有状态信息。我不想通过网络层传递指向该结构的指针 在少数情况下(例如,需要在两个网络上使用更多资源的设备),我将连接到多个设备,每个设备都有自己的全

我正试图在ANSI C89/ISO C90中设计一个可移植的API,以便通过串行接口访问无线网络设备。该库将有多个网络层,各种版本需要在嵌入式设备上运行小到一个8位micro,包含32K代码和2K数据,最多可在包含一兆字节或更多代码和数据的嵌入式设备上运行

在大多数情况下,目标处理器将有一个单一的网络接口,我希望使用一个单一的全局结构,包含该设备的所有状态信息。我不想通过网络层传递指向该结构的指针

在少数情况下(例如,需要在两个网络上使用更多资源的设备),我将连接到多个设备,每个设备都有自己的全局状态,并且需要通过层传递指向该状态的指针(或状态数组的索引)

我提出了两种可能的解决方案,但都不是特别好的。请记住,完整的驱动程序可能有20000行或更多行,覆盖多个文件,并包含数百个函数

第一种解决方案需要一个宏,该宏为需要访问全局状态的每个函数丢弃第一个参数:

// 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);

感谢您的反馈--今天我将尝试一些其他方法,在发布问题之后,我也开始倾向于我的第一个解决方案。我喜欢您使用该解决方案的方式,但我知道我要针对的编译器中至少有一个不支持宏中的变量参数。我已经尝试了更多的方法,包括我的两个发布方法的混合,但是还没有找到任何令人满意的方法。感谢您的投入——我可能会将所有发布的解决方案整合到我的最终设计中。