如何从 iPhone 应用程序中删除 UIApplicationMain?

发布于 2024-08-20 13:08:37 字数 1003 浏览 7 评论 0原文

我正在尝试将游戏库移植到 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 代码,祈祷它在拆卸过程中不会做任何重要的事情。

建议?

I'm trying to port a game library over to the iPhone. Unlike SDL, this library doesn't take full control of your main() function, it's communicated with via quickly-returning functions from your own code. So, for example, obvious pseudocode:

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 makes this difficult, as it requires that you call a UIApplicationMain function that never returns. There's simply no way I could ever get back to the user code after library_init();.

I'm not convinced it's necessary - there's NSRunLoop which supposedly could be used to handle the events. I don't know if UIApplicationMain does anything else of importance, however. (Note that I have no plans to use .nib files, which is the only other thing I've found that UIApplicationMain does.)

I've got three real ideas that I can think of, but they're all a major implementation effort so I'd like to know if anyone has experience with this before I burn a day trying doomed ideas.

  • In Init, spawn a new thread, run UIApplicationMain in that thread. Either communicate all events across threads (ugh) or just put the UIApplicationMain thread to sleep and use a CFRunLoop in the main thread. I've heard UIApplicationMain does not like being run in a different thread, however.
  • Ignore UIApplicationMain entirely, just use NSRunLoop. Am I going to be missing important iPhone setup? Who knows!
  • Do something horrifying with longjmp() to leap out of the UIApplicationMain code after setup, pray that it doesn't do anything important during teardown.

Suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

枯叶蝶 2024-08-27 13:08:37

看来我在这里回答我自己的问题!在我能够在真实硬件上测试它并将其放入应用程序商店之前,我不会接受我的答案。也就是说,我将在这里保留我的最新信息,包括哪些选项不起作用。

想法#1:事实证明,每个 NSRunLoop 都是特定于线程的。如果我在单独的线程中创建 UIApplicationMain,它不会收到任何消息。作为副作用,这使得无法确定何时完成初始化,因此如果它执行了任何非线程安全的操作,它就无法工作。我也许可以跨线程向它发送一条消息,以确定它何时完成初始化,但现在我称这是一个死胡同。

想法#2:UIApplicationMain 做了很多微妙的事情。我不确定它的限制是什么,但如果不涉及 UIApplicationMain,我就无法使任何事情发挥作用。想法#2 已经出来了。

想法#3:接收操作系统信号很重要 - 您需要知道是否有电话呼叫覆盖,或者您是否即将退出。最重要的是,一些设置消息对于正确启动应用程序似乎至关重要。我无法找到任何方法来在不进入 UIApplicationMain 的情况下保持消息发送。我想到的唯一选择是 NSRunLoop 和 CFRunLoop。两者都不起作用——消息没有像我想要的那样传入。我可能没有正确使用这些,但无论如何,想法 #3 已经过时了。

全新疯狂想法#4:可以使用 setjmp/longjmp 来伪造 C/C++ 中的协程。诀窍是首先将堆栈指针设置为某个不会破坏任何重要内容的值,然后开始第二个例程,然后来回跳转,假装您有两个堆栈。如果您的“第二个协程”决定从其主函数返回,事情会变得有点混乱,但幸运的是,UIApplicationMain 永远不会返回,所以这不是问题。

我不知道是否有一种方法可以在实际硬件上显式设置堆栈指针,例如我动态分配的数据块。幸运的是,这并不重要。 iPhone 默认情况下有一个 1MB 的堆栈,足以容纳几个协程。

我目前正在做的是使用 alloca() 将堆栈指针向前推进 768 KB,然后生成 UIApplicationMain,然后使用 setjmp/longjmp在我的“UI 例程”和“主例程”之间来回切换。到目前为止,这是有效的。

注意事项:

  • 不可能知道“UI 例程”何时没有消息要处理,并且当它没有消息要处理时,它将无限期地阻塞,直到情况不再如此。我通过制作一个每 0.1 毫秒触发一次的计时器来解决这个问题。每次计时器触发时,我都会退出“主例程”,执行一个游戏循环,然后返回“UI 例程”进行另一个计时器计时。阅读文档表明它不会无限期地累积“计时器调用”。我似乎确实正确地收到了“终止”消息,尽管我还没有彻底测试它,而且我还没有测试任何其他重要消息。 (幸运的是,总共只有四条消息,其中一条与设置相关。)

  • 大多数现代操作系统不会立即分配整个堆栈。 iPhone 可能就是其中之一。我不知道的是,可以这么说,将堆栈指针向前移动 3/4 的 meg 是否会分配“后面”的所有内容。如果是这样,我可能实际上浪费了 3/4 兆 RAM,这对于 iPhone 来说是非常重要的。这可以通过将指针向前移动较小的量来解决,但这实际上会导致堆栈大小灾难 - 它有效地将堆栈限制为无论您将指针向前移动多远,您都必须提前弄清楚这一点。堆栈中的一些哨兵数据,再加上良好的监控和堆栈大小问题的日志系统,可能可以解决这个问题,但这是一个重要的问题。 (或者,如果我能弄清楚如何直接在本机硬件上使用堆栈指针,我可以直接使用 malloc()/new[] 几千字节,将堆栈指针指向它,并将其用作我的新堆栈。我必须弄清楚它需要多少空间,但我怀疑它会很大,因为它没有做太多事情。)

  • 目前尚未在实际硬件上进行测试(给它一两周时间,我有另一个项目要先完成。)

  • 我不知道苹果是否会弄清楚什么当我尝试提交到应用程序商店时,我正在做并在上面贴上一个巨大的“拒绝”贴纸。我们可以说,这稍微超出了他们对 API 的意图。祈祷吧。

我会不断更新这篇文章,并在我验证它有效后正式接受它。

最新更新:我被其他各种事情分散了注意力。从那时起,我发生了一些变化,这让我对苹果开发不再那么感兴趣。我目前的方法没有显示出不起作用的迹象,但我真的没有动力继续充实它。对不起!如果我改变主意,我会进一步更新,但 Outlook 不太好。

Looks like I'm answering my own question here! I'm not accepting my answer until I've been able to both test it on real hardware and get it into the app store. That said, I'll keep my Most Up-To-Date Info here, including which options didn't work.

Idea #1: It turns out that each NSRunLoop is thread-specific. If I create a UIApplicationMain in a separate thread, it doesn't get any messages. As a side effect this makes it impossible to determine when it's finished initializing, so if there's anything non-threadsafe it does, it just won't work. I may be able to send it a message across threads to figure out when it's finished initializing, but for now I'm calling this a dead end.

Idea #2: UIApplicationMain does a lot of subtle stuff. I'm not sure what it's restricted to, but I was unable to make anything work without involving UIApplicationMain. Idea #2 is right out.

Idea #3: Receiving the OS signals is important - you need to know if there's a phone-call overlay, or whether you're about to exit. On top of that, some of the setup messages seem vital in order to start the app properly. I was unable to find any method to keep messages being sent without being inside UIApplicationMain. The only options I came up with were NSRunLoop and CFRunLoop. Neither one worked - the messages didn't come in like I wanted. I may not be using these right, but in any case, Idea #3 is out.

Brand-new crazy Idea #4: It's possible to use setjmp/longjmp to fake coroutines in C/C++. The trick is to first set the stack pointer to some value that won't clobber anything important, then start your second routine, then jump back and forth, pretending like you have two stacks. Things get a tiny bit messy if your "second coroutine" decides to return from its main function, but luckily, UIApplicationMain never returns, so this isn't a problem.

I don't know if there's a way to set the stack pointer explicitly on real hardware, say, to a chunk of data that I allocated on the fly. Luckily, it doesn't matter. The iPhone has a 1MB stack by default, which is easily enough to fit a few coroutines in.

What I'm currently doing is using alloca() to push the stack pointer ahead by 768 kilobytes, then spawning UIApplicationMain, then using setjmp/longjmp to bounce back and forth between my "UI routine" and my "main routine". So far, this is working.

Caveats:

  • It's impossible to know when the "UI routine" has no messages to handle, and when it has no messages to handle, it will just block indefinitely until that's no longer the case. I'm solving this by making a timer that triggers every 0.1 milliseconds. Every time the timer triggers, I drop out to my "main routine", do a single game loop, then head back into the "UI routine" for another timer tick. Reading the documentation indicates that it won't stack up "timer calls" indefinitely. I do seem to get the "terminate" message appropriately, though I haven't managed to test it thoroughly yet, and I haven't tested any other important messages. (Luckily, there's only four messages total, and one of them is setup-related.)

  • Most modern OSes won't allocate the entire stack at once. The iPhone is probably one of these. What I don't know is whether bumping the stack pointer 3/4 of a meg forward will allocate everything "behind it", so to speak. If so, I may be effectively wasting 3/4 of a meg of RAM, which, on the iPhone, is significant. This could be handled by bumping the pointer forward a smaller amount, but this is really courting stack size disaster - it effectively limits your stack to however far you bump the pointer ahead, and you'll have to figure this out in advance. Some sentinel data in the stack, coupled with good monitoring and a logging system for stack size issues, can probably solve this, but it's a nontrivial issue. (Alternatively, if I can figure out how to muck with the stack pointer directly on the native hardware, I can just malloc()/new[] a few kilobytes, point the stack pointer at it, and use it as my new stack. I'll have to figure out how much space it needs but I doubt it'll be much, considering that it's not doing very much.)

  • This is currently untested on the actual hardware (give it a week or two, I got another project to finish first.)

  • I have no idea whether Apple will figure out what I'm doing and slap a giant REJECTED sticker on it when I try submitting to the app store. This is, shall we say, slightly outside their intentions for the API. Fingers crossed.

I'll keep this post updated, and officially accept it once I've verified that it, you know, works.

Late update: I got distracted by a variety of other things. Since then, I've had a few changes that make me far less interested in Apple development. My current approach showed no sign of not working, but I don't really have the motivation to keep fleshing it out. Sorry! If I ever change my mind I'll update this further, but Outlook Not So Good.

暮凉 2024-08-27 13:08:37

我知道 NSApplicationMain() 读取 Info.plist 并对应用程序执行一些操作(例如获取初始 nib 文件),所以我猜测 UIApplicationMain() 对 iPhone 执行相同的操作(比如获取初始状态栏样式、缩放default.png图像等)。这些东西不会在其他地方公开,因此仍然需要运行它调用的函数才能使应用程序启动而不会产生任何副作用。你唯一的选择就是对它们进行逆向工程并复制它们(并希望他们所做的任何事情都在公共 SDK 中)。

I know NSApplicationMain() reads Info.plist and does stuff to the app (like get the initial nib file) so I would guess that UIApplicationMain() does the same for iPhone (like get the initial status bar style, zoom the default.png image in etc.). That stuff isn't exposed anywhere else, so the functions it calls would still need to be run for the app to launch with out any side effects. You're only bet is to reverse engineer those and copy them (and hope that anything they do is in the public SDK).

你是暖光i 2024-08-27 13:08:37

目标是采用 main() 函数并让它在 iPhone 上不加修改地工作吗?

看起来你不可能完全让库的用户不去思考 iPhone 平台——他们将不得不使用 XCode 来进行代码签名之类的事情。

鉴于此,告诉用户他们必须将 main() 函数分成几个部分,您可以从 applicationDidFinishLaunching 和适当的计时器中调用这些部分,这似乎不会给任何人带来太多不便。

Is the goal to take that main() function and have it work unmodified on the iPhone?

It would seem there's no way you're going to completely insulate the users of the library from thinking about the iPhone platform--they're going to have to deal with XCode for code signing and that sort of thing.

Given that, telling users they have to break their main() function up into a few pieces that you can call from applicationDidFinishLaunching and from an appropriate timer doesn't seem like it would inconvenience anybody much.

遥远的她 2024-08-27 13:08:37

为什么不从 UIApplication 中启动游戏循环? (是的,您不能将其放在 main() 中,但这并不重要。)一些委托消息是很好的候选者。

Why not start your game loop from within UIApplication? (Yes, you can't have it in main() but that shouldn't matter.) Some of the delegate messages are good candidates.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文