web-dev-qa-db-fra.com

Un exécutable peut-il être à la fois une console et une application graphique?

Je souhaite créer un programme C # pouvant être exécuté en tant qu'application CLI ou GUI en fonction des indicateurs qui y sont transmis. Cela peut-il être fait?

J'ai trouvé ces questions, mais elles ne couvrent pas exactement ma situation:

72
BCS

La réponse de Jdigital pointe vers le blog de Raymond Chen , ce qui explique pourquoi vous ne pouvez pas avoir une application qui soit à la fois un programme console et un programme non console*: le système d'exploitation doit savoir avant le démarrage du programme en cours d'exécution quel sous-système utiliser. Une fois que le programme a démarré, il est trop tard pour revenir en arrière et demander l’autre mode.

La réponse de Cade pointe vers un article sur l'exécution d'une application .Net WinForms avec une console . Il utilise la technique consistant à appeler AttachConsole après le démarrage du programme. Cela a pour effet de permettre au programme d'écrire dans la fenêtre de la console de la commande Invite qui a lancé le programme. Mais les commentaires de cet article soulignent ce que je considère être une faille fatale: le processus enfant ne contrôle pas vraiment la console. _ La console continue d'accepter les entrées de la part du processus parent et le processus parent n'est pas conscient qu'il doit attendre que l'enfant ait fini de s'exécuter avant d'utiliser la console pour autre chose.

L'article de Chen pointe vers un article de Junfeng Zhang qui explique quelques autres techniques .

Le premier est ce que (devenv} _ utilise. Cela fonctionne en ayant réellement deux programmes. L'un est devenv.exe, qui est le programme d'interface graphique principal, et l'autre est devenv.com, qui gère les tâches en mode console, mais s'il est utilisé dans un environnement autre que console. De la même manière, il transmet ses tâches à devenv.exe et quitte. La technique repose sur la règle Win32 selon laquelle les fichiers com sont choisis avant les fichiers exe lorsque vous tapez une commande sans l'extension de fichier.

Windows Script Host propose une variante plus simple à cet égard. Il fournit deux fichiers binaires complètement séparés, wscript.exe et cscript.exe. De même, Java fournit Java.exe pour les programmes de console et javaw.exe pour les programmes non-console.

La deuxième technique de Junfeng est ce que ildasm utilise. Il cite le processus suivi par l'auteur de ildasm pour le faire fonctionner dans les deux modes. En fin de compte, voici ce que cela fait:

  1. Le programme est marqué comme un binaire en mode console, il commence donc toujours par une console. Cela permet à la redirection d’entrée et de sortie de fonctionner normalement.
  2. Si le programme n'a pas de paramètre de ligne de commande en mode console, il se relance de lui-même.

Il ne suffit pas d'appeler simplement FreeConsole pour que la première instance cesse d'être un programme de console. En effet, le processus qui a démarré le programme, cmd.exe, "sait" qu'il a démarré un programme en mode console et attend que le programme cesse de s'exécuter. L'appel de FreeConsole ferait ildasm arrêter d'utiliser la console, mais le processus parent ne ferait pas démarrer utiliser la console.

Ainsi, la première instance redémarre d'elle-même (avec un paramètre de ligne de commande supplémentaire, je suppose). Lorsque vous appelez CreateProcess, il existe deux indicateurs différents à essayer, DETACHED_PROCESS ET CREATE_NEW_CONSOLE , l'un ou l'autre garantissant que la deuxième instance ne sera pas attachée à la console parent. Ensuite, la première instance peut se terminer et permettre à l'invite de commande de reprendre le traitement des commandes.

L'effet secondaire de cette technique est que, lorsque vous démarrez le programme à partir d'une interface graphique, il reste une console. Il clignotera sur l'écran momentanément puis disparaîtra.

La partie de l'article de Junfeng sur l'utilisation de editbin pour modifier l'indicateur de mode console du programme est un fouillis rouge, je pense. Votre compilateur ou environnement de développement doit fournir un paramètre ou une option permettant de contrôler le type de fichier binaire créé. Il ne devrait y avoir aucun besoin de modifier quoi que ce soit par la suite.

