Как создать приложение на С#, которое решит, показывать ли его как консольное или оконное приложение?
Есть ли способ запустить приложение С# со следующими функциями?
- Он определяет по параметрам командной строки, является ли это оконным или консольным приложением
- Он не отображает консоль, когда ее просят оконть и не отображает окно GUI при запуске с консоли.
Например,
myapp.exe /help
будет выводиться на stdout на используемой вами консоли, но
myapp.exe
сам запустит мой Winforms или пользовательский интерфейс WPF.
Лучшие ответы, которые я знаю до сих пор, включают в себя наличие двух отдельных exe и использование IPC, но это кажется действительно взломанным.
Какие у меня есть варианты и компромиссы, чтобы получить поведение, описанное в примере выше? Я открыт для идей, специфичных для Winform или WPF.
Ответы
Ответ 1
Сделайте приложение обычным окном и при необходимости создайте консоль на лету.
Подробнее на эта ссылка (код ниже отсюда)
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);
}
}
Ответ 2
Я в основном делаю так, как показано на примере Эрика. Кроме того, я отсоединяю консоль от 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());
}
Ответ 3
Напишите два приложения (одну консоль, одно окно), а затем напишите еще одно меньшее приложение, которое на основе указанных параметров откроет одно из других приложений (а затем, предположительно, закроется, так как оно больше не понадобится)?
Ответ 4
Я сделал это, создав два отдельных приложения.
Создайте приложение WPF с таким именем: MyApp.exe
. Создайте консольное приложение с таким именем: MyApp.com
. Когда вы вводите имя приложения в командной строке, например, MyApp
или MyApp /help
(без расширения .exe
), консольное приложение с расширением .com
будет иметь приоритет. Вы можете использовать консольное приложение MyApp.exe
в соответствии с параметрами.
Это именно то, как ведет себя devenv. Ввод devenv
в командной строке запустит Visual Studio IDE. Если вы передадите такие параметры, как /build
, он останется в командной строке.
Ответ 5
ПРИМЕЧАНИЕ. Я не тестировал это, но считаю, что это сработает...
Вы можете сделать это:
Сделайте приложение приложение форм Windows. Если вы получите запрос на консоль, не показывайте свою основную форму. Вместо использования платформы Invoke для вызова в Консольные функции в Windows API, а также выделить консоль на лету.
(В качестве альтернативы, используйте API для скрытия консоли в консольном приложении, но вы, вероятно, увидите консоль "мерцание", поскольку она была создана в этом случае...)
Ответ 6
Насколько мне известно, в exe есть флаг, который сообщает ему, запускать ли консольное или оконное приложение. Вы можете щелкнуть флагом инструментами, которые поставляются с Visual Studio, но вы не можете сделать это во время выполнения.
Если exe скомпилирован как консоль, он всегда будет открывать новую консоль, если ее не запускать с одной.
Если exe является приложением, он не может выводиться на консоль. Вы можете создать отдельную консоль - но она не будет вести себя как консольное приложение.
В прошлом мы использовали 2 отдельных exe. Консоль - это тонкая оболочка над формами (вы можете ссылаться на exe, поскольку вы ссылаетесь на dll, и вы можете использовать атрибут [assembly: InternalsVisibleTo ( "cs_friend_assemblies_2" )], чтобы доверять консольному, t должен выставить больше, чем вам нужно).
Ответ 7
Я бы создал решение, которое представляет собой приложение Windows Form, поскольку есть две функции, которые вы можете вызвать, которые будут подключаться к текущей консоли. Таким образом, вы можете рассматривать программу как консольную программу. или по умолчанию вы можете запустить графический интерфейс.
Функция 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();
}
}
Ответ 8
Возможно, эта ссылка даст некоторое представление о том, что вы хотите сделать.
Ответ 9
Один из способов сделать это - написать приложение Window, которое не отображает окно, если аргументы командной строки указывают, что это не должно быть.
Вы всегда можете получить аргументы командной строки и проверить их перед отображением первого окна.
Ответ 10
Важное значение, которое следует помнить после вызовов 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
перед вызовом To 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);
}
Надеюсь, что это поможет...
Ответ 11
Нет 1 легко.
Нет, 2 не может быть сделано, я не думаю.
В документах говорится:
Вызов методов, таких как Write и WriteLine, не имеет эффекта в приложениях Windows.
Класс System.Console инициализируется по-разному в консольных и графических приложениях. Вы можете проверить это, посмотрев класс Console в отладчике в каждом типе приложения. Не уверен, есть ли способ повторно инициализировать его.
Демо:
Создайте новое приложение Windows Forms, затем замените метод 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");
}
}
Идея состоит в том, что любые параметры командной строки будут выводиться на консоль и выходить из нее. Когда вы запускаете его без аргументов, вы получаете окно. Но когда вы запускаете его с аргументом командной строки, ничего не происходит.
Затем выберите свойства проекта, измените тип проекта на "Консольное приложение" и перекомпилируйте. Теперь, когда вы запускаете его с аргументом, вы получаете "Консоль!". Как ты хочешь. И когда вы запустите его (из командной строки) без аргументов, вы получите окно. Но командная строка не вернется, пока вы не выйдете из программы. И если вы запустите программу из Проводника, откроется окно команд, а затем вы получите окно.
Ответ 12
Я разработал способ сделать это, включая использование stdin, но я должен предупредить вас, что это не так.
Проблема с использованием stdin из прикрепленной консоли заключается в том, что оболочка также будет читать ее. Это приводит к тому, что входные данные иногда идут в ваше приложение, но иногда в оболочку.
Решение состоит в том, чтобы заблокировать оболочку в течение всего срока службы приложений (хотя технически вы можете попытаться заблокировать ее только тогда, когда вам потребуется ввод). Способ, которым я это делаю, - это отправить нажатия клавиш в оболочку, чтобы запустить команду 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();
}