dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.38k stars 4.75k forks source link

Consider providing a "GUI" version of the shared host #2455

Closed mellinoe closed 4 years ago

mellinoe commented 8 years ago

Currently, dotnet.exe unconditionally spawns a console window on Windows. This can be annoying / problematic if you are building an app which creates its own UI, or for some other reason do not want a console window to be created. It's possible to "dismiss" the console after it is spawned, but this results in an undesirable "flicker" on app startup.

In practice, this simply means linking against the WINDOWS subsystem. With CMake, this means just adding "WIN32" to the add_executable call.

As far as I'm aware, this is only a meaningful distinction on Windows. Other platforms need not change anything.

gkhanna79 commented 8 years ago

Can you share more details on what needs this?

mellinoe commented 8 years ago

This is just motivated by my own uses currently, but I expect that this will become more common now that RTM is out. The only alternative right now is to build a host yourself. The complexity of the shared host (and its friends), and the size of our runtime support list is high enough that forking is a pretty daunting task.

Anyways, I mainly just wanted to poke at our thoughts here for future use cases. If we expect that we want most apps to be using the shared host, for servicing, etc., then it probably makes sense to invest in something for this scenario eventually.

wjk commented 8 years ago

👍 I need this for my projects as well. I would recommend copying the functionality in corehost.cpp re finding and loading hostfxr.dll into a custom GUI-based entry point (i.e. an executable that uses WinMain), and trampolining into that DLL as the current corehost program does. This would be needed because GUI applications usually don't get any parameters passed to them. Hence, I would need to specify the name and path to the DLL containing the managed entry point in my entry code. All I would need to do then is to copy hostfxr.dll into the same directory as my program, and have it do the hard work of resolving the dependencies and calling into CoreCLR to start the program.

@mellinoe @gkhanna79 Would this work? I used to do something much like this when I built a GUI CoreCLR-based application way back when (before .NET CLI existed in its current form).

gkhanna79 commented 8 years ago

All I would need to do then is to copy hostfxr.dll into the same directory as my program, and have it do the hard work of resolving the dependencies and calling into CoreCLR to start the program.

Do note that copying hostfxr.dll into the application is only supported for standalone apps its related invariants - runtime is next to it, so is the jit and so on. Hostfxr.dll, when not used in standalone scenario, expects the layout in which Microsoft.NETCore.App is installed to do the same lookup.

I think what you are looking for the following:

1) Get location of hostfxr.dll on a machine 2) PInvoke a valid entrypoint just like dotnet.exe does

This will allow your GUI app to look like a native host but not get into the business of copy fundamental components all over and remain in the main supported activation scenario.

wjk commented 8 years ago

@gkhanna79 Yes, this is exactly what I need. As long as I have a solid, future-proofed, hopefully Linux-compatible way of finding hostfxr.dll, that technique will work great.

zwcloud commented 8 years ago

@gkhanna79 I'm developing a GUI framework so the built application should be a window application.

I have done that before on .NET 4.5.

  1. Create a C# project.
  2. Change output type to Windows Application image
  3. In the main() method, call CreateWindowEx via P/Invoke to create the main window. Just like what WinForm does to create its main window. I think it is related to the /SUBSYSTEM:WINDOWS and WinMain.
  4. The output exe is a window applicaiton without the console.

Here is a short example of the window applicaiton from pinvoke.net.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using WindowsInterop;

