-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Windows subsystem support #1665
Changes from 3 commits
a80a40e
d5da6a2
e4a2e37
ce16e77
b89530e
8a970a3
7de8ab7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
- Feature Name: Windows Subsystem | ||
- Start Date: 2016-07-03 | ||
- RFC PR: ____ | ||
- Rust Issue: ____ | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Rust programs compiled for windows will always flash up a console window on | ||
startup. This behavior is controlled via the `SUBSYSTEM` parameter passed to the | ||
linker, and so *can* be overridden with specific compiler flags. However, doing | ||
so will bypass the rust-specific initialization code in `libstd`. | ||
|
||
This RFC proposes supporting this case explicitly, allowing `libstd` to | ||
continue to be initialized correctly. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
The `WINDOWS` subsystem is commonly used on windows: desktop applications | ||
typically do not want to flash up a console window on startup. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is more about what the user sees rather than a technical detail: ie. console applications can hide the console, but you can't stop it flashing up momentarily. The motivation being to improve the user experience. |
||
|
||
Currently, using the `WINDOWS` subsystem from rust is undocumented, and the | ||
process is non-trivial: | ||
|
||
A new symbol `pub extern "system" WinMain(...)` with specific argument | ||
and return types must be declared, which will become the new entry point for | ||
the program. | ||
|
||
This is unsafe, and will skip the initialization code in `libstd`. | ||
|
||
# Detailed design | ||
[design]: #detailed-design | ||
|
||
When an executable is linked while compiling for a windows target, it will be | ||
linked for a specific *Subsystem*. The subsystem determines how the operating | ||
system will run the executable, and will affect the execution environment of | ||
the program. | ||
|
||
In practice, only two subsystems are very commonly used: `CONSOLE` and | ||
`WINDOWS`, and from a user's perspective, they determine whether a console will | ||
be automatically created when the program is started. | ||
|
||
The solution this RFC proposes is to always export both `main` and `WinMain` | ||
symbols from rust executables compiled for windows. The `WinMain` function | ||
will simply delegate to the `main` function. | ||
|
||
The exact signature is: | ||
``` | ||
pub extern "system" WinMain( | ||
hInstance: HINSTANCE, | ||
hPrevInstance: HINSTANCE, | ||
lpCmdLine: LPSTR, | ||
nCmdShow: i32 | ||
) -> i32; | ||
``` | ||
|
||
Where `HINSTANCE` is a pointer-sized opaque handle, and `LPSTR` is a C-style | ||
null terminated string. | ||
|
||
All four parameters are either irrelevant or can be obtained easily through | ||
other means: | ||
- `hInstance` - Can be obtained via `GetModuleHandle`. | ||
- `hPrevInstance` - Is always NULL. | ||
- `lpCmdLine` - `libstd` already provides a function to get command line | ||
arguments. | ||
- `nCmdShow` - Can be obtained via `GetStartupInfo`, although it's not actually | ||
needed any more (the OS will automatically hide/show the first window created). | ||
|
||
The end result is that rust programs will "just work" when the subsystem is | ||
overridden via custom linker arguments, and does not require `rustc` to | ||
parse those linker arguments. | ||
|
||
A possible future extension would be to add additional command-line options to | ||
`rustc` (and in turn, `Cargo.toml`) to specify the subsystem directly. `rustc` | ||
would automatically translate this into the correct linker arguments for | ||
whichever linker is actually being used. | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
- Additional platform-specific code. | ||
- The difficulty of manually calling the rust initialization code is potentially | ||
a more general problem, and this only solves a specific (if common) case. | ||
- This is a breaking change for any crates which already export a `WinMain` | ||
symbol. It is likely that only executable crates would export this symbol, | ||
so the knock-on effect on crate dependencies should be non-existent. | ||
|
||
A possible work-around for this is described below. | ||
|
||
# Alternatives | ||
[alternatives]: #alternatives | ||
|
||
- Emit either `WinMain` or `main` from `libstd` based on `cfg` options. | ||
|
||
This has the advantage of not requiring changes to `rustc`, but is something | ||
of a non-starter since it requires a version of `libstd` for each subsystem. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To clarify here, the compiler generates a Also, are you thinking that this alternative is basically There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is considering the hypothetical that the I've seen similar approaches in C++ libraries which attempt to abstract over platform differences. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right now that's not actually possible with the compiler, something like a |
||
|
||
- Emit either `WinMain` or `main` from `rustc` based on `cfg` options. | ||
|
||
This would not require different versions of `libstd`, but it would require | ||
recompiling all other crates depending on the value of the `cfg` option. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand the implications here unfortunately, how come multiple versions of libstd are needed or why would other crates need to be recompiled? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this what I was thinking above where you pass something like |
||
|
||
- Emit either `WinMain` or `main` from `rustc` based on a new command line | ||
option. | ||
|
||
Assuming the command line option need only be specified when compiling the | ||
executable itself, the dependencies would not need to be recompiled were the | ||
subsystem to change. | ||
|
||
Choosing to emit one or the other means that the compiler and linker must | ||
agree on the subsystem, or else you'll get linker errors. If `rustc` only | ||
specified a `subsystem` to the linker if the option is passed, this would be | ||
a fully backwards compatible change. | ||
|
||
A compiler option is probably desirable in addition to this RFC, but it will | ||
require bike-shedding on the new command line interface, and changes to rustc | ||
to be able to pass on the correct linker flags. | ||
|
||
A similar option would need to be added to `Cargo.toml` to make usage as simple | ||
as possible. | ||
|
||
- Add a `subsystem` function to determine which subsystem was used at runtime. | ||
|
||
The `WinMain` function would first set an internal flag, and only then | ||
delegate to the `main` function. | ||
|
||
A function would be added to `std::os::windows`: | ||
|
||
`fn subsystem() -> &'static str` | ||
|
||
This would check the value of the internal flag, and return either `WINDOWS` or | ||
`CONSOLE` depending on which entry point was actually used. | ||
|
||
The `subsystem` function could be used to eg. redirect logging to a file if | ||
the program is being run on the `WINDOWS` subsystem. However, it would return | ||
an incorrect value if the initialization was skipped, such as if used as a | ||
library from an executable written in another language. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand this alternative in terms of how it might replace the detailed design section of this RFC, wouldn't this be a nice-to-have regardless of how this RFC turns out? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I originally included this in the RFC, but moved it to the alternatives (the alternative would be this RFC + the additional function) because of the issues with its implementation. It would also arguably be bad practice to use such a function. Take the logging example: it's probably better practice to just directly check whether Personally I prefer not having the function, so that the choice of subsystem has no effect whatsoever on rust code: everything's much simpler if the choice of subsystem changes one thing, and one thing only (whether a console is automatically created). However, I wanted to include it as an alternative in case someone could justify its inclusion. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm ok, perhaps in that case this "alternative" could be left out for now? |
||
|
||
- Use the undocumented MSVC equivalent to weak symbols to avoid breaking | ||
existing code. | ||
|
||
The parameter `/alternatename:_WinMain@16=_RustWinMain@16` can be used to | ||
export `WinMain` only if it is not also exported elsewhere. This is completely | ||
undocumented, but is mentioned here: (http://stackoverflow.com/a/11529277). | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I would use the terminology
allocate a console window
, due to the behavior matchingAllocConsole
.