Control of Swift Entry Point

Playing around with CMake and improving that led to the realization that the Windows story has one really annoying flaw in it - Swift's lack of explicit entry point. In particular, main will be synthesized by the compiler rather than written out by the user. This works great in nearly all the cases. The Darwin targets are granted privileged control via the @UIApplicationMain attribute.

And then, Windows. Windows has 4 different entry points which the user must select from:

Console Win32
ASCII main WinMain
Unicode (UTF16) wmain wWinMain

Currently, we do not have a way to access that entry point. Furthermore, the WinMain signatures have a different signature and provide a different access to the command line:

int main(int argc, char **argv);
int wmain(int argc, wchar_t **argv);
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, char *lpCmdLine, int nCmdShow);
int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wchar_t *lpCmdLine, int nCmdShow);

The interesting things here are:

  • hInstance which is the module instance which is useful for loading resources (practically speaking, GetModuleHandleA(NULL)/GetModuleHandleW(NULL) should provide a HMODULE which can be used in its stead, as long as it is invoked from the primary module's VA - that is, a library cannot call it and get that handle).
  • hPrevInstance is a relic of Windows 16-bit code and ignored
  • lpCmdLine provides access to the unprocessed command line (practically speaking, GetCommandLine/GetCommandLineW can get access to it) which can be processed by CommandLineToArgv/CommandLineToArgvW.
  • nShowCmd indicates the initial state of the window to be rendered

So, it is possible to access the information. However, beyond the signature, the actual behaviour of the application is different based on the subsystem used (Console or Win32). The console applications will launch a console window (conhost.exe which hosts the application) while the Win32 applications are proper GUI applications and have no console host associated with them. This requires that you manually hide the console window which also means that you have windows flashing when starting up. Furthermore, the type of the application is encoded into the final binary (the subsystem is a flag in the PE/COFF header).

I don't see a good way to actually control this currently. One option is to generalise the @UIApplicationMain attribute somehow to provide the ability to augment with new application main entries. This would allow a similar flow to the AppKit/UIKit application model for bridging into the Windows main loop (message pump).

CC: @jrose @Douglas_Gregor @Michael_Gottesman @graskind

CC: @Torust

You can link with /entry:[w]mainCRTStartup to use main as your entry point, regardless of subsystem. IIRC there are alternative ways to get all the other stuff WinMain passes in.

1 Like

True, that will set the user side entrypoint to main, but the internal initialization is not entirely the same IIRC. As to the alternative ways to get at most of the information - yes, I mentioned ways to handle that in the post.

That said, I still believe that the generalisation of the UIApplicationMain attribute is useful to provide a nice way to hook into the main loop.

I don't think the CRT initialization varys in any way that matters; there's not much practical difference between a console or windows subsystem process aside from whether they come pre-attached to a console. I agree that generalizing the @*Main thing would be interesting, but I think we'd want to still support writing your own main.swift too.

1 Like

Similarly, while we'd love a UTF-8 mode, I don't think a Swift application ever belongs in ASCII-only mode.

@Joe_Groff - oh, wait, so users can actually currently provide a main rather than the compiler synthesized one?

Similarly, while we'd love a UTF-8 mode, I don't think a Swift application ever belongs in ASCII-only mode.

If you use the ASCII entry points in Windows, IIRC that only means that main receives argv as an 8-bit string. It doesn't affect the process state otherwise. If you use GetCommandLineW later, you can still access the original UTF-16 command line.

@Joe_Groff - oh, wait, so users can actually currently provide a main rather than the compiler synthesized one?

Yeah, if your build contains a main.swift file, its top-level code will be used as the entry point for the program. That said, if you don't feel comfortable going the route of setting the entry point manually, it shouldn't be too hard to train SILGen to wrap the top-level code in WinMain instead of main if asked to.

Right, I'm not suggesting that we remove the ability to have a user defined entry point, I am asking if you can write:

func main(_ argc: Int, argv: String[]) -> Int {
   ...
}

It's not supported. Technically, you could probably get away with @_cdecl("main") func main(...), but we don't encourage that.

You can leverage the top level to call your main as Joe points out, which isn’t so bad imo.

func main(argc: Int, argv: [String]) {}

main(
  argc: CommandLine.argc,
  argv: CommandLine.arguments
)

Okay, so I wasn't confused on that point. I was never advocating the removal of support for main.swift, but rather that we have a way to annotate the entry point so that we generate the appropriate application type entry point. I definitely think that removal of support for main.swift would not be acceptable (as a user).

1 Like

Sorry, I wasn't intending to say you were. I was merely pointing out that having an extensible @Main mechanism wouldn't by itself solve the problem of Windows' many entry points.

Oh, absolutely, the user can of course alter the invocation and change the behaviour. I'm trying to just solve the basic case for now. Making it more robust is something that could be done subsequently (basically, draw a line in the sand and then do incremental improvements - I find it easier to work that way).

IMO, the main/WinMain thing is largely an unfortunate legacy quirk, and it'd be nice if we could make it so Swift users don't have to think about it in most cases, which is why I like the idea of driving the linker to always start at main. I think that would also simplify the design of a generalized @Main attribute, since the Main implementation would only have to provide the entry point logic, and not the entry point itself.

I think that we still need two different entry points - I am willing to edict -D_UNICODE is non-optional, but we should be able to generate both a console and a GUI application which should go through the appropriate entry point.

The entry point doesn't really matter to the console- or GUI-ness of the executable, though, nor does it lock you in or out of Unicode-ness; that's just a bit of default convention in LINK.EXE. (A Windows program doesn't need to link the CRT at all if it doesn't use C, theoretically.) We could nonetheless add support for generating any flavor of entry point to the compiler, but that wouldn't have to be exposed in the source model, since the entry point is generated by the compiler.

Correct, the entry point doesn't really matter too much - but the access to the hInstance is much trickier without the correct entry point - remember that you cannot call GetModuleW anywhere and have it do the right thing (that is, doing that in swiftCore.dll will give you the wrong value, doing that in a different library will give you the wrong handle) and so this introduces a problematic scenario.

The other question is, how do we expose this to the user? -Xlinker /SUBSYSTEM:CONSOLE and -Xlinker /SUBSYSTEM:WINDOWS seems less than ideal.

As to your point - yes, that is exactly what I am hoping to accomplish: have this be obscured by the compiler, this is not something to expose to the code model, just a way to enable the user to model the subsystem handling appropriately.

Also, Windows now has an option to set UTF-8 as the codepage for the ASCII API calls.

UTF-8%20Everywhere

1 Like