namespace HelloWin
{
    class Program
    {
        static void Main(string[] args)
        {
            IntPtr hInstance = Process.GetCurrentProcess().Handle;
            string szAppName = "HelloWin";

            WNDCLASS wndclass;

            wndclass.style = ClassStyles.HorizontalRedraw | ClassStyles.VerticalRedraw;
            wndclass.lpfnWndProc = (WndProc)((hWnd, message, wParam, lParam ) => {
                IntPtr hdc;
                PAINTSTRUCT ps;
                RECT rect;

                switch ((WM)message)
                {
                    case WM.PAINT:
                        hdc = Win32.BeginPaint (hWnd, out ps) ;
                        Win32.GetClientRect (hWnd, out rect) ;

                        Win32.DrawText (hdc, "Hello, Windows 98!", -1, ref rect,
                            Win32.DT_SINGLELINE | Win32.DT_CENTER | Win32.DT_VCENTER );

                        Win32.EndPaint(hWnd, ref ps);
                        return IntPtr.Zero;
                        break;
                    case WM.DESTROY:
                        Win32.PostQuitMessage(0);
                        return IntPtr.Zero;
                        break;
                }

                return Win32.DefWindowProc(hWnd, (WM)message, wParam, lParam);
            });

            wndclass.cbClsExtra = 0;
            wndclass.cbWndExtra = 0;
            wndclass.hInstance = hInstance;
            wndclass.hIcon = Win32.LoadIcon(IntPtr.Zero, new IntPtr((int)SystemIcons.IDI_APPLICATION));
            wndclass.hCursor = Win32.LoadCursor(IntPtr.Zero,(int)IdcStandardCursors.IDC_ARROW);
            wndclass.hbrBackground = Win32.GetStockObject(StockObjects.WHITE_BRUSH);
            wndclass.lpszMenuName = null;
            wndclass.lpszClassName = szAppName;

            ushort regResult = Win32.RegisterClass(ref wndclass);

            if (regResult == 0)
            {
                Win32.MessageBox(0, "This program requires Windows NT!", szAppName, MessageBoxOptions.IconError);
                return;
            }

            IntPtr hwnd = Win32.CreateWindowEx(
                WindowStylesEx.WS_EX_OVERLAPPEDWINDOW,
                new IntPtr((int)(uint)regResult),
                //szAppName, // window class name
                "The Hello Program", // window caption
                WindowStyles.WS_OVERLAPPEDWINDOW, // window style
                Win32.CW_USEDEFAULT, // initial x position
                Win32.CW_USEDEFAULT, // initial y position
                Win32.CW_USEDEFAULT, // initial x size
                Win32.CW_USEDEFAULT, // initial y size
                IntPtr.Zero, // parent window handle
                IntPtr.Zero, // window menu handle
                hInstance, // program instance handle
                IntPtr.Zero); // creation parameters

            if( hwnd == IntPtr.Zero )
            {
                int lastError = Marshal.GetLastWin32Error();
                string errorMessage = new Win32Exception(lastError).Message;
            }

            Win32.ShowWindow(hwnd, ShowWindowCommands.Normal );
            Win32.UpdateWindow(hwnd);

            MSG msg;
            while (Win32.GetMessage( out msg, IntPtr.Zero, 0, 0) != 0)
            {
                Win32.TranslateMessage( ref msg);
                Win32.DispatchMessage( ref msg);
            }

            return;
        }
    }
}

As for .NET Core, I thought I can run the dll as a window application like dotnet run -subsyetem:window myApp.dll or build a window application like dotnet build -subsyetem:windows.

https://github.com/aspnet/Tooling/issues/824

gkhanna79 commented 8 years ago

Current POR is for .NET Core to be targeted to enable console and cloud scenarios. While supporting GUI applications is not in the cards for now, this is good datapoint nonetheless that we should account for in our planning.

CC @richlander @blackdwarf

mellinoe commented 8 years ago

Even if it isn't our main focus currently, I think we ought to come up with a plan for how this will work. With the MSBuild SDK being designed right now, and with knowledge of the host being baked into that, we should at least come up with some sort of plan for how you would override it with your own host, even if that isn't one that we directly provide (although I still think we should do that). My fear is that we'll just ignore this and end up with a system that can't accommodate this scenario cleanly when we do want it in the future.

Adding a GUI version of the host nuget package would be very simple, FWIW. It would probably just be a single-line CMake change and then the associated packaging project additions.

gkhanna79 commented 8 years ago

I am not sure what is being baked into MSBuild about the host, but that does not sound right to me. Can you share more details?

My fear is that we'll just ignore this and end up with a system that can't accommodate this scenario cleanly when we do want it in the future.

