Я пытаюсь разработать портативный 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++ в наличии.)
Мне нравится ваше второе решение. Я просто предпочитаю объявлять каждую функцию дважды, чем иметь макрос 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);
Вот решение, которое не будет работать, если у вас есть потоки (или переключение интерфейсов при повторном входе или что-то в этом роде), но это чистый интерфейс, и он может подойти вам.
Вы можете использовать глобальный 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);
}
Лично мне больше нравится ваше первое решение :-)
Это будет работать с 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)
для функций, не принимающих аргументов.