#c #macros #libraries
#c #макросы #библиотеки
Вопрос:
Существует ли обычная практика расширения C API при сохранении прямой совместимости?
Предположим, я хочу предоставить функцию foo в библиотеке c:
foo(int value);
Теперь в более поздней версии я хотел бы расширить foo, чтобы разрешить другой параметр.
foo(int value, const char *description_may_be_NULL);
Для обеспечения прямой совместимости мне пришлось бы назвать новую функцию по-другому, например foo2
.
Было бы разумно предоставить макрос, такой, что:
#define MYLIB_API 2
#include <mylib.h>
/* the header will #define foo foo2 */
Это позволило бы избежать использования неприятных имен на практике.
Существуют ли какие-либо общие методы обработки конфликтующей прямой совместимости и элегантного кода для будущих версий? Будут оценены любые примеры того, как популярные API C справлялись с этим в прошлом.
Редактировать: К сожалению, я четко не упомянул об этом: foo(int)
подпись уже установлена, поэтому введение параметров varargs / struct также было бы несовместимым изменением API. На самом деле маловероятно, что многие функции изменятся со временем, но некоторые в конечном итоге изменятся. Оплата вперед за все функции с необычной подписью кажется мне высокой ценой.
Комментарии:
1. Ядро UNIX и Linux (фактически интерфейс библиотеки C), как правило, делает именно то, что вы предлагаете, предоставляя новую обновленную функцию как
foo2
. Иногда используются макроигры, чтобы «прозрачно» обеспечить поддержку больших файлов. Я должен просто принять и задокументировать новую функцию, если только вы не в состоянии глобально обновить все вызовы функций для вашей библиотечной функции и все файлы заголовков. Играя в макро-игры, так что этоfoo()
действительноfoo2()
может привести к путанице в один прекрасный день, когда.h
файл будет использоваться не так, как вы ожидаете.2. open (2), был расширен, чтобы использовать файловый режим версии 6, в UNIX только что были созданы creat(2) и open (2) только для существующих файлов. Флаг O_CREAT был добавлен к вызову совместимым способом, поэтому иногда с помощью флагов вы можете обойтись без расширений. В этом докладе отлично обсуждаются проблемы, возникающие в результате ускоренного проектирования ABI и необходимости избегать
looseness
использования расширяемого интерфейса для сохранения возможности внесения совместимых изменений — см. youtube.com/watch?v=vlozCt8WfnU
Ответ №1:
Передайте указатель на структуру…
typedef struct
{
int value;
} typeStructure;
void foo(const typeStructure * const pTypeStructure);
При typeStructure
расширении более старые версии foo
будут просто игнорировать новые поля.
Комментарии:
1. Включите версию в эту структуру, и это, безусловно, самый разумный вариант. Теперь существует четко определенная сигнатура функции. Версия сообщает вам, какие поля действительно существуют (т. Е.: не удаляйте ссылки на мусорные поля из значений unset struct!). И даже там, где поля существуют в нескольких версиях, номер версии сообщает вам, что должны означать эти поля.
2. Я думаю, что это самое чистое решение. Однако это усложняет сигнатуры функций и вызовы, которые могут быть расширены, а могут и не быть расширены в будущем. Это неприемлемое значение по умолчанию для библиотеки, если у вас нет особых серьезных ожиданий от будущих расширений. Это доказывает, что в C нет хорошего решения для этой проблемы.
3. @Zulan
However it does complicate function signatures and calls that may or may not be extended in the future.
Если вы имеете в виду, что подписьfoo
не может измениться, чтобы оставаться обратно совместимой, вы абсолютно правы.4. @FiddlingBits я понимаю, что подпись не нужно изменять, и обратная совместимость может быть реализована с использованием некоторого флага версии или зарезервированного заполнения в структуре. Я имел в виду, что если у меня будет 50 вызовов API и, вероятно, один или два изменятся в будущем, то я бы не стал разрабатывать все 50 сигнатур так, чтобы они принимали структуру вместо обычных параметров.
5. @Zulan Звучит как интересный последующий вопрос.
Ответ №2:
Вы могли бы создать переменную функцию:
void foo(int arg, ...);
В первой реализации вы просто используете первый аргумент. В будущих версиях (возможно, при условии arg
наличия определенного значения) вы можете использовать va_arg
для получения доступа к дополнительным аргументам.
Ответ №3:
Используйте foo();
. отсутствие аргумента внутри круглых скобок означает неопределенное количество аргументов, и вы можете вызвать его с любым количеством параметров.
Другим способом является функция с va_args
переменным аргументом, подобная printf()
, или переменный макрос, который вызовет ваш foo
#define FOO(...) foo(__VA_ARGS_)
Комментарии:
1. Следуя этому предложению, вы должны указать на недостатки и отсутствие проверки ошибок.