The current design does support the flexibility to enable overriding the defaults or changing them. I think it would be prudent to first understand the various implications and needs of the GUI scenario before determining the solutions to make that happen. The key here is exposing the API from hostFXR, which is currently an internal implementation detail of the host.

mellinoe commented 8 years ago

I am not sure what is being baked into MSBuild about the host, but that does not sound right to me. Can you share more details?

I don't know how the new SDK stuff handles this. At the very least, it's always possible to create post-publish targets that rearrange things for a custom host, but things like that are always a bit fragile and hacky. Since the SDK is still being designed and worked on, I was just suggesting that we consider this scenario so that something nicer than "make a post-publish target" is an option. I was talking with @nguerrera and it sounded like this should already be manageable with the current targets that we have.

the various implications and needs of the GUI scenario

"Not creating a console window" is the only thing I'm really interested in here. I'm not really aware of other specific needs, but they could be considered separately.

dasMulli commented 8 years ago

"Not creating a console window" is the only thing I'm really interested in here.

Me too, currently working around it using editbin.exe /SUBSYSTEM:windows my.exe as a post-publish step for a self-contained app. Hacky and relies on VC++ installed but works great.

mellinoe commented 7 years ago

I was discussing this problem a bit more in the context of Avalonia who have a working (but still in-progress) version of their GUI toolkit for .NET Core.

One option that's worth considering is:

This would just leave us with one single host which could behave correctly in both scenarios. The problem with linking with the console subsystem by default is that there is no way to hide the console before it becomes visible.

Me too, currently working around it using editbin.exe /SUBSYSTEM:windows my.exe as a post-publish step for a self-contained app. Hacky and relies on VC++ installed but works great.

For what it's worth, this is what I am doing in my project as well. It is an okay workaround, but it means that I can only publish my self-contained stuff from Windows; when I do so from Ubuntu, for example, the Windows version will spawn a console window.

wjk commented 7 years ago

@mellinoe That still won't quite hit all my required feature boxes, though. I would also need a Win32 common control manifest, an icon, and a VERSIONINFO resource. While I would much prefer a public API in hostFXR I could call into directly, I could insert these resources using an MSBuild target and the Windows resource-modification APIs.

In addition, while a dual-mode EXE as you described might seem like it would work well, I beg to differ. Your idea of creating the console window at runtime breaks a key scenario for console apps: Being able to interact with the app in the console window you launched it from, as well as having the command interpreter block waiting for it to exit. Doing it this way would always spawn a new console window, which would then need to be interacted with separately, even if the user did not intend that; it would also cause the command prompt (both cmd.exe and PowerShell) to think that the command had exited immediately, breaking scripts that assume its commands always run synchronously.

mellinoe commented 7 years ago

@wjk Yes, you're absolutely correct. Upon further research my idea does not actually work, although it would have been simple and clean if possible.

dasMulli commented 7 years ago

This has been a hard-to-solve problem on windows for a long time and this is the reason java ships java.exe and javaw.exe. There isn't really a solution without a flashing console window or loosing outputting / spawning another console (and maybe nano server refusing to launch it?) - even though AttachConsole() and FreeConsole() really want to make you believe otherwise. :trollface:

In order to publish for GUI applications from any platform for both self-contained and shared framework scenarios, there'd probably be a need to introduce an additional property to the msbuild SDK and runtimeconfig.json. maybe like preferConsoleLessHost. So the shared fx would probably need to ship an additional dotnetw.exe and runtime nugets would also need to include it (or apphostw.exe). The muxer/hostfxr would then need to launch the GUI-host as another process.

For mac, it is enough to create a correct bundle layout on publish - meaning a folder ending in .app and having a Contents subdirectory with an Info.plist and PkgInfo file and an executable placed in a MacOS subdirectory of that. This would only be achievable for self-contained apps and can probably be done through custom msbuild targets.

mellinoe commented 7 years ago

