有没有办法启动具有以下功能的C#应用程序?
例如,
myapp.exe /help将在您使用的控制台上输出到stdout,但
myapp.exe本身会启动我的Winforms或WPF用户界面。
我可以选择哪些选项并进行权衡以获得上述示例中描述的行为?我也愿意接受特定于Winform或WPF的想法。
答案 0 :(得分:57)
使应用成为常规Windows应用,并在需要时动态创建控制台。
this link的更多详细信息(以下代码)
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);
}
}
答案 1 :(得分:14)
我基本上按照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());
}
答案 2 :(得分:7)
编写两个应用程序(一个控制台,一个窗口),然后编写另一个较小的应用程序,根据给定的参数打开其中一个其他应用程序(然后可能会关闭自己,因为它将不再需要)?
答案 3 :(得分:5)
我通过创建两个单独的应用程序来完成此操作。
使用以下名称创建WPF应用:MyApp.exe
。并使用以下名称创建控制台应用程序:MyApp.com
。当您在命令行中输入应用名称时,例如此MyApp
或MyApp /help
(不带.exe
扩展名),具有.com
扩展名的控制台应用优先。您可以让控制台应用程序根据参数调用MyApp.exe
。
这正是devenv的行为方式。在命令行键入devenv
将启动Visual Studio的IDE。如果传递/build
之类的参数,它将保留在命令行中。
答案 4 :(得分:4)
注意:我没有测试过这个,但我相信它会起作用......
你可以这样做:
使您的应用成为Windows窗体应用程序。如果您收到控制台请求,请不要显示主表单。相反,使用平台调用来调用Windows API中的Console Functions并动态分配控制台。
(或者,使用API在控制台应用程序中隐藏控制台,但您可能会看到控制台“闪烁”,因为它是在这种情况下创建的......)
答案 5 :(得分:2)
据我所知,exe中有一个标志,告诉它是以控制台还是窗口应用程序运行。您可以使用Visual Studio附带的工具轻弹该标志,但是您无法在运行时执行此操作。
如果exe编译为控制台,那么如果它没有从一个控制台启动,它将始终打开一个新的控制台。 如果exe是一个应用程序,那么它无法输出到控制台。您可以生成一个单独的控制台 - 但它不会像控制台应用程序那样。
我过去我们使用了2个单独的exe。控制台一个是表单上的一个瘦包装器(你可以像引用一个dll那样引用一个exe,你可以使用[assembly:InternalsVisibleTo(“cs_friend_assemblies_2”)]属性来信任控制台一个,所以你不要必须暴露超出你需要的。)
答案 6 :(得分:2)
我会创建一个Windows窗体应用程序的解决方案,因为您可以调用两个可以挂钩到当前控制台的函数。所以你可以像控制台程序一样对待程序。或者默认情况下,您可以启动GUI。
AttachConsole功能不会创建新控制台。有关AttachConsole的更多信息,请查看PInvoke: AttachConsole
下面是如何使用它的示例程序。
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();
}
}
答案 7 :(得分:1)
也许这个link会提供一些有关您希望做什么的见解。
答案 8 :(得分:1)
执行此操作的一种方法是编写一个不显示窗口的Window应用程序,如果命令行参数表明它不应该。
在显示第一个窗口之前,您始终可以获取命令行参数并进行检查。
答案 9 :(得分:1)
在所有情况下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);
}
我发现有或没有VS主机进程的工作。在调用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;
}
当我完成输入时,如果我需要命令提示重新显示,我也会调用它。
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);
}
希望这会有所帮助......
答案 10 :(得分:0)
不容易。
没有2不能做,我不认为。
文档说:
调用Write和WriteLine等方法对Windows应用程序没有影响。
System.Console类在控制台和GUI应用程序中的初始化方式不同。您可以通过查看每种应用程序类型中调试器中的Console类来验证这一点。不确定是否有任何方法可以重新初始化它。
演示: 创建一个新的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");
}
}
这个想法是任何命令行参数都将打印到控制台并退出。当你没有参数运行它时,你会得到窗口。但是当你使用命令行参数运行它时,没有任何反应。
然后选择项目属性,将项目类型更改为“Console Application”,然后重新编译。现在,当您使用参数运行它时,您将获得“控制台!”像你要的那样。当你运行它(从命令行)没有参数时,你得到了窗口。但是在退出程序之前,命令提示符不会返回。如果您从资源管理器运行程序,将打开一个命令窗口,然后您将获得一个窗口。
答案 11 :(得分:0)
我已经找到了一种方法来做到这一点,包括使用stdin,但我必须警告你它不漂亮。
从连接的控制台使用stdin的问题是shell也会从中读取。这会导致输入有时会转到您的应用程序,但有时会转到shell。
解决方案是在应用程序生命周期内阻止shell(尽管从技术上讲,您可以尝试仅在需要输入时阻止它)。我选择这样做的方法是向shell发送键击以运行powershell命令,该命令等待应用程序终止。
顺便提一下,这也解决了应用程序终止后提示无法返回的问题。
我已经简单地尝试过从PowerShell控制台开始工作。同样的原则适用,但我没有让它执行我的命令。 PowerShell可能会进行一些安全检查以防止从其他应用程序运行命令。因为我没有使用powershell,所以我没有调查它。
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AllocConsole();
[DllImport("kernel32", SetLastError = true)]
private static extern bool AttachConsole(int dwProcessId);
private const uint STD_INPUT_HANDLE = 0xfffffff6;
private const uint STD_OUTPUT_HANDLE = 0xfffffff5;
private const uint STD_ERROR_HANDLE = 0xfffffff4;
[DllImport("kernel32.dll")]
private static extern IntPtr GetStdHandle(uint nStdHandle);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern int SetStdHandle(uint nStdHandle, IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern int GetConsoleProcessList(int[] ProcessList, int ProcessCount);
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
/// <summary>
/// Attach to existing console or create new. Must be called before using System.Console.
/// </summary>
/// <returns>Return true if console exists or is created.</returns>
public static bool InitConsole(bool createConsole = false, bool suspendHost = true) {
// first try to attach to an existing console
if (AttachConsole(-1)) {
if (suspendHost) {
// to suspend the host first try to find the parent
var processes = GetConsoleProcessList();
Process host = null;
string blockingCommand = null;
foreach (var proc in processes) {
var netproc = Process.GetProcessById(proc);
var processName = netproc.ProcessName;
Console.WriteLine(processName);
if (processName.Equals("cmd", StringComparison.OrdinalIgnoreCase)) {
host = netproc;
blockingCommand = $"powershell \"& wait-process -id {Process.GetCurrentProcess().Id}\"";
} else if (processName.Equals("powershell", StringComparison.OrdinalIgnoreCase)) {
host = netproc;
blockingCommand = $"wait-process -id {Process.GetCurrentProcess().Id}";
}
}
if (host != null) {
// if a parent is found send keystrokes to simulate a command
var cmdWindow = host.MainWindowHandle;
if (cmdWindow == IntPtr.Zero) Console.WriteLine("Main Window null");
foreach (char key in blockingCommand) {
SendChar(cmdWindow, key);
System.Threading.Thread.Sleep(1); // required for powershell
}
SendKeyDown(cmdWindow, Keys.Enter);
// i haven't worked out how to get powershell to accept a command, it might be that this is a security feature of powershell
if (host.ProcessName == "powershell") Console.WriteLine("\r\n *** PRESS ENTER ***");
}
}
return true;
} else if (createConsole) {
return AllocConsole();
} else {
return false;
}
}
private static void SendChar(IntPtr cmdWindow, char k) {
const uint WM_CHAR = 0x0102;
IntPtr result = PostMessage(cmdWindow, WM_CHAR, ((IntPtr)k), IntPtr.Zero);
}
private static void SendKeyDown(IntPtr cmdWindow, Keys k) {
const uint WM_KEYDOWN = 0x100;
const uint WM_KEYUP = 0x101;
IntPtr result = SendMessage(cmdWindow, WM_KEYDOWN, ((IntPtr)k), IntPtr.Zero);
System.Threading.Thread.Sleep(1);
IntPtr result2 = SendMessage(cmdWindow, WM_KEYUP, ((IntPtr)k), IntPtr.Zero);
}
public static int[] GetConsoleProcessList() {
int processCount = 16;
int[] processList = new int[processCount];
// supposedly calling it with null/zero should return the count but it didn't work for me at the time
// limiting it to a fixed number if fine for now
processCount = GetConsoleProcessList(processList, processCount);
if (processCount <= 0 || processCount >= processList.Length) return null; // some sanity checks
return processList.Take(processCount).ToArray();
}