La ligne du bas, alors, est que vous pouvez soit avoir deux fichiers binaires, soit scintiller momentanément une fenêtre de la console. Une fois que vous avez décidé quel est le moindre mal, vous avez le choix entre plusieurs implémentations.

* Je dis non console au lieu de GUI parce que sinon c'est une fausse dichotomie. Ce n’est pas parce qu’un programme n’a pas de console qu’il a une interface graphique. Une application de service est un excellent exemple. En outre, un programme peut avoir une console et Windows.

85
Rob Kennedy

Découvrez le blog de Raymond sur ce sujet:

http://blogs.msdn.com/oldnewthing/archive/2009/01/01/9259142.aspx

Sa première phrase: "Vous ne pouvez pas, mais vous pouvez essayer de la simuler."

10
jdigital

http://www.csharp411.com/console-output-from-winforms-application/

Il suffit de vérifier les arguments de la ligne de commande avant les opérations WinForms Application..

Je devrais ajouter que, dans .NET, il est extrêmement facile de créer simplement une console et des projets d'interface graphique dans la même solution, qui partagent tous leurs assemblages, à l'exception de main. Et dans ce cas, vous pouvez créer la version en ligne de commande en lançant simplement la version graphique si elle est lancée sans paramètre. Vous obtiendrez une console clignotante.

6
Cade Roux

Il existe un moyen facile de faire ce que vous voulez. Je l'utilise toujours lors de la création d'applications qui devraient avoir à la fois une interface de ligne de commande et une interface graphique. Vous devez définir votre "OutputType" sur "ConsoleApplication" pour que cela fonctionne.

class Program {
  [DllImport("kernel32.dll", EntryPoint = "GetConsoleWindow")]
  private static extern IntPtr _GetConsoleWindow();