@dasMulli I think it would be sufficient (at least IMO) to just provide alternative versions of dotnet.exe and apphost.exe to be deployed next to the application. I'm not very concerned about the scenario where the application is invoked through the shared / CLI host program. Doing so implies to me that you are already operating from the command line and thus don't care about the console window. It's not strictly true of course, but I'm okay with a design that assumes it. With that, we would not need to have extra configuration options in the runtimeconfig file.

The real important feature here (again, IMO) is being able to invoke the application directly, either as a "shared" app, or a fully standalone app, without creating a console window. It seems like that can be accomplished by just having a separate version of apphost.exe (call it "apphostw.exe" like you suggested) and baking some knowledge of that into the SDK regarding when it should be deployed. You can imagine that there could be a <PreferConsoleLessHost> property in the project file controlling this behavior. When set to true, the SDK targets would deploy apphostw.exe instead of apphost.exe on Windows. Or, it could be controlled through some other explicit mechanism in the tooling.

richlander commented 7 years ago

I'm happy to consider this scenario. As @gkhanna79 suggests, the team is focused on scenarios that don't require this, however, I appreciate and love the experimentation and productization we are seeing with .NET Core. I want to encourage that more and part of doing that is developing a track record of helping people do cool stuff. If we can find a good solution that doesn't add significant complication or cost to the mainline .NET Core project, I'm all for it. Do we have a concrete proposal on how to get there?

Petermarcu commented 7 years ago

I am supportive too. Seems like the leading proposals are:

  1. Provide an alternate entrypoint dotnetw that will do the right thing to avoid the command prompt.
  2. Provide a variant of dotnet in the SDK that gets copied and renamed to myapp depending on a setting in the project. Default would be what it does today. Then the value for GUIApplication could be set to true to get the alternate.

Thoughts? I'd be happy to see a community PR along these lines and as @richlander said, would like to take something like this in a way that doesn't impact any critical dates for the project.

mellinoe commented 7 years ago

"2." is something I think could be easily accomplished, assuming we agree on the particulars. There's a couple of additional things discussed above which are not necessarily restricted to GUI apps:

I can imagine that the two things above can be readily accomplished with a post-build MSBuild task. On the other hand, which native host we deploy into the project output seems like a more "baked-in" part of the SDK, and therefore could warrant special treatment (e.g. a special property that we define and interpret).

richlander commented 7 years ago

Clarification -- Is this soley for self-contained apps? The "always dotnet" is a shared fx scenario but then you wouldn't be copying.

Petermarcu commented 7 years ago

I would start with self-contained. @mellinoe , do you agree? The shared scenario I think would neccesitate something more like dotnet/core-setup#1.

mellinoe commented 7 years ago

I'm interested in the native host which is published next to all applications, both portable and standalone. I'm not really as interested in having a version of the "global dotnet" host, because the use of that implies to me that you are running from a command line in the first place.

yzrmn commented 7 years ago

Hi guys,

@mellinoe, thanks for bringing this up.

I am currently developing a purely managed, CPU-based graphics toolkit for .NET and running into the same problem that @zwcloud described with Core (console window is showing and output type 'Windows Application' does not work).

This might be OT, but I would like to propose an approach that might be better in terms of user experience for GUI applications (especially on Windows): Assemblies with entry points (console or window) are executables just like in previous .NET versions and behave in the same way (selected output type). It does the same with Mono under Linux. Could this work or would it be impossible for the runtime to distinguish between .NET and .NET Core applications?

Greetings

dasMulli commented 7 years ago

So i guess this would mean a new "branch" of RIDs? Like.. you put <RuntimeIdentifier>win7-x64-gui</RuntimeIdentifier> in your csproj and it will resolve a "gui subsystem" host on dotnet publish?

wjk commented 7 years ago

@yzrmn @mellinoe I have already accomplished what is being discussed (mark as GUI subsystem, add resources to entry point) with an MSBuild task in a private repo of mine. No modifications to .NET Core itself are required, and Visual Studio still debugs the application just fine. I will work on moving the code into a public repo sometime in the near future. Thanks!

mellinoe commented 7 years ago

