我想设计一个程序,在输入命令行参数的时候,它就是一个WIN32的CONSLOE程序,可以进行控制台上的输入输出交互,如果没有命令行参数,它就是一个WIN32的WINDOWS程序,可以进行图形界面的交互。额外的二点要求:
1)在作为控制台程序的时候,如果是在cmd.exe中启动的,要使用cmd的控制台,如果在运行菜单中启动的,用自己的控制台。
2)GUI界面不要出现控制台的黑窗口。我曾经试过并失败了的方法:
1)工程创建为/subsystem windows,然后根据命令行参数来隐藏窗口,AllocConsole。失败原因:如果是在cmd.exe命令行中启动本程序,无法使用cmd的控制台。
2)同上,但是使用AttachConsole挂接到父进程的控制台,还是不行,因为GUI的程序运行起来之后,控制台就退到提示符状态,无法进行交互了。
3)工程创建为/subsystem console,然后根据命令行参数来创建GUI窗口,失败原因:如果在运行菜单中运行程序,老是有一个console黑窗口出来。如果在命令行运行程序,GUI界面已经起来了,控制台上还不返回到提示符状态。
4)修改PE文件头结构,结果死机。恳请各位大侠帮忙!
不胜感谢!

解决方案 »

  1.   

    换个思路:两个程序,一个console一个gui
      

  2.   

    三个程序,一个负责根据环境是Console还是gui而启动另外两个
      

  3.   

    http://www.codeguru.com/console/Console.html
      

  4.   

    我用两个程序试了一下,唯一不满足的是gui时console会出现,然后消失
      

  5.   

    codeguru我去看过了,很多想法都是从那里得到的,但是都不满足我得需求。恭喜wuxfBrave新版主当选,我目前能做到的最好结果就是在运行菜单中启动程序、期望出现GUI界面时,会有黑窗口一闪。而且这个黑窗口是操作系统创建的,应用程序无法屏蔽。可能思路还有局限,应该有十全十美的办法。
      

  6.   

    没什么,就一个程序
    有参数时 不建主窗口,当然你的处理函数不能放在CDialog/CView的派生类里我现在就这么干
      

  7.   

    sorry, 没看清
    可以这样:
    GUI程序 + Console程序
    GUI程序如果有命令行参数,就Shell运行Console程序,然后自己退出(当然要在MainWnd创建前)
      

  8.   

    GUI程序 + Console程序
    能作到一个exe文件里面吗?
      

  9.   

    GUI程序如果有命令行参数,就Shell运行Console程序
    这个方法也不行,因为如果在命令行运行GUI,GUI调用ShellExec运行的CONSOLE会创建新的CONSOLE窗口,不能用已有的命令行的窗口。
      

  10.   

    本人不懂,但建议研究一下PE和WINDOWS加载可执行文件的方法,看看能不能写符合你的要求的PE文件
      

  11.   

    一个程序,一套核心处理代码,两套UI代码就可以了。这两套UI代码的case就在有无参数里面或者由具体的参数决定。一个普通的MFC Application,一般都有CCommandLineInfo,重载它。并且把它的处理放到C***App::InitInstance前面去。当然自己直接处理输入的参数也可以。一个普通的Win32 Application,更简单了,根据输入的参数决定是否RegisterClass和CreateWindow。应该不是很难。
      

  12.   

    有一点思路,就是CreateProcess时,看分配控制台的操作是在用户级完成的还是在核心级完成的,如果是在用户级完成的,可以自己写一个CreateProcess函数代替之。要是在核心,就没办法了。
      

  13.   

    用ShellExecute开启外部应用程序(也包括cmd)
      

  14.   

    搜索MSDN的RT库函数_spawn看看,或许有用》
      

  15.   

    我也遇到一个问题:
    如果为自由软件编译器(如: MinGW)开发自己的IDE,怎么在GUI中捕获到Console中输出的调试信息,因为gdb还在运行,每一个单步执行的结果(比如变量的新值)怎么得到并在GUI中显示。
      

  16.   

    如果是gcc,g++,一步就执行完的,倒可以用_spawn, CreatePipe, ...来实现。
      

  17.   

    是否分配控制台好像是由系统通过静态分析.exe文件后决定的。
    我的做法是分别生成两种项目(WA和WCA),每个里面只有一句话:
    void main
    {
    }
    然后将程序入口定成main,用Release模式编译,然后使用
    fc wa.exe wca.exe >result.txt
    打开result.txt就可以发现区别。
      

  18.   

    是否分配控制台好像是由系统通过静态分析.exe文件后决定的。
    我的做法是分别生成两种项目(WA和WCA),每个里面只有一句话:
    void main
    {
    }
    然后将程序入口定成main,用Release模式编译,然后使用
    fc wa.exe wca.exe >result.txt
    打开result.txt就可以发现区别。
      

  19.   

    WinXP里有一个shutdown,效果跟你想要得差不多,但是我看到它使用GUI的时候,控制台并没有返回。
      

  20.   

    建议看一下:
    利用了匿名管道输入,输出.不过只能在2000以上的系统中使用……
    http://www.qiker.com/jiaocheng/heibai3/%BA%DA%BF%CD%C8%EB%C3%C5/hacker0000018.htm
      

  21.   

    首先感谢CSDN上这么多的热心朋友参与这个讨论。
    祝大家春节快乐,猴年加薪升职,大大发财!这个问题,我在春节期间又试了试,现将所有的尝试和结果写出来给大家分享,也请大家批评指正或者提出更好的方法。然后过几天我就要结贴了,如果有必要的话,我会令开新贴欢迎大家继续讨论。
      

  22.   

    一、背景介绍
    1、应用程序类型
    (1)从运行环境来分,WINDOWS应用程序有两种常见类型:命令行字符界面应用程序(CONSOLE APPLICATION)和图形界面应用程序(WINDOWS APPLICATION)。应用程序的类型是由开发环境的连接器的连接选项来控制的,例如Microsoft的连接器LINK.EXE(可能由ML.EXE间接调用)的相应选项就是(详细的说明请参考MSDN LIBRARY):/SUBSYSTEM:{CONSOLE | EFI_APPLICATION | EFI_BOOT_SERVICE_DRIVER | EFI_ROM | EFI_RUNTIME_DRIVER | NATIVE | POSIX | WINDOWS | WINDOWSCE} [,major[.minor]]开发环境最终生成的应用程序的头部会有一个标志字段,来标志这个应用程序的类型。MSDN LIBRARY(KEY:Portable Executable file format, Win32)对于这个标志位的说明如下:WORD Subsystem 
    The type of subsystem that this executable uses for its user interface. WINNT.H defines the following values:
    NATIVE 1 Doesn't require a subsystem (such as a device driver) 
    WINDOWS_GUI 2 Runs in the Windows GUI subsystem 
    WINDOWS_CUI 3 Runs in the Windows character subsystem (a console app) 
    OS2_CUI 5 Runs in the OS/2 character subsystem (OS/2 1.x apps only) 
    POSIX_CUI 7 Runs in the Posix character subsystem WINNT.H中对于此字段的定义如下://
    // Optional header format.
    //typedef struct _IMAGE_OPTIONAL_HEADER {
        //
        // Standard fields.
        //    WORD    Magic;
        BYTE    MajorLinkerVersion;
        BYTE    MinorLinkerVersion;
        ...
        ...
        ...    //
        // NT additional fields.
        //    DWORD   ImageBase;
        DWORD   SectionAlignment;
        ...
        ...
        ...
        WORD    Subsystem;
        ...
        ...
        ...
        DWORD   LoaderFlags;
        DWORD   NumberOfRvaAndSizes;
        IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
    } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;操作系统在运行应用程序时,就是根据这个字段来确定应用程序的类型并为不同类型的应用程序分配相适应的资源的。(2)从运行级别来分,WINDOWS的应用程序又可以分为系统应用程序和用户应用程序。
    系统应用程序(例如内核、驱动、服务等)一般设置在系统配置文件或系统注册表中,由系统在启动阶段加载运行。
    用户应用程序是面向操作系统用户的应用软件,用户应用程序依赖于系统应用程序的支持,为用户提供多样化的服务。2、用户应用程序的运行方式
    在WINDOWS操作系统中,用户应用程序通常有这样几种运行方式:
    A,在命令行提示符下运行用户应用程序
    B,在命令行BAT文件中运行用户应用程序
    C,在“开始”菜单中选择“运行”菜单项,在弹出的对话框中运行用户应用程序
    D,在文件夹中,直接点击运行用户应用程序
    E,由应用程序或者SPCRIPT脚本间接调用运行用户应用程序
    F,配置在启动菜单或注册表中,由在启动或登录或其它触发条件满足时自动运行用户应用程序
    归纳一下,用户应用程序一般只存在一下两种运行方式:
      i. CONSOLE运行方式,包括上面列表中的A、B两项
     ii. SHELL(或许不太恰当)运行方式,包括上面列表中的C、D、E、F四项现在分析一下不同类型(只取两种常见类型)的用户应用程序在不同运行方式下的表现,如下表:-----------------------------------------------------------------------
                  \运行方式|                        |
                   \       |      CONSOLE EXEC      |      SHELL EXEC
    用户应用程序类型\      |                        |
    -----------------------------------------------------------------------
                           | 在已有的控制台上运行, | 产生新的控制台,在新
          CONSOLE APP      | 不产生新的控制台。程序 | 控制台上运行。程序结
                           | 结束,回到提示符状态。 | 束,新控制台消失。
    -----------------------------------------------------------------------
                           | 产生新的应用程序UI窗口 | 产生新的应用程序窗口
          WINDOWS APP      | 。控制台马上退回到提示 | 。不产生控制台窗口。
                           | 符状态。               |
    -----------------------------------------------------------------------
      

  23.   

    二、我的需求我的需求很简单,就是要生成一个可执行文件,这个可执行文件在自身运行时,判断是否有命令行参数:如果有命令行参数,就表现得象一CONSOLE APP;如果没有命令行参数,就表现得象一个WINDOWS APP。详细的表现应该如下表:---------------------------------------------------------------------
                \运行方式|                        |    
                 \       |      CONSOLE EXEC      |      SHELL EXEC
    有无命令行参数\      |                        |
    ---------------------------------------------------------------------
                         | 在已有的控制台上运行, | 产生新的控制台,在新
         有命令行参数    | 不产生新的控制台。程序 | 控制台上运行。程序结
                         | 结束,回到提示符状态。 | 束,新控制台消失。
    ---------------------------------------------------------------------
                         | 产生新的应用程序UI窗口 | 产生新的应用程序窗口
         无命令行参数    | 。控制台马上退回到提示 | 。不产生控制台窗口。
                         | 符状态。               |
    ---------------------------------------------------------------------为了后续的描述和引用方便,将上述四个需求编号如下:---------------------------------------------------------------------
                \运行方式|                        |    
                 \       |      CONSOLE EXEC      |      SHELL EXEC
    有无命令行参数\      |                        |
    ---------------------------------------------------------------------
                         |                        |
         有命令行参数    |         需求一         |        需求二
                         |                        |
    ---------------------------------------------------------------------
                         |                        |
         无命令行参数    |         需求三         |        需求四
                         |                        |
    ---------------------------------------------------------------------
      

  24.   

    三、我所尝试过的方案无论那种方案,都需要判断是否有命令行参数,我使用过的方法如下:
    A,使用_tmain函数的参数argc来判断
    B,使用API函数GetCommandLineW和CommandLineToArgvW来获得参数个数
    C,使用全局变量__argc或全局指针___argc来获得参数个数1、方案一:
      i. 创建工程为CONSOLE APP,选择USE MFC选项;
     ii. 添加一个对话框资源。
    iii. 为对话框资源添加一个类CGuiDlg,选择基类为CDialog。
     iv. 添加一个MFC类CGuiApp,选择基类为CWinApp。修改其结构函数和析构函数为public成员。
      v. 修改CGuiApp::InitInstance函数        #include "GuiDlg.h"        BOOL CGuiApp::InitInstance()
            {
                // TODO:  perform and per-thread initialization here
                CGuiDlg dlg;
                dlg.DoModal();            return 0;
            } vi. 修改_tmain函数        #include "GuiApp.h"        CGuiApp theApp;        int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
            {
                if (argc > 1)
                {
                    // 有命令行参数,直接在CUI环境中进行处理
                    ;
                }
                else
                {
                    // 没有命令行参数,启动GUI窗口。
                    return _tWinMain(GetModuleHandle(NULL), NULL, _T(""), SW_SHOW);
                }
            }2、方案二:
      i. 创建工程为WINDOWS APP;
     ii. 修改编译选项的预编译宏,添加_CONSOLE宏定义;
    iii. 修改连接选项的subsystem为console;
     iv. 添加_tmain函数        int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
            {
                if (argc > 1)
                {
                    // 有命令行参数,直接在CUI环境中进行处理
                    ;
                }
                else
                {
                    // 没有命令行参数,启动GUI窗口。
                    return _tWinMain(GetModuleHandle(NULL), NULL, _T(""), SW_SHOW);
                }
            }小结:以上两个方案实质上是一样的,都是使程序连接成一个CONSOLE APP,使程序的入口为main函数,但是程序代码中已经有了GUI的代码。在CONSOLE APP的main函数里,判断是否有命令行参数,如果有命令行参数,直接在命令行状态下进行处理,如果没有命令行参数,就调用WinMain函数启动GUI环境。
    这两种方案的缺点是:不满足需求三和需求四。具体表现如下:---------------------------------------------------------------------
                \运行方式|                        |    
                 \       |      CONSOLE EXEC      |      SHELL EXEC
    有无命令行参数\      |                        |
    ---------------------------------------------------------------------
                         |                        |
         有命令行参数    |          满足          |         满足
                         |                        |
    ---------------------------------------------------------------------
                         | GUI窗口已经产生,但是  | 会有一个控制台黑窗口
         无命令行参数    | 控制台不能退回到提示符 | 产生,直到GUI窗口退
                         | 状态下,直到GUI退出。  | 出。
    ---------------------------------------------------------------------对于上述两种方案进行一下改进,就生成了方案三。
      

  25.   

    3、方案三:
      i. 在方案一或者方案二的基础上修改_tmain函数        int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
            {
                if (argc > 1)
                {
                    // 有命令行参数,直接在CUI环境中进行处理
                    ;
                }
                else
                {
                    // 没有命令行参数,释放控制台
                    //DetachConsole();
                    FreeConsole();
                    // 启动GUI窗口。
                    return _tWinMain(GetModuleHandle(NULL), NULL, _T(""), SW_SHOW);
                }
            }小结:方案三对于方案一和方案二来说,虽然有一些改进,但是好不了多少,仍然不支持需求三,对需求四的支持也很不完美。具体表现如下:---------------------------------------------------------------------
                \运行方式|                        |    
                 \       |      CONSOLE EXEC      |      SHELL EXEC
    有无命令行参数\      |                        |
    ---------------------------------------------------------------------
                         |                        |
         有命令行参数    |          满足          |         满足
                         |                        |
    ---------------------------------------------------------------------
                         | GUI窗口已经产生,但是  | 在程序启动时,会有一
         无命令行参数    | 控制台不能退回到提示符 | 个控制台窗口一闪而过
                         | 状态下,直到GUI退出。  | 。
    ---------------------------------------------------------------------由方案三的改动没有满足需求三可见,WINDOWS操作系统对于控制台的控制是很复杂的,控制台并不会因为一个从控制台运行的CONSOLE APP调用了FreeConsole就返回到提示符状态。4、方案四:
      i. 创建一个WINDOWS APP。
     ii. 在InitInstance函数中,判断命令行参数,决定继续运行GUI界面还是创建CUI环境。
    iii. 如果需要创建CUI环境,判断程序是从CONSOLE环境启动的还是从SHELL环境启动的,判断的方法是使用Native API获得父进程的ProcessID,然后进一步获得父进程的BASE MODULE的路径名,看是不是cmd.exe,如果是,就认为是从CONSOLE环境启动的,如果不是就认为侍从SHELL环境启动的。
     iv. 如果程序是从CONSOLE环境启动的,AttackConsole到父进程的CONSOLE上去;如果程序是在SHELL环境中启动的,AllocConsole分配一个新的控制台。
      v. 创建好控制台环境之后,马上进行CUI处理,处理完成时,return FALSE结束GUI程序的运行。小结:方案四如果能够实现,基本上能够满足我得需求,但是这个方案在AttackConsole时,根本行不通,也就是说,根本就无法Attach到cmd.exe的CONSOLE上去。我想这可能是遭遇到了WINDOWS的安全问题。试想,如果应用程序能够很容易就Attach到cmd.exe的CONSOLE上去,那系统的安全性肯定是要大打折扣的。方案四还有另外的一个缺陷,就是由于使用了Native API,所以只能运行在NT系统上。5、方案五
      i. 创建一个WINDWOS APP。
     ii. 在相同的Solution下,创建一个CONSOLE APP。
    iii. 在相同的Solution下,创建一个makefile工程,把两个可执行文件合并起来,让CUI在前面,被系统运行;GUI在后面,在需要的时候由CUI启动运行。
     iv. CUI启动GUI的方法是这样的:
            a,使用GetModuleBaseNameW获得正在运行的可执行文件(合并后的)的名称。
            b,使用CreateFileW打开正在运行的(合并后的)可执行文件。
            c,使用ZwCreateSection创建可执行文件在内核中的IMAGE Section对象。
            d,使用ZwMapViewOfSection将Section内核对象映射到本进程地址空间。
            e,在Section对象中找到GUI程序代码,提前到Section开始地址处。
            f,使用ZwCreateProcess创建新的GUI进程。
            g,使用ZwCreateThread创建GUI进程的主线程(挂起)。
            h,通知CSRSS模块进程信息。
            i,运行主线程。小结:方案五如果能够实现,只存在一个缺陷,那就是在满足需求四时不很完美,会有控制台窗口一闪而过。其它需求都可以满足。但是实际上这个方法也是行不通的:因为映射到本进程的Section要想能够有写权限,就必需以可写方式打开可执行文件。而可执行文件此时正在被执行,不可能用通常的办法以可写的方式打开(虽然有一些不通常的办法可用,但是都属于钻系统安全的漏洞,其扩展性和移植性都不好)。同方案四一样,就算是方案五可行,也只能运行在NT系统上。6、方案六
    比方案五土一点的办法,就是更改由CUI启动GUI的方法为,以只读方式打开可执行文件,提取GUI程序,写入到TEMP目录下的临时文件里,然后调用CreateProcess或者ShellExecute来运行之。这种办法虽然很土,但是目前看来是效果最好的。其对需求的满足情况如下:---------------------------------------------------------------------
                \运行方式|                        |    
                 \       |      CONSOLE EXEC      |      SHELL EXEC
    有无命令行参数\      |                        |
    ---------------------------------------------------------------------
                         |                        |
         有命令行参数    |          满足          |         满足
                         |                        |
    ---------------------------------------------------------------------
                         |                        | 在程序启动时,会有一
         无命令行参数    |          满足          | 个控制台窗口一闪而过
                         |                        | 。
    ---------------------------------------------------------------------所以,我目前使用的就是方案六。其实,方案六是我最先想到的、被我认为是最土的、有垃圾的方案。也是最后尝试的方案。但是事情往往是这样,越是土的办法,效果可能越好;越是花哨的方法,可能越不实用。----------------------------好了,就写这么多,欢迎批评指正和讨论交流。