Wednesday, November 4, 2020

Apple's Uncanny Entry Point

     The week before last I had encountered a problem, one that I believe very few developers, if any, have tried to solve. To understand the problem we must first understand the goal. First Gateware code is meant to be compatible with 3 (currently) platforms, Windows, Linux, and macOS. Over the last few months, Chase and I have been porting Gateware to less desktop based environments, Windows UWP for him, and iOS for me. There is a subtle difference between desktop and app ecosystems that most developers don't think of off the top of their heads; entry points. Entry points serve as the beginning of any program, and most (if not all) programs need an entry point to start out. Windows (Win32), Linux, and macOS all use some form of

 int main(int argc, char** argv); 

for an entry point, but things get a little funky with app based programs.

    UWP and, if you configure it correctly,  Win32 take a different function as an entry point

 int WINAPI wWinMain(...);  

and while this may be convenient for developers of those platforms to get the extra information from the parameters of this function, it makes the life of a cross-platform developer much more difficult. To account for this change in entry, we made GApp, a class that would define whatever entry point was required on a platform, and call the usual main that the user would write. This keeps the code simple as the users of Gateware would only have to write one main for every platform. For UWP this was pretty simple, you take main as a parameter to a global constructor and run it from there, making sure to define WinMain at the same time. For iOS, this is a different story.  

    iOS does traditional main, so there's no issue right? Well there is a caveat with that. To get the storyboard to render, one must call the

 int UIApplicationMain(int argc, char * _Nullable *argv, NSString *principalClassName, NSString *delegateClassName);  

function. Well then just call this function in GApp and we're good right? Nope. Not only must this function be called on the main operating thread, it must also be called in main, or in any function that main calls (assuming that it isn't on another thread). Well, this is an issue, how can we call this function and run the code that the user supplies? You cannot multithread this function on the same thread, as it overrides regular main and as such it will never run the regular main code. And you cannot dispatch this function, as dispatch will favor UIApplicationMain over traditional main and traditional main will be put on the back burner.

    So is that it? Is there any way to have main run on the same thread put asynchronous to UIApplicationMain? I do not know. But there is an unexpected solution. With UWP, GApp defines the main entry point and calls the user-defined main later, so why can't we do the same with iOS? So we defined the traditional main entry point, and later in the file, did this

 #define main Gateware_main  
and it worked. As with the UWP GApp, the iOS GApp constructor takes the main function as a parameter and calls it later, but this time the main function that the user writes is not called, as it is now recognized by the compiler as "Gateware_main" and as such doesn't run it. Instead the compiler will see the GApp main as the entry point and run that. So we can have both functions. The GApp main handles everything for the iOS side, and then later calls the users main so that their functionality still works. This works as UIApplicationMain does require to be on the main thread and called by main, so that condition is checked, and there are none for the users main, other than it is expected to be the start of the program, so we run it as soon as the app is done launching.

     

No comments:

Post a Comment