So i guess this would mean a new "branch" of RIDs? Like.. you put win7-x64-gui in your csproj and it will resolve a "gui subsystem" host on dotnet publish?

I don't think we need anything so complicated. I was anticipating we could just ship apphostw.exe in the existing Windows package next to apphost.exe. The SDK already understands that it needs to deploy and rename this file for published executable projects, so we should be able to easily modify it to deploy the other one instead, based on a project property.

I have already accomplished what is being discussed (mark as GUI subsystem, add resources to entry point) with an MSBuild task in a private repo of mine.

Could you outline how you are doing this? Using Win32 API's to accomplish it is kind of a non-starter, because they can't be used outside of Windows.

wjk commented 7 years ago

@mellinoe I have just finished open-sourcing my .NET Core GUI-entry-point solution. Here it is! Note that my implementation of this task requires both Win32 APIs and Visual Studio, simply because I found that invoking rc.exe and link.exe is easier and less error-prone than trying to assemble the resource data myself. However, even if I did assemble the data myself, I would still need to use Win32 APIs to copy it into the generated EXE.

Since I wrote this code as part of a Windows Forms-like GUI framework for .NET Core, I found that seeing as applications built referencing this library won't run on non-Windows platforms, not being able to run these targets on a non-Windows platform seems like an OK tradeoff to me. In addition, I have coded my targets file to ignore the targets referencing these APIs when run on Linux or macOS, to prevent dotnet build from failing on those platforms. (The code for that GUI framework is in the repository linked above, by the way, if you're interested.)

mellinoe commented 7 years ago

I've implemented a version of this, roughly as described above. There's two pieces involved, one in this repo and one in dotnet/sdk. Overall it only needed a few small changes and is fairly isolated.

dotnet/core-setup: https://github.com/mellinoe/core-setup/commit/dacf01d0a1445e0d36b0ba23a556caa919f2d393

dotnet/sdk: https://github.com/mellinoe/sdk/commit/563c626e8630c723e55dfad1fa70bcae1b90c401

The result is that you get a console-less app if you put <UseGuiHost>true</UseGuiHost> in your project, and a regular console app otherwise.

nguerrera commented 7 years ago

@mellinoe Cool!

If you put <UseGuiHost>true</UseGuiHost> in your project

I'm thinking we should use OutputType=WinExe, which controls this for .NET framework exectuables. We have some bugs around it in the SDK (see dotnet/sdk#1176), but we're going to get those fixed. The nice things there beyond consistency with .NET Framework is that we can get VS property page support for free.

tannergooding commented 7 years ago

It would be great if we "just worked" with the existing OutputType=WinExe (especially since this essentially resolves down to a compiler switch that sets a PE Header flag).

It would be even better if we respected this flag on all platforms and not just on Windows.

mellinoe commented 7 years ago

@nguerrera Yeah, OutputType=WinExe is probably the obvious choice here. My only hangup is the fact that it makes you put a "windows-ism" into your project file. That's just a small problem, though; it's probably overcome by the fact that years of tooling understand "WinExe".

@tannergooding What would respecting this flag look like on other platforms? As far as I can tell, there is no such distinction for executables on other systems, only Windows. There is some discussion about producing a macOS bundle above, but I'm not as familiar with those. It seems like they are only tangentially related. Was there something concrete you were thinking of with respect to other platforms?

tannergooding commented 7 years ago

@mellinoe, I would imagine it would do (or attempt to do) the same thing that Windows does. Launching a "regular" executable would cause a terminal window to appear for all output (or use the current terminal if launched from there). Launching a "WinExe" executable would not cause any terminal window to appear and would require the end user to construct and display a window, if desired.

Also, I think "WinExe" should be fine, as you could say it stands for "Windowed Executable" (that is an executable which has a graphical window) rather than "Windows Executable" (which is an executable specific to the Windows Operating System)

dasMulli commented 7 years ago

