Hmmmmm…… Co to jest hybrydowa aplikacja WPF? Próbowałem znaleźć jakieś inne określenie na ten problem, ale niestety to wydaje się najtrafniejsze. Pojęciem aplikacja hybrydowa WPF będę nazywał aplikację, które działa i prezentuje wyniki działania w konsoli, jak również we własnym oknie. Dodatkowo aplikacja powinna wspierać przekazywanie parametrów w trakcie startu.
Analizując przedstawione wymagania możemy spodziewać się następujących scenariuszy:
- chcemy uruchomić aplikację w trybie graficznym bez podawania parametrów,
- chcemy uruchomić aplikację w trybie graficznym z dodatkowymi parametrami,
- chcemy uruchomić aplikację w trybie konsoli bez podawania parametrów,
- chcemy uruchomić aplikację w trybie konsoli z dodatkowymi parametrami.
Analizując dalej możemy mieć do czynienie z następującymi sytuacjami:
- aplikacja zostanie uruchomiona w oknie konsoli,
- aplikacja zostanie uruchomiona z poza okna konsoli.
Otrzyma lista możliwych sytuacji od razu wskazuje, że powinniśmy zastanowić się nad dokładniejszą specyfikacją wymagań. Może okazać się, że niektóre funkcjonalności nie są potrzebne.
I rzeczywiście tak się okazało. Powyższa liczba przypadków została zredukowana do następujących dwóch scenariuszy:
- w każdej sytuacji, gdy uruchamiamy aplikację bez parametrów uruchamia się ona we własnym oknie,
- w sytuacji, gdy podane są jakiekolwiek parametry aplikacja musi odpalić się w konsoli.
Znając już dokładne wymagania można przejść do implementacji.
Pierwszą kwestią, z którą się spotykamy jest problem odczytu parametrów przekazywanych do programu. Jeśli otworzymy metodę Main() w programie WPF zobaczymy następujący kod:
/// <summary> /// Application Entry Point. /// </summary> [System.STAThreadAttribute()] [System.Diagnostics.DebuggerNonUserCodeAttribute()] public static void Main() { WpfApplication1.App app = new WpfApplication1.App(); app.InitializeComponent(); app.Run(); }
Od razu na myśl przychodzi pomysł, aby zmienić nagłówek metody na ten znany z programów pisanych na konsolę:
/// <summary> /// Application Entry Point. /// </summary> [STAThreadAttribute] [System.Diagnostics.DebuggerNonUserCodeAttribute] public static void Main(string[] args) { WpfApplication1.App app = new WpfApplication1.App(); app.InitializeComponent(); app.Run(); }
Jak się okazuje pomysł okazuje się trafiony. Po drodze napotkamy jeszcze na jeden problem z kompilacją. Otrzymamy następujący błąd:
Error 1 Program …\WpfApplication1\WpfApplication1\obj\x86\Debug\WpfApplication1.exe' has more than one entry point defined: 'WpfApplication1.Program.Main()'. Compile with /main to specify the type that contains the entry point. …\WpfApplication1\WpfApplication1 \Program.cs 15 28 WpfApplication1 Error 2 Program …\WpfApplication1\WpfApplication1\obj\x86\Debug\WpfApplication1.exe' has more than one entry point defined: 'WpfApplication1.App.Main()'. Compile with /main to specify the type that contains the entry point. …\WpfApplication1\WpfApplication1 \obj\x86\Debug\App.g.cs 61 28 WpfApplication1
O tym jak poradzić sobie z tym błędem można przeczytać w innym moim wpisie – Zaginiona metoda Main()?
Po rozwiązaniu wszystkich problemów, szkielet metody Main(string[] args) wygląda następująco:
/// <summary> /// Application Entry Point. /// </summary> [STAThreadAttribute] [System.Diagnostics.DebuggerNonUserCodeAttribute] public static void Main(string[] args) { if (args.Length == 0) { WpfApplication1.App app = new WpfApplication1.App(); app.InitializeComponent(); app.Run(); } else { // Missing code } }
Jak widać połowa wymagań jest już spełniona – jeśli nie podamy jakichkolwiek parametrów uruchomi się program we własnym oknie. Pozostało tylko rozwiązanie problemu z oknem konsoli.
W przypadku konsoli mamy do czynienie z dwoma sytuacjami:
- program odpalany jest z konsoli i wystarczy tylko podpiąć się do niej,
- program nie jest odpalany z konsoli i należy otworzyć okno konsoli samodzielnie.
W celu rozwiązania tego problemu należy sprawdzić, w jaki sposób został uruchomiony program. Jeśli okaże się, że procesem aktywnym jest konsola to oznacza to, że program został uruchomiony z konsoli i wystarczy się pod nią podpiąć. W przeciwnym przypadku należy utworzyć okno konsoli:
// Get uppermost window process IntPtr ptr = GetForegroundWindow(); int u; GetWindowThreadProcessId(ptr, out u); Process process = Process.GetProcessById(u); // Check if it is console? if (process.ProcessName == "cmd") { // Yes – attach to active console AttachConsole(process.Id); } else { // No – create new console AllocConsole(); } // Program actions ... FreeConsole();
Dodatkowo należy jeszcze zaimportować potrzebne funkcje:
[DllImport("kernel32.dll", SetLastError = true)] static extern bool AllocConsole(); [DllImport("kernel32.dll", SetLastError = true)] static extern bool FreeConsole(); [DllImport("kernel32", SetLastError = true)] static extern bool AttachConsole(int dwProcessId); [DllImport("user32.dll")] static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll", SetLastError = true)] static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
Po złożeniu wszystkiego w całość otrzymuje się następujący kod:
[DllImport("kernel32.dll", SetLastError = true)] static extern bool AllocConsole(); [DllImport("kernel32.dll", SetLastError = true)] static extern bool FreeConsole(); [DllImport("kernel32", SetLastError = true)] static extern bool AttachConsole(int dwProcessId); [DllImport("user32.dll")] static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll", SetLastError = true)] static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); /// <summary> /// Application Entry Point. /// </summary> [STAThreadAttribute] [System.Diagnostics.DebuggerNonUserCodeAttribute] public static void Main(string[] args) { if (args.Length == 0) { WpfApplication1.App app = new WpfApplication1.App(); app.InitializeComponent(); app.Run(); } else { // Get uppermost window process IntPtr ptr = GetForegroundWindow(); int u; GetWindowThreadProcessId(ptr, out u); Process process = Process.GetProcessById(u); // Check if it is console? if (process.ProcessName == "cmd") { // Yes – attach to active console AttachConsole(process.Id); } else { // No – create new console AllocConsole(); } // Program actions ... FreeConsole(); } }
Hi, thanks for your great example. I solved the issue with the additional „ENTER”. Look at my reaction on stackoverflow:
http://stackoverflow.com/questions/1305257/using-attachconsole-user-must-hit-enter-to-get-regular-command-line/33504466#33504466
Hello,
very good example, however I also had the same issue that when I started application from the console window I had to press Enter when it was done in order to quit process. What could be the problem? I do not have Console.ReadKey() anywhere or anything else that could stop it from quiting (I basically only tried to Console.WriteLine every single argument).
Thank you!
Can you post fragment of your code. With out that it is hard to tell what is wrong.
When I try this, or a slight variation that handles creation of the console in the application_startup event, I get a disturbing result when I run my application from the command line. Specifically, I’m dropped back to a command prompt immediately, after which point my application prints its status messages. How can I prevent cmd.exe from dropping me back to the command prompt until the process ends?
Could you please send me a code of application. It will be easier to answer on your question when I will see it.
Any idea why this doesn’t work from a Visual Studio Command Prompt? Everything seems to work great from a normal command prompt.
It will work from VS. You need just add Command line arguments in Project properties -> Debug -> Start Options.
Just enter there what you want and it will open console view.
After my hybrid program exited from the command line it will pause until press 'Enter’, have you seen this behavior?
What are the last lines of code in your main function? Maybe you have there some function that is waiting for user input from keyboard, e.g. Console.Readline?
I combine two apps the first one is console the second is wpf but…when i start hybrid app i see console and wpf window. my old console app had messages which i can’t print on the the new app! How to solve the problem?
Can you share here your main method?
Thank you!
It was exactly what I was looking for.