Как правильно делить проект Delphi на BPL?

Компания, с которой я работаю, разрабатывает систему в Delphi, которая содержит десятки модулей exe, и каждый из них в определенной степени идентичен, если речь идет о исходном коде. К сожалению, никто никогда не заботился о том, чтобы использовать библиотеки для размещения общего кода. Это означает, что каждый раз, когда в коде присутствуют исправления ошибок, все эти модули распространяются, программист должен внести исправления во всех них отдельно! Это всегда занимает много времени...

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

Я поместил весь код, разделяемый всеми модулями exe, в BPL, и все кажется прекрасным, но есть некоторые вещи, которые я не понимаю, и был бы благодарен, если бы вы объяснили мне это.

  • То, что я ожидал после деления кода на BPL, было то, что было бы достаточно развернуть exe файлы с созданными мной BPL. Но оказалось, что им нужны и rtl100.bpl и vcl100.bpl. Почему это так? Я хочу развернуть exes и мои BPL. Я не хочу предоставлять конечным пользователям целую кучу библиотек, поставляемых Borland и сторонними компаниями:). Я хочу, чтобы они были скомпилированы в exes, как они раньше компилировались. Возможно ли это сделать?

  • Что я сделал до сих пор:

    • Я поместил все разделяемые единицы pas в BPL. Каждая BPL содержит единицы, относящиеся к одной и той же категории, поэтому для программистов ясно, какой код следует ожидать в данной BPL.
    • Каждая BPL представляет собой библиотеку времени исполнения и времени разработки.
    • Каждая BPL "перестроена явно". Два последних являются настройками проекта по умолчанию для BPL.
  • И если речь идет о проектах exe:

    • Я удалил все единицы, которые я ранее ввел в BPL.
    • Я установил свои BPL из меню Tools- > Install package в BDS 2006.
    • В моих настройках проекта exe я проверил вариант "сборка с пакетами времени выполнения", и я перечислил все мои пакеты BPL в окне редактирования ниже (только мои пакеты, поскольку я очистил все остальные, которые появились там).

Это все, что я сделал. Проекты exe компилируются должным образом, но у меня нет доступа к исходному коду BPL (я не могу перейти в этот код из моих проектов exe), хотя все BPL хранятся вместе с файлами исходного кода. Зачем? Мне это кажется странным.

Я всегда стараюсь писать длинные описания - извините за это:). Буду признателен за вашу помощь. Мне просто нужно несколько пояснений к пунктам, которые я упомянул: развертывание exe только с моими BPL, правильность того, что я делал в целом, и невозможность перейти в исходные коды BPL. Большое вам спасибо заранее!


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

Я думал, что использование BPL будет хорошим решением, но у него есть некоторые недостатки, как некоторые из вас упомянули. Худшая проблема заключается в том, что если для каждого EXE требуется несколько BPL, наши специалисты по технической поддержке должны будут знать, какие EXE нужны, какие BPL, а затем предоставить конечным пользователям правильные файлы. До тех пор, пока у нас нет программного обеспечения для обновления, это будет много для наших технических специалистов и конечных пользователей. Они наверняка потеряются и разозлится: -/.

Также могут возникнуть проблемы совместимости - если одна BPL будет использоваться многими EXE, модификация одной BPL может быть хорошей для одного EXE и плохой для некоторых других - @Warren P.

Что мне делать, чтобы быстрее исправить ошибки в стольких проектах? Я думаю об одном из следующих подходов. Если у вас есть идеи, пожалуйста, дайте мне знать.

  • Поместите общий код в отдельные и автономные блоки pas, поэтому, когда в одном из них есть исправление ошибки, достаточно скопировать его во все проекты (перезаписать старые файлы) и перекомпилировать их все.