  /// <summary>
  /// The main entry point for the application.
  /// </summary>
  [STAThread]
  static void Main(string[] args) {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    /*
     * This works as following:
     * First we look for command line parameters and if there are any of them present, we run the CLI version.
     * If there are no parameters, we try to find out if we are run inside a console and if so, we spawn a new copy of ourselves without a console.
     * If there is no console at all, we show the GUI.
     * We make an exception if we find out, that we're running inside visual studio to allow for easier debugging the GUI part.
     * This way we're both a CLI and a GUI.
     */
    if (args != null && args.Length > 0) {

      // execute CLI - at least this is what I call, passing the given args.
      // Change this call to match your program.
      CLI.ParseCommandLineArguments(args);

    } else {
      var consoleHandle = _GetConsoleWindow();

      // run GUI
      if (consoleHandle == IntPtr.Zero || AppDomain.CurrentDomain.FriendlyName.Contains(".vshost"))

        // we either have no console window or we're started from within visual studio
        // This is the form I usually run. Change it to match your code.
        Application.Run(new MainForm());
      else {

        // we found a console attached to us, so restart ourselves without one
        Process.Start(new ProcessStartInfo(Assembly.GetEntryAssembly().Location) {
          CreateNoWindow = true,
          UseShellExecute = false
        });
      }
    }
  }
5
user1566352

Je pense que la technique préférée est ce que Rob a appelé la technique devenv consistant à utiliser deux exécutables: un lanceur ".com" et le ".exe" original. Ce n’est pas si difficile à utiliser si vous avez le code standard sur lequel travailler (voir lien ci-dessous).

La technique utilise des astuces pour que ".com" soit un proxy pour stdin/stdout/stderr et lance le fichier .exe portant le même nom. Cela donne le comportement de permettre au programme de s’exécuter en mode ligne de commande lorsqu’il est appelé depuis une console (éventuellement uniquement lorsque certains arguments de ligne de commande sont détectés) tout en pouvant être lancé en tant qu’application graphique sans console.

J'ai hébergé un projet appelé dualsubsysting sur Google Code qui met à jour une ancienne solution codeguru de cette technique et fournit le code source et des exemples de binaires.

3
gabeiscoding

Voici ce que je pense être la solution simple .NET C # au problème. Juste pour reformuler le problème, lorsque vous exécutez la console "version" de l'application à partir d'une ligne de commande avec un commutateur, la console continue d'attendre (elle ne revient pas à la commande Invite et le processus continue de s'exécuter) même si vous avez un Environment.Exit(0) à la fin de votre code. Pour résoudre ce problème, avant d'appeler Environment.Exit(0), appelez ceci:

SendKeys.SendWait("{ENTER}");

Ensuite, la console obtient la touche Entrée finale dont elle a besoin pour revenir à la commande Invite et le processus se termine. Remarque: n'appelez pas SendKeys.Send(), sinon l'application se bloquera.

Il est toujours nécessaire d'appeler AttachConsole(), comme mentionné dans de nombreux articles, mais je ne vois aucun scintillement dans la fenêtre de commande lors du lancement de la version WinForm de l'application.

Voici le code complet dans un exemple d'application que j'ai créé (sans le code WinForms):

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace ConsoleWriter
{
    static class Program
    {
        [DllImport("kernel32.dll")]
        private static extern bool AttachConsole(int dwProcessId);
        private const int ATTACH_PARENT_PROCESS = -1;

        [STAThread]
        static void Main(string[] args)
        {
            if(args.Length > 0 && args[0].ToUpperInvariant() == "/NOGUI")
            {
                AttachConsole(ATTACH_PARENT_PROCESS);
                Console.WriteLine(Environment.NewLine + "This line prints on console.");

                Console.WriteLine("Exiting...");
                SendKeys.SendWait("{ENTER}");
                Environment.Exit(0);
            }
            else
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
        }
    }
}

J'espère que cela aidera quelqu'un qui passe aussi plusieurs jours sur ce problème. Merci pour l'allusion, allez à @dantill.

3
LTDev
/*
** dual.c    Runs as both CONSOLE and GUI app in Windows.
**
** This solution is based on the "Momentary Flicker" solution that Robert Kennedy
** discusses in the highest-rated answer (as of Jan 2013), i.e. the one drawback
** is that the console window will briefly flash up when run as a GUI.  If you
** want to avoid this, you can create a shortcut to the executable and tell the
** short cut to run minimized.  That will minimize the console window (which then
** immediately quits), but not the GUI window.  If you want the GUI window to
** also run minimized, you have to also put -minimized on the command line.
**
** Tested under MinGW:  gcc -o dual.exe dual.c -lgdi32
**
*/
#include <windows.h>
#include <stdio.h>

static int my_win_main(HINSTANCE hInstance,int argc,char *argv[],int iCmdShow);
static LRESULT CALLBACK WndProc(HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam);
static int win_started_from_console(void);
static BOOL CALLBACK find_win_by_procid(HWND hwnd,LPARAM lp);

int main(int argc,char *argv[])

