Подтвердить что ты не робот

Как создать библиотеку C/С++ для использования на многих клиентских языках?

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

Выбор языка

Учитывая все известные требования и подробности, я пришел к выводу, что библиотека, написанная на C или С++, - это путь. Я думаю, что основное использование моей библиотеки будет в программах, написанных на C, С++ и Java SE, но я также могу думать о причинах использовать его из Java ME, PHP,.NET, Objective C, Python, Ruby, bash и т.д.. Возможно, я не могу настроить их для всех, но если это возможно, я сделаю это.

Требования

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

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

Конечно, ответы приветствуются независимо от того, отвечают ли они моим конкретным требованиям или отвечают на вопрос в целом, что важно для более широкой аудитории!

Мои предположения, пока

Итак, вот некоторые из моих предположений и выводов, которые я собрал в последние месяцы:

  • Внутри я могу использовать все, что захочу, например. С++ с перегрузкой оператора, множественным наследованием, мета-программированием шаблонов... пока есть переносимый компилятор, который его обрабатывает (подумайте о gcc/g++)
  • Но мой интерфейс должен быть чистым интерфейсом C, который не связан с изменением имени
  • Кроме того, я думаю, что мой интерфейс должен состоять только из функций с базовыми/примитивными типами данных (и, возможно, указателями), переданными как параметры и возвращаемыми значениями
  • Если я использую указатели, я думаю, что я должен использовать их только для их возврата в библиотеку, а не для работы непосредственно в указанной памяти
  • Для использования в приложении С++ я также могу предложить объектно-ориентированный интерфейс (который также подвержен манипулированию именами, поэтому приложение должно либо использовать один и тот же компилятор, либо включить библиотеку в исходную форму).
  • Это также верно для использования в С#?
  • Для использования в Java SE/Java EE применяется родной интерфейс Java (JNI). У меня есть некоторые базовые знания об этом, но я определенно должен дважды проверить это.
  • Не все языки клиента хорошо обрабатывают многопоточность, поэтому должен быть один поток, разговаривающий с клиентом
  • Для использования на Java ME нет такой вещи, как JNI, но я могу пойти с Вложенная виртуальная машина
  • Для использования в сценариях bash должен быть исполняемый файл с интерфейсом командной строки
  • Для других языков клиента я понятия не имею
  • Для большинства языков клиентов было бы неплохо иметь интерфейс адаптера, написанный на этом языке. Я думаю, что есть инструменты для автоматического создания этого для Java и некоторых других.
  • Для объектно-ориентированных языков может быть возможно создать объектно-ориентированный адаптер, который скрывает тот факт, что интерфейс к библиотеке основан на функциях, но я не знаю, стоит ли его усилий

Возможные подзапросы

  • Это возможно с управляемыми усилиями, или это слишком большая переносимость?
  • Есть ли хорошие книги/веб-сайты об этих типах критериев дизайна?
  • Являются ли какие-либо из моих допущений неправильными?
  • какие библиотеки с открытым исходным кодом стоит изучить, чтобы учиться на их дизайне/интерфейсе/суме?
  • meta: Этот вопрос довольно длинный, вы видите какой-либо способ разбить его на несколько более мелких? (Если вы ответите на это, сделайте это как комментарий, а не как ответ)
4b9b3361

Ответ 1

В основном правильно. Прямой процедурный интерфейс - лучший. (что не совсем совпадает с C btw (**), но достаточно близко)