Это решение кажется ОК, поскольку код, модифицированный по-разному, запутан. Но у нас также есть модули pas с функциями и процедурами общего пользования, которые часто устраняют изменения - мы добавляем туда новые функции там, где это необходимо, но в отдельных проектах. Представьте себе, что вы пишете новую функцию в одном из 100 модулей и поместите ее в свой общий блок управления. Через месяц или два вы измените другой модуль, и вы считаете, что вам нужна такая же функция, которую вы написали 2 месяца назад. Вы должны найти модуль (это сложно, если вы не помните, какой он был) и скопировать функцию в ваш код. И, очевидно, общие единицы использования становятся совершенно разными в каждом модуле, если они хранятся в каждом проекте отдельно. И тогда, если есть ошибка, чтобы сделать... вся история повторяется.

  • Создайте BPL для всего общего кода, но свяжите их с EXE, чтобы EXE были автономными.

Теперь мне кажется, что это лучшее решение, но есть несколько минусов. Если я исправлю ошибку в BPL, каждый программист должен будет обновить BPL на своем компьютере. Что, если они забудут? Но все же, я думаю, что это небольшая проблема. Если мы позаботимся об информировании друг друга об изменениях, все должно быть в порядке.

  • @CodeInChaos: Я не знаю, правильно ли я вас понял. Вы имеете в виду обмен файлами pas между проектами? Как это сделать? Мы храним исходники в SVN. Это означает, что нам нужно будет хранить общий код в отдельной папке и делать все проекты для поиска этого кода, верно? И загрузка из SVN проекта и всех папок зависит от...

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

Большое спасибо.

Ответ 1

Несмотря на то, что этот вопрос имеет принятый ответ, я собираюсь нанести удар.

Название спрашивает, как разделить проект на bpls, но реальный вопрос выглядит следующим образом: "Какой лучший способ поделиться кодом между проектами?"

Есть несколько способов сделать это:

  • Общие единицы
  • Dlls
  • ВР

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

Общие единицы измерения


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

Компилятор должен иметь возможность находить ваши общие устройства. Есть 4 способа сказать компилятору, где искать.

  • Добавьте их в проект - SHIFT + F11 - добавляет ссылку на устройство в файлы проекта (dpr, dproj). IDE обычно использует относительные пути, если устройство находится под тем же деревом каталогов, что и файлы проекта, в противном случае он будет использовать абсолютные пути, что может быть проблематичным, если машины-разработчики не настроены одинаково.
  • Путь поиска проекта - CTRL + SHIFT + F11 Компилятор Delphi > Путь поиска - добавьте каталог, и компилятор будет искать там, чтобы найти единицы, упомянутые в предложении uses любого подразделения в проекте. Лучше всего использовать относительные пути, если сможете. Вы также можете использовать переменные среды: $(MyPath)
  • Глобальный путь поиска - Инструменты > Параметры > Параметры среды > Параметры Delphi > Библиотека - Win32 > Путь к библиотеке - все перечисленные здесь пути доступны для всех проектов на машине. Это зависит от машины
  • Командная строка. Если вы создаете с помощью средства автоматизации script или сборки, вы можете установить путь поиска с помощью переключателя dcc32 -U или msbuild /property:UnitSearchPath=.

Варианты 1 и 2 будут наиболее полезными.

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

Projects
    trunk
        ProjectA
        ProjectB
        ProjectC
        Library (shared units)

Если по какой-либо причине вышеуказанная структура невозможна, вы можете попробовать эту альтернативу:

ProjectA
    trunk
        Library (branch of main library)
ProjectB
    trunk
        Library (branch of main library)
ProjectC
    trunk
        Library (branch of main library)
Library
    trunk (main library)

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

Dlls


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

В то время как dll всегда связаны во время выполнения, вы решаете, загружаются ли они при запуске приложения или только при необходимости. Загрузка при запуске называется статической загрузкой, а в Delphi выполняется с помощью директивы external. Подавляющее большинство классов rtl/vcl, которые обертывают вызовы системы api, используют статическую загрузку. Динамическая загрузка позволяет отложить загрузку dll до тех пор, пока она не понадобится. Это использует функции WinAPI LoadLibrary и GetProcAddress. Соответствующий вызов FreeLibrary выгрузит dll.

К сожалению, стандартные dll ограничивают, какие типы данных могут быть переданы. Если вам нужно получить доступ к dll из проектов, отличных от Delphi, вам нужно ограничить себя использованием типов данных стиля c. Если вы будете использовать только DLL с проектами Delphi, вы можете безопасно использовать строки Delphi и динамические массивы, если вы используете блок SharedMem в dll и любые проекты, которые его используют.

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

COM (Component Object Model) хорошо поддерживается в Delphi, но у него немного кривая обучения. Потребление COM-объектов довольно просто, но для разработки потребуется некоторое время, если вы не знакомы с COM. COM имеет то преимущество, что он является нейтральным для языка и поддерживается на большинстве языков, ориентированных на платформу Windows (включая языки, ориентированные на платформу .NET).

ВР


Bpls (также называемые просто "пакетами" ) - это специально отформатированные DLL, которые облегчают работу с объектами. Как и стандартные DLL, они связаны во время выполнения и могут быть статически или динамически загружены. Их легче освоить и использовать, чем COM-библиотеки DLL, и обеспечить более сложную интеграцию в ваши проекты, чем COM. Пакеты состоят из двух частей: bpl и dcp. Dcp подобен файлам dcu, сгенерированным при компиляции файла нормального файла, за исключением того, что он содержит целую кучу единиц. Использование класса, скомпилированного в bpl, так же просто, как добавление dcp в список пакетов проекта, а затем добавление единицы в раздел uses одного из блоков проекта.

При развертывании приложения вам также потребуется установить bpl. Как и другие, вы должны включить пакет rtl как минимум и, скорее всего, пакет vcl, если вы используете какие-либо формы. Существует способ развертывания Borland, поставляемого с вашими проектами. Вы можете создать пакет "mini" rtl, который содержит только те единицы, которые необходимы вашему проекту. Трудно определить, какие единицы включать.

Резюме


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

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

Ответ 2

Представьте, что у вас есть проект с EXE и двумя разными BPL-модулями, а где-то в этой кодовой базе, есть строка, которая говорит if MyObject is TStringList then DoSomething;. Оператор is работает, исследуя метаданные класса объектов, хранящиеся в VMT, а затем следуя цепочке VMT через указатель ClassParent, чтобы увидеть, соответствует ли какой-либо из них ссылку на класс (также указатель VMT) для TStringList. Чтобы убедиться, что это будет работать правильно, необходимо, чтобы один единственный VMT для TStringList был таким же во всей вашей программе, независимо от того, сколько BPL разделено на него, что означает, что он должен быть в своем собственном пакете. Поэтому необходимо время работы системы, такое как rtl *.bpl и vcl *.bpl, и вы вряд ли сможете это сделать. Это часть цены использования BPL.

Что касается невозможности отладки, вам нужно убедиться, что BPL построены с включенной информацией об отладке и что отладчик знает, как найти папку, в которой DCP (файл, содержащий информацию об отладке для BPL), является располагается. И вы не сможете отслеживать в BPL системы, потому что DCP с поддержкой отладки не были отправлены с вашей версией. Они были добавлены совсем недавно, я думаю, что в XE, но это могло быть в D2010.

Ответ 3

Почему я не могу просматривать исходный код? Есть ли способ исправить это?

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

configuring your search path

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

Ответ 4

"В моих настройках проекта exe я проверил вариант" сборка с пакетами времени выполнения "

Вот почему вы не можете развертывать без BPL и т.д. - этот параметр запутан для многих разработчиков - "сборка с пакетами времени исполнения" означает, что вам понадобится bpl, присутствующий во время выполнения. Снимите флажок и пакеты будут связаны с вашим exe в compileTime. (Ваш exe будет g-r-o-w по размеру.) Идея создания "build with runtime packages" заключается в том, чтобы сохранить размер exe и позволить нескольким приложениям обмениваться общим bpl, потому что они НЕ связаны с exe @compileTime - это вверх. Недостаток, с которым вы сейчас сталкиваетесь - вы должны распространять свой bpl с вашим exe.