Какой ваш любимый способ работы с кросс-платформенной разработкой?

В настоящее время я работаю над кросс-платформенными приложениями, и мне было любопытно, как другие люди решат такие проблемы, как:

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

Очевидно, что это ориентировано на такие языки, как c/С++, которые не абстрагируют большую часть этого материала (в отличие от java или С#, которые не поддерживаются во многих системах).

И если вам было любопытно, системы, которые я разрабатываю, это Nintendo DS, Wii, PS3, XBox360 и ПК.


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

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

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

Поддержка плавающей точки
В большинстве современных систем значения с плавающей запятой прекрасны, исключением является Nintendo DS (и GBA, но в наши дни это довольно мертвая платформа). Мы справляемся с этим через два разных класса. Первый - это класс с фиксированной точкой (templated, может указывать, какой тип целого использовать и сколько бит для десятичного значения), который реализует все арифметические операторы (заботясь о сдвиге бит) и автоматизирует преобразования типов. Второй - это класс с плавающей запятой, который по большей части является просто оболочкой вокруг поплавка. Единственное отличие заключается в том, что он также реализует операторы сдвига. Внедряя операторы сдвига, мы можем использовать бит-сдвиги для быстрых умножений/делений на DS, а затем плавно переходить на платформы, которые лучше работают с поплавками (например, XBox360).

Системы ввода/вывода
Это, наверное, самая сложная проблема для нас, потому что у каждой системы есть собственный метод ввода контроллера, графика (XBox360 использует вариант DirectX9, у PS3 есть OpenGL, или вы можете писать свой собственный с нуля, а DS и Wii имеют собственные собственные системы), звук и сеть (на самом деле только DS отличается в протоколе много, но тогда у каждого из них есть своя собственная серверная система, которую вы должны использовать).

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

Различия в компиляторах
Это одна вещь, с которой нельзя справиться слишком легко, так как мы сталкиваемся с проблемами с компиляторами, мы обычно регистрируем информацию в локальной вики (чтобы другие могли видеть, что искать и обходные пути, чтобы пойти с ней), и если возможно, напишите макрос, который будет обрабатывать ситуацию для нас. Хотя это не самое элегантное решение, оно работает и видит, как некоторые компиляторы просто сломаны в определенных местах, более элегантные решения, как правило, ломают компиляторы в любом случае. (Я просто хочу, чтобы все компиляторы выполнили команду Microsoft "#pragma once", что намного проще, чем обернуть все в # ifdef's)

Ответ 1

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

Ответ 2

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

Объект, который загружает данные, просматривает это значение и, если он соответствует его внутреннему представлению значения, тогда файл содержит значения native endian. Нагрузка проста оттуда.

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

Ответ 3

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

Ответ 4

Я обычно использую многоплатформенные библиотеки, такие как boost или Qt, они решают около 95% моих проблем, связанных с конкретными кодами платформы (я признаю, что единственная платформа i-m, с которой работают win-xp и linux). Для остальных 5% я обычно инкапсулирую код конкретной платформы в один или несколько классов, используя шаблон factory или общее программирование, чтобы уменьшить разделы # ifdef/# endif

Ответ 5

Я думаю, что другие ответы проделали большую работу по устранению всех ваших проблем, кроме энтузиазма, поэтому я добавлю что-то об этом... это должно быть проблемой только на ваших интерфейсах для внешнего мира. Вся ваша внутренняя обработка данных должна выполняться в соответствии с исходной спецификацией. При общении через TCP/IP (или любой другой протокол сокета) существуют функции, которые вы всегда должны использовать для преобразования ваших значений в и из сетевого байтового порядка. IIRC, функции htons() и htonl(), (хост для сети короткий, хост для сети долго) и их инверсии, которые я не могу вспомнить... возможно, что-то вроде ntohl() и т.д.?

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

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

Ответ 6

Обычно такая проблема переносимости остается в системе сборки (autotools или cmake в моем случае), которые обнаруживают специфику системы. Наконец, я получаю config.h из этой системы сборки, а затем мне просто нужно использовать константу, определенную в этом заголовке (используя IF DEFINED).

Например, здесь config.h:

/* Define to 1 if you have the <math.h> header file. */
#define HAVE_MATH_H

/* Define to 1 if you have the <sys/time.h> header file. */
#define HAVE_SYS_TIME_H

/* Define to 1 if you have the <errno.h> header file. */
#define HAVE_ERRNO_H

/* Define to 1 if you have the <time.h> header file. */
#define HAVE_TIME_H

Тогда код будет выглядеть так (например, для time.h):

#ifdef (HAVE_TIME_H)
//you can use some function from time.h
#else
//find another solution :)
#endif

Ответ 7

Для форматов данных - используйте обычный текст для всего. Для различий с компилятором помните о стандарте С++ и используйте ключи компилятора, такие как g++ -pedantic, которые предупреждают вас о проблемах с переносимостью.

Ответ 8

Это зависит от того, что вы делаете. Единственное, что почти всегда является правильным выбором, - это перенести основные вещи на любую целевую платформу, а затем справиться с ним с помощью обычного API.

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

Но это только реально работает для вещей низкого уровня. Для графического интерфейса IO высокого уровня, используя уже существующую библиотеку, определенно является лучшим вариантом почти каждый раз.

Ответ 9

Для платформ без встроенной поддержки с плавающей запятой мы использовали некоторый собственный тип фиксированной точки и некоторые typedef. Вот так:

// native floating points
typedef float Real;

или для фиксированных точек что-то вроде:

typedef FixedPoint_16_16 Real;

Тогда математические функции могут выглядеть так:

Real OurMath::ourSin(const Real& value);

Фактическая реализация может быть, конечно:

float OurMath::ourSin(const float& value)
{
  return sin(value);
}
// for fixed points something more or less trickery stuff

Ответ 10

Для таких вещей, как endianness, использующих разные функции или классы, это немного избыточные накладные расходы. попробуйте использовать предварительный процессор, например:

#ifdef INTEL_LINUX:
   code here
#endif

#ifdef SOLARIS_POWERPC
   code here
#endif