Я много (*) взаимодействую с DLL, как с открытым исходным кодом, так и с коммерческими, поэтому вот некоторые моменты, которые я помню из повседневной практики, обратите внимание, что это более рекомендуемые области для исследования, а не кардинальные истины:

  • Не упустите украшение и подобные "незначительные" схемы искажения, особенно если вы используете компилятор MS. В частности, соглашение stdcall иногда приводит к генерации декораций для VB ради (декорация - это как @6 после имени символа функции)
  • Не все компиляторы могут разметить все виды структур:
    • так что избегайте чрезмерного использования союзов.
    • избегать упаковки битов
    • и желательно упаковать записи для 32-битного x86. Хотя теоретически медленнее, по крайней мере, все компиляторы могут получить доступ к упакованным записям, и официальные требования к выравниванию со временем менялись по мере развития архитектуры.
  • На Windows используйте stdcall. Это значение по умолчанию для Windows DLL. Избегайте FastCall, он не полностью стандартизирован (особенно, как небольшие записи передаются)
  • Несколько советов по упрощению автоматического перевода заголовков:
    • Макросы трудно конвертировать из-за их нетипичности. Избегайте их, используйте функции
    • Определите отдельные типы для каждого типа указателя и не используйте составные типы (xtype **) в объявлениях функций.
    • следуйте мантре "определите перед использованием" настолько, насколько это возможно, это позволит избежать того, что пользователи, которые переводят заголовки, переставят их, если их язык в целом требует определения перед использованием, и облегчит для однопроходных анализаторов их перевод. Или если им нужна контекстная информация для автоматического перевода.
  • Не выставляйте больше, чем необходимо. Оставьте типы ручек непрозрачными, если это возможно. Это только вызовет проблемы с версиями позже.
  • Не возвращайте структурированные типы, такие как записи/структуры или массивы, в качестве возвращаемых функций.
  • всегда есть функция проверки версии (проще сделать различие).
  • будьте осторожны с перечислениями и логическими значениями. Другие языки могут иметь немного другие предположения. Вы можете использовать их, но хорошо документируйте, как они ведут себя и насколько они велики. Также подумайте заранее и убедитесь, что перечисления не становятся больше, если вы добавите несколько полей, сломайте интерфейс. (например, в Delphi/pascal по умолчанию логические значения 0 или 1, а другие значения не определены. Существуют специальные типы для C-подобных логических выражений (байтовый, 16-битный или 32-битный размер слова, хотя они изначально были введены для COM, не с интерфейсом))
  • Я предпочитаю stringtypes, которые указывают на char + length как отдельное поле (COM также делает это). Желательно, чтобы не полагаться на ноль прекращается. Это не только из-за соображений безопасности (переполнения), но и потому, что проще/дешевле связать их с нативными типами Delphi таким образом.
  • Память всегда создает API таким образом, чтобы поощрять полное разделение управления памятью. Я не предполагаю ничего об управлении памятью. Это означает, что все структуры в вашей библиотеке выделяются через ваш собственный менеджер памяти, и если функция передает вам структуру, скопируйте ее вместо хранения указателя, сделанного с помощью управления памятью "клиенты". Потому что вы рано или поздно случайно позвоните бесплатно или перезапустите его :-)
  • (язык реализации, а не интерфейс), неохотно меняйте маску исключений сопроцессора. Некоторые языки изменяют это как часть соответствия их стандартной обработке ошибок с плавающей запятой (exception-).
  • Всегда связывайте обратные вызовы с настраиваемым пользователем контекстом. Это может использоваться пользователем для определения состояния обратного вызова без определения глобальных переменных. (например, экземпляр объекта)
  • будьте осторожны со словом состояния сопроцессора. Это может быть изменено другими и нарушить ваш код, и если вы измените его, другой код может перестать работать. Слово состояния обычно не сохраняется/восстанавливается как часть соглашений о вызовах. По крайней мере, на практике.
  • не используйте параметры в стиле C Не все языки допускают переменное количество параметров небезопасным способом (*) Программист на Delphi в течение дня, работа, которая включает в себя взаимодействие большого количества оборудования и, следовательно, перевод заголовков SDK производителя. Ночью разработчик Free Pascal, в частности, отвечает за заголовки Windows.

(**) Это потому, что то, что "C" означает двоичный, все еще зависит от используемого компилятора C, особенно если нет реальной универсальной системы ABI. Думайте о вещах как:

  • C добавление префикса подчеркивания в некоторых двоичных форматах (a.out, Coff?)
  • иногда разные компиляторы Си имеют разные мнения о том, что делать с небольшими структурами, передаваемыми по значению. Официально они вообще не должны его поддерживать, но большинство поддерживают.
  • Упаковка структуры иногда меняется, как и детали соглашений о вызовах (например, пропуск регистров целых чисел или нет, если параметр регистрируется в регистре FPU)

===== Автоматические преобразования заголовков ====

Хотя я не очень хорошо знаю SWIG, я знаю и использую некоторые специфичные для Delphi инструменты заголовков (h2pas, Darth/headconv и т.д.).

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

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

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

Самая сложная схема, которую я сделал для Winapi commctrl и ActiveX/comctl заголовков. Там я скомбинировал IDL и заголовок C (IDL для интерфейсов, которые представляют собой набор не разбираемых макросов в C, заголовок C для остальных) и сумел набрать макросы примерно на 80% (путем распространения типов типов в sendmessage макросы обратно к объявлению макроса с разумными значениями по умолчанию (wparam, lparam, lresult)

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

Проект конвертации джедаев в winapi может иметь больше информации, они перевели около половины оконных заголовков на Delphi и, таким образом, имеют огромный опыт.

Ответ 2

Я не знаю, но если это для Windows, тогда вы можете попробовать либо прямой C-подобный API (аналогичный WINAPI), либо упаковать свой код в качестве COM-компонента: поскольку я предполагаю, что языки программирования могут захотеть чтобы иметь возможность запускать Windows API и/или использовать COM-объекты.

Ответ 3

Что касается автоматического создания обертки, подумайте об использовании SWIG. Для Java он будет выполнять всю работу JNI. Кроме того, он способен правильно переводить сложные OO-С++-интерфейсы (при условии, что вы выполняете некоторые основные рекомендации, то есть нет вложенных классов, без чрезмерного использования шаблонов, а также те, которые упомянуты Марко ван де Воортом).

Ответ 4

Думайте C, ничего больше. C - один из самых популярных языков программирования. Он широко используется на многих различных программных платформах, и существует несколько компьютерных архитектур, для которых компилятор C не существует. Все популярные языки высокого уровня предоставляют интерфейс C. Это делает вашу библиотеку доступной практически на всех платформах. Не беспокойтесь о предоставлении объектно-ориентированного интерфейса. После того, как вы создадите библиотеку в C, OOP, функциональный или любой другой интерфейс стиля можно создать на соответствующих клиентских языках. Никакой другой язык программирования системы не даст вам C гибкости и возможности.

Ответ 5

NestedVM Я думаю, что это будет медленнее, чем чистая Java из-за проверки границ массива на int [] [], которая представляет собой память виртуальной машины MIPS. Это такая хорошая концепция, но сейчас она может не работать достаточно хорошо (пока производители телефонов не добавят поддержку NestedVM (если они это сделают!), Большинство вещей сейчас будет SLOW, n'est-ce pas)? Несмотря на то, что он может распаковывать JPEG без ошибок, скорость не имеет большого значения!:)

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

Мои мысли:

Все ваши комментарии к совместимости с интерфейсом интерфейса C мне кажутся мне очень полезными, но вы, похоже, не правильно относитесь к политике управления памятью - некоторые предложения немного неоднозначны/неопределенны/неправильны. Конструкция управления памятью будет в значительной степени определяться шаблонами доступа, выполненными в вашем приложении, а не самой функциональностью. Я стараюсь, чтобы вы изучали попытки других сделать портативные интерфейсы, такие как стандартный API ANSI C, Unix API, Win32 API, Cocoa, J2SE и т.д. Тщательно.

Если бы это был я, я бы написал библиотеку в тщательно подобранном подмножестве общих элементов обычной Java и Davlik виртуальной машины Java, а также написал собственный собственный парсер, который переводит код на C для платформ, поддерживающих C, которые, конечно же, были бы большинством из них. Я бы предположил, что если вы ограничиваете себя типами данных различных типов int, bools, Strings, Dictionaries и Arrays и тщательно их используете, что поможет в межплатформенных вопросах, не влияя на производительность в большинстве случаев.

Ответ 6

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

Основная проблема, с которой я вижу, заключается в том, что люди выбирают конкретный язык и его среду выполнения, потому что их образ мышления (функциональный или объектно-ориентированный) или проблема, которую они затрагивают (веб-программирование, база данных...) соответствует этому языку так или иначе. Библиотека, реализованная в c, вероятно, никогда не будет похожа на библиотеки, к которым они привыкли, если они не программируются в c. Лично я всегда предпочел бы библиотеку, которая "чувствует себя как python", когда я использую python, и тот, который чувствует себя java, когда я делаю Java EE, хотя знаю c и С++.

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

Я также опасаюсь, что желаемая мобильность серьезно помешает развитию. Подумайте о необходимых настройках бесконечной сборки и тестах для этого. Я работал над проектом, который пытался поддерживать совместимость для 5 операционных систем (все posix-like, но все еще) и около 10 компиляторов, сборки были кошмаром для тестирования и поддержки.

Ответ 7

Дайте ему интерфейс XML, переданный как параметр и возвращаемое значение, или как файлы с помощью вызова командной строки. Это может показаться не таким прямым, как обычный функциональный интерфейс, но является наиболее практичным способом доступа к исполняемому файлу, например, Java.