Разработка API с опцией времени компиляции удалить первый параметр к большинству функций и использовать глобальное

Я пытаюсь разработать портативный API в ANSI C89/ISO C90 для доступа к устройству беспроводных сетей на последовательном интерфейсе. Библиотека будет иметь несколько сетевых уровней, и различные версии должны работать на встроенных устройствах, столь же маленьких как 8-разрядное микро с 32K кода и 2K данных, на до встроенных устройств с мегабайтом или большим количеством кода и данных.

В большинстве случаев целевой процессор будет иметь единственный сетевой интерфейс, и я захочу использовать единственную глобальную структуру со всей информацией состояния для того устройства. Я не хочу передавать указатель на ту структуру через сетевые уровни.

В нескольких случаях (например, устройство с большим количеством ресурсов, которое должно жить на двух сетях) я буду взаимодействовать через интерфейс к нескольким устройствам, каждому с их собственным глобальным состоянием, и должен буду передать указатель на то состояние (или индекс к массиву состояния) через слои.

Я предложил два возможных решения, но никакой не особенно симпатичен. Следует иметь в виду, что полный драйвер потенциально будет 20 000 строк или больше, покройте несколько файлов и содержите сотни функций.

Первое решение требует макроса, который отбрасывает первый параметр для каждой функции, которая должна получить доступ к глобальному состоянию:

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

Существует ли более чистый метод, который я не выяснил? Я склоняюсь к второму, так как должно быть легче поддержать, и более ясно, что существует некоторое макро-волшебство в параметрах к функции. Кроме того, первый метод требует добавления префикса имен функций с "_", когда я хочу использовать их в качестве указателей функции.

Я действительно хочу удалить параметр в "единственном интерфейсном" случае для устранения ненужного кода, чтобы продвинуть параметр на стек и позволить функции получать доступ к первому "реальному" параметру в регистре вместо того, чтобы загрузить его из стека. И, если вообще возможный, я не хочу должным быть поддерживать две отдельных кодовых базы.

Мысли? Идеи? Примеры чего-то подобного в существующем коде?

(Обратите внимание, что использование C++ не является опцией, так как некоторые запланированные цели не имеют компилятор C++ в наличии.)

5
задан tomlogic 11 May 2010 в 23:54
поделиться

3 ответа

Мне нравится ваше второе решение. Я просто предпочитаю объявлять каждую функцию дважды, чем иметь макрос PARAM в общедоступном заголовке. Я предпочитаю помещать макросы в скрытый файл 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);
2
ответ дан 14 December 2019 в 19:05
поделиться

Вот решение, которое не будет работать, если у вас есть потоки (или переключение интерфейсов при повторном входе или что-то в этом роде), но это чистый интерфейс, и он может подойти вам.

Вы можете использовать глобальный 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);
}

Другой вариант - использовать переменные args, и передавать и получать дополнительный arg (который содержит интерфейс для использования) #ifdef MULTI, но это ужасно, потому что вы потеряете безопасность типов, и не сможете передавать arg в регистр, который, возможно, очень важен для вашей платформы. Кроме того, все функции с переменными аргументами должны иметь хотя бы один именованный аргумент, а ваш вопрос как раз о том, как избежать аргументов! Но в любом случае:


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

Лично мне больше нравится ваше первое решение :-)

2
ответ дан 14 December 2019 в 19:05
поделиться

Это будет работать с 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;
}

. Используя этот метод, вы должны будете убедиться, что все ваши функции 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(void * arg_for_illustration_purposes ) {
    dev->stuff = arg_for_illustration_purposes;
    return 0;
}

Но в конечном итоге это выглядит так, будто dev никогда не объявляется тому, кто читает ваш код.

При всем вышесказанном вы можете обнаружить, что на небольшой платформе с одним устройством, использующей глобальную структуру устройства, в конечном итоге будет стоить больше, чем передача указателя, из-за того, что адрес этой структуры придется перезагружать столько раз. . Это более вероятно, если ваш API сложен в стек (некоторые из ваших функций вызывают другие ваши функции и передают им указатель разработчика), использует много хвостовой рекурсии и / или ваша платформа использует регистры для передачи большинства аргументов, а не стек.

РЕДАКТИРОВАТЬ: Я только что понял, что с этим методом могут возникнуть проблемы, если у вас есть функции api, которые не принимают дополнительных аргументов, даже если вы используете оператор ##, если ваш компилятор хочет заставить вас скажем int foo (void) для функций, не принимающих аргументов.

1
ответ дан 14 December 2019 в 19:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: