Skip to content
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

Merged
merged 7 commits into from
Oct 31, 2016
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions text/0000-windows-subsystem.md
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
Copy link
Member

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 matching AllocConsole.

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flash up -> allocate here as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify here, the compiler generates a main symbol as the very last thing when an executable is generated. This main symbol is then wired up to libstd's #[start] function. In that sense, I don't think that this would require multiple versions of libstd?

Also, are you thinking that this alternative is basically rustc --cfg subsystem=windows foo.rs or something like that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is considering the hypothetical that the main symbol generation is moved out of the compiler and into libstd - libstd would then conditionally export a main or WinMain function depending on cfg options.

I've seen similar approaches in C++ libraries which attempt to abstract over platform differences.

Copy link
Member

Choose a reason for hiding this comment

The 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 --cfg flag can't conditionally export different symbols when we're shipping a precompiled artifact, so maybe that's why I'm finding this confusing? Are you assuming here that we're compiling std from source on demand or something like that?


- 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.
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, main or WinMain is directly emitted by the compiler, but it's reusing the existing cfg system for the configuration (which avoids adding extra surface area to the CLI and Cargo.toml). The problem is that upstream dependencies may condition on that cfg option, requiring them to be recompiled for different subsystems.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this what I was thinking above where you pass something like --cfg subsystem=windows to the compiler? Sorry if I'm not following these alternatives, I'm just trying to get a better picture for what the alternatives might be in terms of technical details of how they're implemented.


- 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.
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 stdout exists before writing to it, rather than inferring it based on the subsystem.

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.

Copy link
Member

Choose a reason for hiding this comment

The 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