Рекомендации по повторному использованию встроенных C?

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

Каковы наилучшие методы привязки библиотеки к каждому проекту?

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

До сих пор я решил сохранить библиотеку централизованно, но для нее требуется конкретный проект libraryConfig.h, который включает определения функций, макросы и т.д. Это требует, чтобы библиотека включала заголовок в свой собственный код, что означает, что исходный каталог проекта должен быть включен в путь include (а не только исходный каталог библиотеки). Это различие между #include "" и #include <>, не так ли?

Это как обычно?

Ответ 1

Очень хороший вопрос, и ответ не прост. Несколько вещей, чтобы рассмотреть. Вот несколько мнений из моего опыта.

Общий код и проект-локальная копия

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

Это подробно обсуждается в этом SO-вопросе.

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

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

Чтобы получить хорошее значение из общего кода, например, центральной библиотеки повторного использования:

Библиотека:

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

Индивидуальные проекты:

  • не следует автоматически и слепо получать "последние", но должен иметь возможность получить конкретную "версию" с указанным номером версии. Тогда проекты должны иметь контроль над тем, когда/когда они будут обновлены до более новой версии. Проект должен иметь возможность четко отслеживать "мы используем версию 1.2.3 библиотеки xyz".
  • следует избегать "разветвления" кода библиотеки, если это вообще возможно. Например. не добавляйте специфические для проекта "функции" в код библиотеки.
  • должен отслеживать любые локальные изменения в библиотечном коде
  • должен учитывать ошибки как ошибки библиотек, которые могут быть исправлены в центральной библиотеке, если это вообще возможно. Компания должна иметь процессы для их исправления в центральной библиотеке, протестировать библиотеку со своим набором unit test (возможно, улучшая модульные тесты, чтобы поймать ошибку в будущем). Затем при необходимости создайте новую версию центральной библиотеки и разверните ее в другие проекты, если/когда эти проекты будут соответствовать требованиям.

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

Конфигурация, специфичная для проекта

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

Для хорошего примера того, как это можно сделать, см. μC/OS-II RTOS (книга) от Jean Labrosse, от Micrium.

Ответ 2

Это не испортит различие, которое в любом случае практически полностью определено платформой. Единственное определенное поведение заключается в том, что если include, использующий "", не может найти файл, он снова ищет, как если бы вы сказали <>.

Я думаю, вы поступаете правильно. По моему опыту, обычный способ обработки заголовка, специфичного для платформы, заключается в том, что вы даете ему имя, которое вы как можно более уверены, никогда не столкнется ни с чем другим, а # включите его с помощью "". Затем вы говорите платформеру портье, чтобы сделать все необходимое для компилятора, чтобы убедиться, что оно найдено. Обычно это просто означает указание некоторого аргумента компилятора, такого как -I, где бы он ни захотел сохранить файл. Так что да, один из его проектных каталогов. Но если все остальное не удается, он всегда может скопировать свой файл в место, где будет выглядеть его компилятор. Он мог даже скопировать его в свою локальную копию источника вашей библиотеки, если его компилятор необоснованно усложняет все это.

Другой способ - иметь файл в библиотеке, selectplatform.h, выглядящий следующим образом:

// obviously WIN32 isn't an embedded platform, and GCC is too broad
// to be supported by a single header file. Replace with whatever platforms
// it is you do support out of the box.
#if _WIN32
    #include "platforms/msvc32.h"
#elif __GNUC__
    #include "platforms/gcc.h"
#else
    #error "You must add a new clause to selectplatform.h for your platform"
#endif

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

Ответ 3

Нет.
Обычно вы определяете путь к вашему каталогу lib, используя флаг команды в вашем компиляторе (обычно это флаг -I).

Скажем, если вы используете компилятор GCC, а ваши файлы заголовков библиотеки находятся в каталоге

/usr/local/include/mylibheaders

тогда вы должны вызвать компилятор со следующей опцией:

-I/usr/local/include/mylibheader/mycurrentplatform

где каталог mycurrentplatform отличается для каждого проекта и содержит специфичную для проекта библиотекуConfig.h

Таким образом, вы можете использовать #include<libraryConfig.h> в каждом проекте.

Ответ 4

Это скорее вопрос управления конфигурацией, чем вопрос C. По моему опыту, использование хорошей программы контроля версий является наиболее полезным. Найдите тот, который позволяет вам определить "проект", потянув исходный код из нескольких разных мест. Поймите, что ваше определение программы управления версиями "проекта" станет важным элементом при создании проекта.

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

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