Как удалить UIApplicationMain из приложения iPhone?

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

int main() {
  library_init();
  // game init code here
  while(we_have_not_quit_the_game) {
    library_message_loop();
    library_init_render();
    // render stuff
    library_end_render();
    // update game state
  }
  library_shutdown();
}

iPhone делает это трудным, так как требует, чтобы вы вызывали функцию UIApplicationMain, которая никогда не возвращается. Просто нет способа, чтобы я мог вернуться к пользовательскому коду после library_init();.

Я не уверен, что это необходимо - там NSRunLoop, который предположительно может использоваться для обработки событий. Однако я не знаю, имеет ли значение UIApplicationMain что-то еще важное. (Обратите внимание, что я не планирую использовать .nib файлы, это единственная другая вещь, которую я обнаружил, что делает UIApplicationMain.)

У меня есть три реальных идеи, о которых я могу думать, но они все являются крупными усилиями по осуществлению, поэтому я хотел бы знать, есть ли у кого-нибудь опыт с этим, прежде чем я сгорит день, пытаясь обречь идеи.

  • В Init, создайте новый поток, запустите UIApplicationMain в этом потоке. Либо передайте все события по потокам (ть), либо просто поставьте поток UIApplicationMain в режим сна и используйте CFRunLoop в основном потоке. Я слышал, что UIApplicationMain не нравится запускаться в другом потоке.
  • Игнорировать UIApplicationMain полностью, просто используйте NSRunLoop. Будет ли отсутствовать важная настройка iPhone? Кто знает!
  • Сделайте что-то ужасное с помощью longjmp(), чтобы выскочить из кода UIApplicationMain после установки, молите, чтобы он не делал ничего важного во время срыва.

Предложения?

Ответ 1

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

Идея № 1. Оказывается, каждый NSRunLoop имеет отношение к потоку. Если я создаю UIApplicationMain в отдельном потоке, он не получит никаких сообщений. В качестве побочного эффекта это делает невозможным определить, когда он завершил инициализацию, поэтому, если там что-то не-потоковое, это просто не сработает. Я могу отправить ему сообщение по потокам, чтобы выяснить, когда он завершил инициализацию, но пока я называю это тупиком.

Идея № 2: UIApplicationMain делает много тонких вещей. Я не уверен, к чему это ограничивается, но я не смог ничего сделать без участия UIApplicationMain. Идея № 2 прямо.

Идея № 3. Получение сигналов ОС важно - вам нужно знать, есть ли наложение телефонного звонка или вы собираетесь выйти. Кроме того, некоторые сообщения об установке кажутся жизненно важными для правильного запуска приложения. Мне не удалось найти какой-либо метод для отправки сообщений, не находясь внутри UIApplicationMain. Единственными параметрами, которые я придумал, были NSRunLoop и CFRunLoop. Ни один из них не работал - сообщения не поступали так, как я хотел. Я не могу использовать это право, но в любом случае Идея № 3 отсутствует.

Совершенно новая сумасшедшая идея № 4. Возможно использование setjmp/longjmp для подделки сопрограмм в C/С++. Хитрость заключается в том, чтобы сначала установить указатель стека на какое-то значение, которое не будет поглощать ничего важного, затем начните вторую процедуру, а затем прыгайте туда-сюда, делая вид, будто у вас есть два стека. Вещи становятся немного грязными, если ваша "вторая сопрограмма" решает вернуться из своей основной функции, но, к счастью, UIApplicationMain никогда не возвращается, так что это не проблема.

Я не знаю, есть ли способ установить указатель стека явно на реальном оборудовании, скажем, на кусок данных, которые я выделил на лету. К счастью, это не имеет значения. По умолчанию iPhone имеет стек 1 МБ, что достаточно просто, чтобы вместить несколько сопрограмм.

То, что я сейчас делаю, это использовать alloca(), чтобы продвинуть указатель стека вперед на 768 килобайт, а затем порождать UIApplicationMain, а затем использовать setjmp/longjmp для возврата назад и вперед между моей "программой UI" и моей "основной рутиной", Пока это работает.

Предостережения:

  • Невозможно узнать, когда "подпрограмма UI" не имеет сообщений для обработки, и когда у нее нет сообщений для обработки, она будет просто блокироваться бесконечно, пока это больше не будет. Я решаю это, создавая таймер, который запускает каждые 0,1 миллисекунды. Каждый раз, когда таймер срабатывает, я бросаю свою "основную рутину", делаю одиночный игровой цикл, а затем возвращаюсь обратно в "режим пользовательского интерфейса" для другого таймера. Чтение документации показывает, что она не будет складывать "таймерные вызовы" на неопределенный срок. Кажется, я получаю сообщение "прекратить", хотя мне еще не удалось проверить его до сих пор, и я не тестировал никаких других важных сообщений. (К счастью, всего четыре сообщения, и один из них связан с установкой.)

  • Большинство современных ОС не будут выделять весь стек одновременно. Возможно, iPhone является одним из них. То, что я не знаю, это то, что, скажем, напасть на указатель стека 3/4 мега-форварда будет выделять все "за ним". Если это так, я могу эффективно тратить 3/4 мегабайта ОЗУ, что на iPhone значительно. Это можно было бы обработать, нажимая указатель вперед на меньшую величину, но это действительно ухаживает за размером стека - оно эффективно ограничивает ваш стек до того, как вы набросите указатель вперед, и вам придется разобраться в этом заранее. Некоторые дозорные данные в стеке, в сочетании с хорошим мониторингом и системой протоколирования для проблем с размером стека, вероятно, могут решить эту проблему, но это нетривиальная проблема. (В качестве альтернативы, если я смогу понять, как уклониться от указателя стека непосредственно на собственное оборудование, я могу просто malloc()/new [] несколько килобайт, указать на него указатель стека и использовать его в качестве нового стека. Мне нужно выяснить, сколько места ему нужно, но я сомневаюсь, что это будет много, учитывая, что это не очень много.)

  • В настоящий момент это не проверено на самом аппаратном обеспечении (дайте ему неделю или две, я сначала заработал другой проект.)

  • Я не знаю, сможет ли Apple выяснить, что я делаю, и нанести на нее гигантскую REJECTED наклейку, когда я попытаюсь отправить в магазин приложений. Это, скажем так, немного вне их намерений для API. Пальцы пересекли.

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

Позднее обновление: меня отвлекало множество других вещей. С тех пор у меня было несколько изменений, которые заставляют меня гораздо меньше интересоваться развитием Apple. Мой нынешний подход не показал признаков неработоспособности, но на самом деле у меня нет мотивации, чтобы сохранить его. Сожалею! Если я когда-нибудь передумаю, я обновлю это дальше, но Outlook не так хорош.

Ответ 2

Я знаю, что NSApplicationMain() читает Info.plist и делает что-то полезное для приложения (например, получить начальный файл nib), поэтому я бы предположил, что UIApplicationMain() делает то же самое для iPhone (например, получить начальный стиль строки состояния, масштабировать default.png и т.д.). Этот материал не подвергается нигде, поэтому функции, которые он вызывает, все равно должны запускаться для запуска приложения без каких-либо побочных эффектов. Вы только ставите на то, чтобы перепроектировать их и скопировать их (и надеяться, что все, что они делают, находится в общедоступном SDK).

Ответ 3

Является ли цель взять эту функцию main() и заставить ее работать без изменений на iPhone?

Казалось бы, вы не сможете полностью изолировать пользователей библиотеки от размышлений о платформе iPhone - им придется иметь дело с XCode для подписи кода и такого рода вещи.

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

Ответ 4

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