Sadly, I think a lot of tools/"SDKs" picked up conditioning on OutputType being Exe. It also seems weird that if I want to multi-target for multiple TFMs and RIDs, I would need to condition the output type on two other properties. (inverse: it would seem weird if it just worked)

dasMulli commented 7 years ago

@tannergooding I think this console-window experience is pretty much a vs-specific thing.. most other IDEs have integrated console windows and route the stdin/out through it. Even for "Gui type" apps, or mobile apps that run in simulators or attached devices.

Furthermore, I believe the ability to select a different app host is more a deployment-time choice than an app model selection - just like $(SelfContained) (2.0 tooling).

danwalmsley commented 7 years ago

I am also in need of this feature 👍

steveharter commented 7 years ago

@mellinoe and I had a discussion on this, using an approach similar to what he has already prototyped.

Tentatively flagging this as 2.1 until we can fully prioritize and schedule that work.

steveharter commented 6 years ago

Moving to 2.2; a custom build task to re-write the peheader in the exe is probably the least intrusive.

steveharter commented 6 years ago

See also https://github.com/dotnet/core-setup/issues/230 which requested version, icon and SxS manifest

wjk commented 6 years ago

@steveharter Note that since I posted in dotnet/core-setup#230, I have figured out how to insert the required resources using the Win32 resource-modification APIs. (I also use dumpbin.exe to change the command-line shared host into a GUI shared host.) Therefore, this is a pretty low-priority request for me now. Thanks!

steveharter commented 6 years ago

Linking issue https://github.com/dotnet/cli/issues/6237 because that also has a need for a app-specific named exe (not the shared dotnet.exe) along with shared framework semantics. Today you can only have an app-specific exe for standalone apps.

Having a named exe for a GUI app (v.s. dotnetw.exe or equivalent) would also allow an icon and other app-specific resources to be embedded (for Windows anyway).

xoofx commented 6 years ago

I'm also super interested by this feature! (Mostly for AOT and Standalone apps)

As suggested earlier by @nguerrera I would be in favor of <OutputType>WinExe</OutputType>. Even with a PE rewriter, @steveharter what's the decision around this? (would like to make sure that it will fit with well with CoreRT story...)

steveharter commented 6 years ago

With the 3.0 proposed support for WPF and WinForms, setting 3.0 milestone.

Basic functionality would include (on Windows): 1) Support for standalone and framework-dependent executables via a project setting \ CLI option. This will ensure the PE header change to make it GUI not console. 2) Support for adding icon(s) and other resources including Title, Description, etc. This needs to change the embedded resources of apphost.exe.

Optional \ TBD: 3) A mechanism to launch the .dll without an apphost (i.e. not using the native executable ) This may include adding a dotnetw.exe as mentioned earlier. At a minimim, this is useful for using the wdotnet.exe muxer with dlls and to continue to support the muxer for F5 (non-publish) standalone-apps for performance so we don't have to (re)deploy the entire application to run\test each time 4) Consider OSX and some Linux variants

ericstj commented 6 years ago

See also https://github.com/dotnet/sdk/issues/1899

jkotas commented 6 years ago

The plan is to have apphost .exe by default for GUI apps. It makes the GUI shared host unnecessary.

mellinoe commented 6 years ago

@jkotas Could we close the loop on this one with some instructions on how to enable the new functionality? For example -- I have existing projects that should be GUI projects. What do I need to change to make that happen?

nguerrera commented 6 years ago

You will get an apphost by default, and it will be GUI if OutputType is WinExe. This is already happening in 3.0 daily builds, which you can try from https://github.com/dotnet/core-sdk

jkotas commented 6 years ago

What do I need to change to make that happen?

As Nick said, you need to update to netcoreapp3.0 and build. That's all.

mellinoe commented 6 years ago

Thanks, that worked like a charm.

dasMulli commented 6 years ago

Shouldn't it even work with netcoreapp < 3.0? AFAIK there is code in the SDK that just flips the subsystem bit after copying the apphost and embedding the dll name

nguerrera commented 6 years ago

Yes, I believe so, as long as you use the 3.0 sdk.

Cc @peterhuene