如何创建一个 C# 应用程序来决定自己是显示为控制台还是窗口应用程序?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/807998/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I create a C# app that decides itself whether to show as a console or windowed app?
提问by Matthew
Is there a way to launch a C# application with the following features?
有没有办法启动具有以下功能的 C# 应用程序?
- It determines by command-line parameters whether it is a windowed or console app
- It doesn't show a console when it is asked to be windowed and doesn't show a GUI window when it is running from the console.
- 它通过命令行参数确定它是窗口应用程序还是控制台应用程序
- 当它被要求窗口化时它不显示控制台,并且当它从控制台运行时不显示 GUI 窗口。
For example,
例如,
myapp.exe /help将输出到您使用的控制台上的 stdout,但是
myapp.exe本身会启动我的 Winforms 或 WPF 用户界面。
到目前为止,我所知道的最佳答案涉及使用两个单独的 exe 并使用 IPC,但这感觉真的很糟糕。
What options do I have and trade-offs can I make to get the behavior described in the example above? I'm open to ideas that are Winform-specific or WPF-specific, too.
为了获得上述示例中描述的行为,我有哪些选择和权衡取舍?我也愿意接受特定于 Winform 或 WPF 的想法。
采纳答案by Eric Petroelje
Make the app a regular windows app, and create a console on the fly if needed.
使该应用程序成为常规的 Windows 应用程序,并在需要时即时创建一个控制台。
More details at this link(code below from there)
此链接中的更多详细信息(下面的代码来自那里)
using System;
using System.Windows.Forms;
namespace WindowsApplication1 {
static class Program {
[STAThread]
static void Main(string[] args) {
if (args.Length > 0) {
// Command line given, display console
if ( !AttachConsole(-1) ) { // Attach to an parent process console
AllocConsole(); // Alloc a new console
}
ConsoleMain(args);
}
else {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
private static void ConsoleMain(string[] args) {
Console.WriteLine("Command line = {0}", Environment.CommandLine);
for (int ix = 0; ix < args.Length; ++ix)
Console.WriteLine("Argument{0} = {1}", ix + 1, args[ix]);
Console.ReadLine();
}
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool AllocConsole();
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool AttachConsole(int pid);
}
}
回答by TheTXI
Write two apps (one console, one windows) and then write another smaller app which based on the parameters given opens up one of the other apps (and then would presumably close itself since it would no longer be needed)?
编写两个应用程序(一个控制台,一个窗口),然后编写另一个较小的应用程序,该应用程序根据给定的参数打开其他应用程序之一(然后可能会关闭自己,因为不再需要它)?
回答by Reed Copsey
NOTE: I haven't tested this, but I believe it would work...
注意:我还没有测试过这个,但我相信它会起作用......
You could do this:
你可以这样做:
Make your app a windows forms application. If you get a request for console, don't show your main form. Instead, use platform invoke to call into the Console Functionsin the Windows API and allocate a console on the fly.
使您的应用程序成为 Windows 窗体应用程序。如果您收到控制台请求,请不要显示您的主表单。相反,使用平台调用调用Windows API 中的控制台函数并动态分配控制台。
(Alternatively, use the API to hide the console in a console app, but you'd probably see the console "flicker" as it was created in this case...)
(或者,使用 API 在控制台应用程序中隐藏控制台,但您可能会看到控制台“闪烁”,因为它是在这种情况下创建的......)
回答by Shea
One way to do this is to write a Window app that doesn't show a window if the command line arguments indicate it shouldn't.
一种方法是编写一个 Window 应用程序,如果命令行参数表明它不应该显示窗口,则该应用程序不显示。
You can always get the command line arguments and check them before showing the first window.
在显示第一个窗口之前,您始终可以获取命令行参数并检查它们。
回答by Colin
As far as I am aware there is a flag in the exe that tells it whether to run as console or windowed app. You can flick the flag with tools that come with Visual Studio, but you cann't do this at runtime.
据我所知,exe 中有一个标志,告诉它是作为控制台还是窗口应用程序运行。您可以使用 Visual Studio 附带的工具轻弹标志,但不能在运行时执行此操作。
If the exe is compiled as a console, then it will always open a new console if its not started from one. If the the exe is an application then it can't output to the console. You can spawn a separate console - but it won't behave like a console app.
如果 exe 被编译为控制台,那么它总是会打开一个新的控制台,如果它不是从一个控制台启动的。如果 exe 是应用程序,则无法输出到控制台。您可以生成一个单独的控制台 - 但它不会像控制台应用程序那样运行。
I the past we have used 2 separate exe's. The console one being a thin wrapper over the forms one (you can reference an exe as you would reference a dll, and you can use the [assembly:InternalsVisibleTo("cs_friend_assemblies_2")] attribute to trust the console one, so you don't have to expose more than you need to).
我过去我们使用过 2 个单独的 exe。控制台是表单的一个薄包装器(您可以像引用 dll 一样引用 exe,并且可以使用 [assembly:InternalsVisibleTo("cs_friend_assemblies_2")] 属性来信任控制台,因此您不不必暴露比你需要的更多)。
回答by Anthony Brien
I've done this by creating two separate apps.
我通过创建两个单独的应用程序来做到这一点。
Create the WPF app with this name: MyApp.exe
. And create the console app with this name: MyApp.com
. When you type your app name in the command line like this MyApp
or MyApp /help
(without .exe
extension) the console app with the .com
extension will take precedence. You can have your console application invoke the MyApp.exe
according to the parameters.
使用以下名称创建 WPF 应用程序:MyApp.exe
. 并创建该名称的控制台应用程序:MyApp.com
。当您像这样MyApp
或MyApp /help
(没有.exe
扩展名)在命令行中键入您的应用程序名称时,带有.com
扩展名的控制台应用程序将优先。您可以让您的控制台应用程序MyApp.exe
根据参数调用。
This is exactly how devenv behaves. Typing devenv
at the command line will launch Visual Studio's IDE. If you pass parameters like /build
, it will remain in the command line.
这正是 devenv 的行为方式。devenv
在命令行键入将启动 Visual Studio 的 IDE。如果您传递类似 的参数/build
,它将保留在命令行中。
回答by user71950
No 1 is easy.
没有 1 很容易。
No 2 can't be done, I don't think.
没有 2 不能完成,我不认为。
The docs say:
文档说:
Calls to methods such as Write and WriteLine have no effect in Windows applications.
对 Write 和 WriteLine 等方法的调用在 Windows 应用程序中无效。
The System.Console class is initialized differently in console and GUI applications. You can verify this by looking at the Console class in the debugger in each application type. Not sure if there's any way to re-initialize it.
System.Console 类在控制台和 GUI 应用程序中的初始化方式不同。您可以通过查看每种应用程序类型的调试器中的 Console 类来验证这一点。不确定是否有任何方法可以重新初始化它。
Demo: Create a new Windows Forms app, then replace the Main method with this:
演示:创建一个新的 Windows 窗体应用程序,然后将 Main 方法替换为:
static void Main(string[] args)
{
if (args.Length == 0)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
else
{
Console.WriteLine("Console!\r\n");
}
}
The idea is that any command line parameters will print to the console and exit. When you run it with no arguments, you get the window. But when you run it with a command line argument, nothing happens.
这个想法是任何命令行参数都将打印到控制台并退出。当你不带参数运行它时,你会得到窗口。但是当您使用命令行参数运行它时,什么也没有发生。
Then select the project properties, change the project type to "Console Application", and recompile. Now when you run it with an argument, you get "Console!" like you want. And when you run it (from the command line) with no arguments, you get the window. But the command prompt won't return until you exit the program. And if you run the program from Explorer, a command window will open and then you get a window.
然后选择项目属性,将项目类型改为“Console Application”,重新编译。现在当你用参数运行它时,你会得到“控制台!” 像你要的那样。当你不带参数运行它(从命令行)时,你会得到窗口。但是在您退出程序之前,命令提示符不会返回。如果您从资源管理器运行该程序,则会打开一个命令窗口,然后您会看到一个窗口。
回答by Richard R
I would create a solution that is a Windows Form App since there are two functions you can call that will hook into the current console. So you can treat the program like a console program. or by default you can launch the GUI.
我将创建一个 Windows 窗体应用程序解决方案,因为您可以调用两个函数来连接到当前控制台。因此,您可以将程序视为控制台程序。或者默认情况下,您可以启动 GUI。
The AttachConsole function will not create a new console. For more information about AttachConsole, check out PInvoke: AttachConsole
AttachConsole 函数不会创建新的控制台。有关 AttachConsole 的更多信息,请查看PInvoke:AttachConsole
Below a sample program of how to use it.
下面是一个如何使用它的示例程序。
using System.Runtime.InteropServices;
namespace Test
{
/// <summary>
/// This function will attach to the console given a specific ProcessID for that Console, or
/// the program will attach to the console it was launched if -1 is passed in.
/// </summary>
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FreeConsole();
[STAThread]
public static void Main()
{
Application.ApplicationExit +=new EventHandler(Application_ApplicationExit);
string[] commandLineArgs = System.Environment.GetCommandLineArgs();
if(commandLineArgs[0] == "-cmd")
{
//attaches the program to the running console to map the output
AttachConsole(-1);
}
else
{
//Open new form and do UI stuff
Form f = new Form();
f.ShowDialog();
}
}
/// <summary>
/// Handles the cleaning up of resources after the application has been closed
/// </summary>
/// <param name="sender"></param>
public static void Application_ApplicationExit(object sender, System.EventArgs e)
{
FreeConsole();
}
}
回答by Mike Fuchs
I basically do that the way depicted in Eric's answer, additionally I detach the console with FreeConsole and use the SendKeys command to get the command prompt back.
我基本上是按照 Eric 的回答中描述的方式来做的,另外我用 FreeConsole 分离控制台并使用 SendKeys 命令来获取命令提示符。
[DllImport("kernel32.dll")]
private static extern bool AllocConsole();
[DllImport("kernel32.dll")]
private static extern bool AttachConsole(int pid);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FreeConsole();
[STAThread]
static void Main(string[] args)
{
if (args.Length > 0 && (args[0].Equals("/?") || args[0].Equals("/help", StringComparison.OrdinalIgnoreCase)))
{
// get console output
if (!AttachConsole(-1))
AllocConsole();
ShowHelp(); // show help output with Console.WriteLine
FreeConsole(); // detach console
// get command prompt back
System.Windows.Forms.SendKeys.SendWait("{ENTER}");
return;
}
// normal winforms code
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
回答by Stephen L. De Rudder
The important thing to remember to do after AttachConsole()
or AllocConsole()
calls to get it to work in all cases is:
重要的是要记住以后做AttachConsole()
或AllocConsole()
调用得到它的工作在所有情况下是:
if (AttachConsole(ATTACH_PARENT_PROCESS))
{
System.IO.StreamWriter sw =
new System.IO.StreamWriter(System.Console.OpenStandardOutput());
sw.AutoFlush = true;
System.Console.SetOut(sw);
System.Console.SetError(sw);
}
I have found that works with or without VS hosting process. With output being sent with System.Console.WriteLine
or System.Console.out.WriteLine
before call To AttachConsole
or AllocConsole
. I have included my method below:
我发现无论有没有 VS 托管过程都可以使用。输出在调用 To或之前System.Console.WriteLine
或System.Console.out.WriteLine
之前发送。我在下面包含了我的方法:AttachConsole
AllocConsole
public static bool DoConsoleSetep(bool ClearLineIfParentConsole)
{
if (GetConsoleWindow() != System.IntPtr.Zero)
{
return true;
}
if (AttachConsole(ATTACH_PARENT_PROCESS))
{
System.IO.StreamWriter sw = new System.IO.StreamWriter(System.Console.OpenStandardOutput());
sw.AutoFlush = true;
System.Console.SetOut(sw);
System.Console.SetError(sw);
ConsoleSetupWasParentConsole = true;
if (ClearLineIfParentConsole)
{
// Clear command prompt since windows thinks we are a windowing app
System.Console.CursorLeft = 0;
char[] bl = System.Linq.Enumerable.ToArray<char>(System.Linq.Enumerable.Repeat<char>(' ', System.Console.WindowWidth - 1));
System.Console.Write(bl);
System.Console.CursorLeft = 0;
}
return true;
}
int Error = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
if (Error == ERROR_ACCESS_DENIED)
{
if (log.IsDebugEnabled) log.Debug("AttachConsole(ATTACH_PARENT_PROCESS) returned ERROR_ACCESS_DENIED");
return true;
}
if (Error == ERROR_INVALID_HANDLE)
{
if (AllocConsole())
{
System.IO.StreamWriter sw = new System.IO.StreamWriter(System.Console.OpenStandardOutput());
sw.AutoFlush = true;
System.Console.SetOut(sw);
System.Console.SetError(sw);
return true;
}
}
return false;
}
I also called this when I was done in case I needed command prompt to redisplay when I was done doing output.
当我完成输出时,如果我需要命令提示符重新显示,我也会在完成时调用它。
public static void SendConsoleInputCR(bool UseConsoleSetupWasParentConsole)
{
if (UseConsoleSetupWasParentConsole && !ConsoleSetupWasParentConsole)
{
return;
}
long LongNegOne = -1;
System.IntPtr NegOne = new System.IntPtr(LongNegOne);
System.IntPtr StdIn = GetStdHandle(STD_INPUT_HANDLE);
if (StdIn == NegOne)
{
return;
}
INPUT_RECORD[] ira = new INPUT_RECORD[2];
ira[0].EventType = KEY_EVENT;
ira[0].KeyEvent.bKeyDown = true;
ira[0].KeyEvent.wRepeatCount = 1;
ira[0].KeyEvent.wVirtualKeyCode = 0;
ira[0].KeyEvent.wVirtualScanCode = 0;
ira[0].KeyEvent.UnicodeChar = '\r';
ira[0].KeyEvent.dwControlKeyState = 0;
ira[1].EventType = KEY_EVENT;
ira[1].KeyEvent.bKeyDown = false;
ira[1].KeyEvent.wRepeatCount = 1;
ira[1].KeyEvent.wVirtualKeyCode = 0;
ira[1].KeyEvent.wVirtualScanCode = 0;
ira[1].KeyEvent.UnicodeChar = '\r';
ira[1].KeyEvent.dwControlKeyState = 0;
uint recs = 2;
uint zero = 0;
WriteConsoleInput(StdIn, ira, recs, out zero);
}
Hope this helps...
希望这可以帮助...