Разработка API с опцией времени компиляции для удаления первого параметра для большинства функций и использования глобального - PullRequest
5 голосов
/ 12 мая 2010

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

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

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

Я придумал два возможных решения, но ни одно из них не было особенно привлекательным. Имейте в виду, что полный драйвер может содержать 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;
   }

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

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

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

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

Ответы [ 3 ]

2 голосов
/ 13 мая 2010

Мне нравится ваше второе решение. Я просто предпочитаю объявлять каждую функцию дважды, а не добавлять этот макрос 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 голосов
/ 12 мая 2010

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

Вы могли бы иметь свои функции единственного экземпляра, используя глобальный 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);
}

Лично я предпочитаю ваше первое решение: -)

1 голос
/ 13 мая 2010

Это будет работать на 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 сложен (некоторые из ваших функций вызывают другие ваши функции и передают им указатель dev), использует много хвостовой рекурсии и / или ваша платформа использует регистры для передачи большинства аргументов, а не стека.

EDIT: Я только что понял, что может быть проблема с этим методом, если у вас есть API-функции, которые не принимают дополнительных аргументов, даже если вы используете оператор ##, если ваш компилятор хочет заставить вас сказать int foo(void) для функций, которые не принимают аргументов .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...