    {
    HINSTANCE hinst;
    int i,gui,relaunch,minimized,started_from_console;

    /*
    ** If not run from command-line, or if run with "-gui" option, then GUI mode
    ** Otherwise, CONSOLE app.
    */
    started_from_console = win_started_from_console();
    gui = !started_from_console;
    relaunch=0;
    minimized=0;
    /*
    ** Check command options for forced GUI and/or re-launch
    */
    for (i=1;i<argc;i++)
        {
        if (!strcmp(argv[i],"-minimized"))
            minimized=1;
        if (!strcmp(argv[i],"-gui"))
            gui=1;
        if (!strcmp(argv[i],"-gui-"))
            gui=0;
        if (!strcmp(argv[i],"-relaunch"))
            relaunch=1;
        }
    if (!gui && !relaunch)
        {
        /* RUN AS CONSOLE APP */
        printf("Console app only.\n");
        printf("Usage:  dual [-gui[-]] [-minimized].\n\n");
        if (!started_from_console)
            {
            char buf[16];
            printf("Press <Enter> to exit.\n");
            fgets(buf,15,stdin);
            }
        return(0);
        }

    /* GUI mode */
    /*
    ** If started from CONSOLE, but want to run in GUI mode, need to re-launch
    ** application to completely separate it from the console that started it.
    **
    ** Technically, we don't have to re-launch if we are not started from
    ** a console to begin with, but by re-launching we can avoid the flicker of
    ** the console window when we start if we start from a shortcut which tells
    ** us to run minimized.
    **
    ** If the user puts "-minimized" on the command-line, then there's
    ** no point to re-launching when double-clicked.
    */
    if (!relaunch && (started_from_console || !minimized))
        {
        char exename[256];
        char buf[512];
        STARTUPINFO si;
        PROCESS_INFORMATION pi;

        GetStartupInfo(&si);
        GetModuleFileNameA(NULL,exename,255);
        sprintf(buf,"\"%s\" -relaunch",exename);
        for (i=1;i<argc;i++)
            {
            if (strlen(argv[i])+3+strlen(buf) > 511)
                break;
            sprintf(&buf[strlen(buf)]," \"%s\"",argv[i]);
            }
        memset(&pi,0,sizeof(PROCESS_INFORMATION));
        memset(&si,0,sizeof(STARTUPINFO));
        si.cb = sizeof(STARTUPINFO);
        si.dwX = 0; /* Ignored unless si.dwFlags |= STARTF_USEPOSITION */
        si.dwY = 0;
        si.dwXSize = 0; /* Ignored unless si.dwFlags |= STARTF_USESIZE */
        si.dwYSize = 0;
        si.dwFlags = STARTF_USESHOWWINDOW;
        si.wShowWindow = SW_SHOWNORMAL;
        /*
        ** Note that launching ourselves from a console will NOT create new console.
        */
        CreateProcess(exename,buf,0,0,1,DETACHED_PROCESS,0,NULL,&si,&pi);
        return(10); /* Re-launched return code */
        }
    /*
    ** GUI code starts here
    */
    hinst=GetModuleHandle(NULL);
    /* Free the console that we started with */
    FreeConsole();
    /* GUI call with functionality of WinMain */
    return(my_win_main(hinst,argc,argv,minimized ? SW_MINIMIZE : SW_SHOWNORMAL));
    }


static int my_win_main(HINSTANCE hInstance,int argc,char *argv[],int iCmdShow)

    {
    HWND        hwnd;
    MSG         msg;
    WNDCLASSEX  wndclass;
    static char *wintitle="GUI Window";

    wndclass.cbSize        = sizeof (wndclass) ;
    wndclass.style         = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc   = WndProc;
    wndclass.cbClsExtra    = 0 ;
    wndclass.cbWndExtra    = 0 ;
    wndclass.hInstance     = hInstance;
    wndclass.hIcon         = NULL;
    wndclass.hCursor       = NULL;
    wndclass.hbrBackground = NULL;
    wndclass.lpszMenuName  = NULL;
    wndclass.lpszClassName = wintitle;
    wndclass.hIconSm       = NULL;
    RegisterClassEx (&wndclass) ;

    hwnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,wintitle,0,
                          WS_VISIBLE|WS_OVERLAPPEDWINDOW,
                          100,100,400,200,NULL,NULL,hInstance,NULL);
    SetWindowText(hwnd,wintitle);
    ShowWindow(hwnd,iCmdShow);
    while (GetMessage(&msg,NULL,0,0))
        {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        }
    return(msg.wParam);
    }


static LRESULT CALLBACK WndProc (HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam)

    {
    if (iMsg==WM_DESTROY)
        {
        PostQuitMessage(0);
        return(0);
        }
    return(DefWindowProc(hwnd,iMsg,wParam,lParam));
    }


static int fwbp_pid;
static int fwbp_count;
static int win_started_from_console(void)

    {
    fwbp_pid=GetCurrentProcessId();
    if (fwbp_pid==0)
        return(0);
    fwbp_count=0;
    EnumWindows((WNDENUMPROC)find_win_by_procid,0L);
    return(fwbp_count==0);
    }


static BOOL CALLBACK find_win_by_procid(HWND hwnd,LPARAM lp)

    {
    int pid;

    GetWindowThreadProcessId(hwnd,(LPDWORD)&pid);
    if (pid==fwbp_pid)
        fwbp_count++;
    return(TRUE);
    }
2
willus

J'ai écrit une approche alternative qui évite le flash de la console. Voir Comment créer un programme Windows qui fonctionne à la fois comme interface graphique et comme application console.

1
dantill

Exécuter AllocConsole () dans un constructeur statique fonctionne pour moi

0
Shaggy