不要在发布中显示控制台窗口,而是在调试中显示

时间:2012-04-02 21:35:30

标签: c++ windows visual-c++

我想创建一个将作为最终产品在后台运行的程序。出于调试目的,我希望它显示一个控制台。

我了解到有一个 ShowWindow(hWnd,SW_HIDE); 功能,但如果我在'标准'主功能中使用它,控制台窗口仍会弹出一会儿。我试图这样做(是的,我知道它很糟糕):

#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <iostream>
#include <stdio.h>
#include <tchar.h>


#define DEBUG
//#undef DEBUG

#ifndef DEBUG
#pragma comment(linker, "/SUBSYSTEM:WINDOWS")

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
    HWND hWnd = GetConsoleWindow();
    ShowWindow( hWnd, SW_HIDE );    

    while(1);

    return 0;
}
#else
#pragma comment(linker, "/SUBSYSTEM:CONSOLE")
    int main(int argc, int **argv)
    {
        HWND hWnd = GetConsoleWindow();

        while(1);

        return 0;
    }
#endif

这里我设法阻止窗口弹出窗口,但是我无法将参数传递给程序。

我相信有更好的解决方案。你能分享吗?

PS

我不想使用.NET。

4 个答案:

答案 0 :(得分:4)

这是问题第一部分的答案,“我设法防止弹出窗口”,即如何在Visual C ++中为应用程序设置Windows子系统。

我将分别回答关于命令行参数的问题的第二部分。

// How to create a Windows GUI or console subsystem app with a standard `main`.

#ifndef _MSC_VER
#   error Hey, this is Visual C++ specific source code!
#endif

// Better set this in the project settings, so that it's more easily configured.
#ifdef  NDEBUG
#   pragma comment( linker, "/subsystem:windows /entry:mainCRTStartup" )
#else
#   pragma comment( linker, "/subsystem:console" )
#endif

#undef  UNICODE
#define UNICODE
#undef  NOMINMAX
#define NOMINAX
#undef  STRICT
#define STRICT
#include <windows.h>

int main()
{
    MessageBox( 0, L"Hi!", L"This is the app!", MB_SETFOREGROUND );
}

NDEBUG 标准C ++宏旨在抑制标准assert的影响,因此不需要具有全局意义。但是,在实践中它具有全球意义。与使用Visual C ++宏(如DEBUG

相比,它提供了一些可移植性

无论如何,为了更容易配置子系统,除非你想强制调试版本应该是控制台和发布版本应该是GUI,那么我建议在项目设置中这样做而不是通过#pragma(另请注意,例如g ++编译器不支持链接器编译指示,因此使用项目设置更加便携)。

如果您需要,可以通过编程方式检查子系统,而不仅仅是通过检查(即,而不是注意上述程序是否产生控制台):

// How to create a Windows GUI or console subsystem app with a standard `main`.

#ifndef _MSC_VER
#   error Hey, this is Visual C++ specific source code!
#endif

// Better set this in the project settings, so that it's more easily configured.
#ifdef  NDEBUG
#   pragma comment( linker, "/subsystem:windows /entry:mainCRTStartup" )
#else
#   pragma comment( linker, "/subsystem:console" )
#endif

#undef  UNICODE
#define UNICODE
#undef  NOMINMAX
#define NOMINAX
#undef  STRICT
#define STRICT
#include <windows.h>

#include <assert.h>         // assert
#include <string>           // std::wstring
#include <sstream>          // std::wostringstream
using namespace std;

template< class Type >
wstring stringFrom( Type const& v )
{
    wostringstream  stream;

    stream << v;
    return stream.str();
}

class S
{
private:
    wstring     s_;

public:
    template< class Type >
    S& operator<<( Type const& v )
    {
        s_ += stringFrom( v );
        return *this;
    }

    operator wstring const& () const { return s_; }
    operator wchar_t const* () const { return s_.c_str(); }
};

IMAGE_NT_HEADERS const& imageHeaderRef()
{
    HMODULE const                   hInstance   =
        GetModuleHandle( nullptr );

    IMAGE_DOS_HEADER const* const   pImageHeader    =
        reinterpret_cast< IMAGE_DOS_HEADER const* >( hInstance );
    assert( pImageHeader->e_magic == IMAGE_DOS_SIGNATURE );     // "MZ"

    IMAGE_NT_HEADERS const* const   pNTHeaders      = reinterpret_cast<IMAGE_NT_HEADERS const*>(
            reinterpret_cast< char const* >( pImageHeader ) + pImageHeader->e_lfanew
            );
    assert( pNTHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC );    // "PE"

    return *pNTHeaders;
}

int main()
{
    IMAGE_NT_HEADERS const& imageHeader = imageHeaderRef();
    WORD const              subsystem   = imageHeader.OptionalHeader.Subsystem;

    MessageBox(
        0,
        S() << L"Subsystem " << subsystem << L" "
            << (0?0
                : subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI?     L"GUI"
                : subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI?     L"Console"
                : L"Other"),
        L"Subsystem info:",
        MB_SETFOREGROUND );
}

答案 1 :(得分:2)

您仍然可以将参数传递给常规的非控制台Win32程序:它们只显示在单个lpCmdLine字符串中,所有字符串都汇总到一个大命令行中。您可以使用CommandLineToArgvW将其解析为单独的参数,但请注意该函数仅在Unicode风格中可用。例如:

int wmain(int argc, wchar_t **argv)
{
    // Common main function (Unicode args)
}

#ifndef DEBUG
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
    // Forward invocation to wmain
    int argc;
    LPWSTR *argv = CommandLineToArgvW(pCmdLine, &argc);
    int status = wmain(argc, argv);
    LocalFree(argv);
    return status;
}
#endif

我还建议您使用项目设置来设置可执行文件的子系统(控制台或Windows),具体取决于配置,而不是使用#pragma来执行此操作。

答案 2 :(得分:2)

这是对问题第二部分的回答,“但我无法将参数传递给程序”,即如何在Visual C ++ Windows应用程序中获取命令行参数。

最简单但最有限的方法是使用标准C ++ main的参数,

int main( int argc, char* argv[] )
{
    // Whatever, e.g.
    vector<string> const args( argv, argv + argc );
}

C ++标准强烈建议这些参数应该用一些多字节字符集编码,如UTF-8,

C ++11§3.6.1/ 2

  

“如果argc非零,则argv[0]中应提供这些参数   通过argv[argc-1]作为指向空终止多字节字符串(NTMBS)(17.5.2.1.4.2)和argv[0]的初始字符的指针应该是指向NTMBS的初始字符的指针,该NTMBS表示使用的名称调用程序或""。“

然而,在第一个C ++标准时,在1998年,* nix世界约定和Windows约定都不是这样做的。相反,惯例是使用一些特定于语言环境的字符编码传递参数。 Linux世界几乎立即开始向UTF-8迁移,而Windows则没有,因此截至2012年,在Windows中,标准main参数不足以通过例如任意文件名......

令人高兴的是,在Windows中,传递给进程的命令行是可通过GetCommandLine API函数获得的,是UTF-16编码的,这意味着任何文件名(和确实可以通过任何文本。

第三方面,提供命令行CommandLineToArgvW的标准解析的API函数有at least one sillybug,可能还有更多......并且可能是非标准的Visual C ++ Unicode C ++启动函数wmain具有该函数提供的参数。因此,为了获得最佳结果,在修复之前,应该使用一些适当的自制命令行解析,例如,如下面的程序所示(我刚刚选择了一个我上周制作的特别“个人工具”程序,它类似于Windows 2000 Resource Kit的timethis):

// A program to measure the execution time of another program.
// Based vaguely on Jeffrey Richter's "timep" program in
// the 2nd edition of "Win32 System Programming".
//
// Author: Alf P. Steinbach, 2012. License: Boost license 1.0.

#undef  UNICODE
#define UNICODE
#undef  STRICT
#define STRICT
#undef  NOMINMAX
#define NOMINMAX
#include <windows.h>
#include <shlwapi.h>            // PathGetCharType

#include    <assert.h>          // assert
#include    <functional>        // std::function
#include    <iomanip>           // set::setfill, std::setw
#include    <iostream>          // std::wcout, std::endl
#include    <sstream>           // std::wostringstream
#include    <stddef.h>          // ptrdiff_t
#include    <stdexcept>         // std::runtime_error, std::exception
#include    <stdint.h>          // int64_t
#include    <string>            // std::string
#include    <type_traits>       // std::is_fundamental
#include    <utility>           // std::move
using namespace std;

#if !defined( CPP_STATIC_ASSERT )
#   define CPP_STATIC_ASSERT( e )   static_assert( e, #e )
#endif

#if !defined( CPP_NORETURN )
#   define CPP_NORETURN             [[noreturn]]
#endif
// MSVC  workaround: "#define CPP_NORETURN __declspec( noreturn )"
// clang workaround: "#define CPP_NORETURN __attribute__(( noreturn ))"

namespace cpp {
    namespace detail {
        template< class Destination, class Source >
        class ImplicitCast
        {
        public:
            static Destination value( Source const v )
            {
                return static_cast<Destination>( v );
            }
        };

        template< class Source >
        class ImplicitCast< bool, Source >
        {
        public:
            static bool value( Source const v )
            {
                return !!v;     // Shuts up Visual C++ sillywarning about performance.
            }
        };
    };

    template< class Destination, class Source >
    Destination implicitCast( Source const v )
    {
        CPP_STATIC_ASSERT( is_fundamental< Destination >::value );
        CPP_STATIC_ASSERT( is_fundamental< Source >::value );

        return detail::ImplicitCast< Destination, Source >::value( v );
    }

    typedef ptrdiff_t       Size;

    inline bool hopefully( bool const c ) { return c; }

    inline CPP_NORETURN bool throwX( string const& s )
    {
        throw runtime_error( s );
    }

    inline CPP_NORETURN bool throwX( string const& s, exception const& reasonX )
    {
        throwX( s + "\n!Because - " + reasonX.what() );
    }

    class ScopeGuard
    {
    private:
        function<void()>  cleanup_;

        ScopeGuard( ScopeGuard const& );                // No such.
        ScopeGuard& operator=( ScopeGuard const& );     // No such.

    public:
        ~ScopeGuard() { cleanup_(); }

        ScopeGuard( function<void()> const cleanup )
            : cleanup_( cleanup )
        {}
    };

    class SubstringRef
    {
    private:
        wchar_t const*  start_;
        wchar_t const*  end_;

    public:
        Size length() const             { return end_ - start_; }
        wchar_t const* start() const    { return start_; }
        wchar_t const* end() const      { return end_; }

        SubstringRef( wchar_t const* start, wchar_t const* end )
            : start_( start )
            , end_( end )
        {}
    };

    inline void skipWhitespace( wchar_t const*& p )
    {
        while( *p != L'\0' && iswspace( *p ) ) { ++p; }
    }

    inline wchar_t const* theAfterWhitespacePart( wchar_t const* p )
    {
        skipWhitespace( p );
        return p;
    }

    inline void invert( bool& b ) { b = !b; }
}  // namespace cpp

namespace winapi {
    using cpp::hopefully;
    using cpp::invert;
    using cpp::Size;
    using cpp::skipWhitespace;
    using cpp::SubstringRef;
    using cpp::theAfterWhitespacePart;
    using cpp::throwX;

    namespace raw {
        typedef DWORD                   DWord;
        typedef FILETIME                FileTime;
        typedef HANDLE                  Handle;
        typedef PROCESS_INFORMATION     ProcessInformation;
        typedef SYSTEMTIME              SystemTime;
        typedef WORD                    Word;
    }  // namespace raw

    // The following logic is mainly a workaround for a bug in CommandLineToArgvW.
    // See [http://preview.tinyurl.com/CommandLineToArgvWBug].
    inline SubstringRef nextArgumentIn( wchar_t const* const commandLine )
    {
        wchar_t const*  p   = commandLine;

        skipWhitespace( p );
        wchar_t const* const    start   = p;

        bool isInQuotedPart = false;
        while( *p != L'\0' && (isInQuotedPart || !iswspace( *p ) ) )
        {
            if( *p == L'\"' ) { invert( isInQuotedPart ); }
            ++p;
        }
        return SubstringRef( start, p );
    }

    // This corresponds essentially to the argument of wWinMain(...).
    inline wchar_t const* commandLineArgPart()
    {
        SubstringRef const programSpec = nextArgumentIn( GetCommandLine() );
        return theAfterWhitespacePart( programSpec.end() );
    }

    class ProcessInfo
    {
    private:
        raw::ProcessInformation info_;

        ProcessInfo( ProcessInfo const& );              // No such.
        ProcessInfo& operator=( ProcessInfo const& );   // No such.

    public:
        raw::ProcessInformation& raw()      { return info_; }
        raw::Handle handle() const          { return info_.hProcess; }

        ~ProcessInfo()
        {
            ::CloseHandle( info_.hThread );
            ::CloseHandle( info_.hProcess );
        }

        ProcessInfo(): info_() {}

        ProcessInfo( ProcessInfo&& other )
            : info_( move( other.info_ ) )
        {
            other.info_ = raw::ProcessInformation();      // Zero.
        }
    };

    inline ProcessInfo createProcess( wchar_t const commandLine[] )
    {
        STARTUPINFO         startupInfo     = { sizeof( startupInfo ) };
        ProcessInfo         processInfo;
        wstring             mutableCommandLine( commandLine );

        mutableCommandLine += L'\0';
        GetStartupInfo( &startupInfo );
        bool const  creationSucceeded = !!CreateProcess (
            nullptr,                // LPCTSTR lpApplicationName,
            &mutableCommandLine[0], // LPTSTR lpCommandLine,
            nullptr,                // LPSECURITY_ATTRIBUTES lpProcessAttributes,
            nullptr,                // LPSECURITY_ATTRIBUTES lpThreadAttributes,
            true,                   // BOOL bInheritHandles,
            NORMAL_PRIORITY_CLASS,  // DWORD dwCreationFlags,
            nullptr,                // LPVOID lpEnvironment,
            nullptr,                // LPCTSTR lpCurrentDirectory,
            &startupInfo,           // LPSTARTUPINFO lpStartupInfo,
            &processInfo.raw()      // LPPROCESS_INFORMATION lpProcessInformation
            );
        hopefully( creationSucceeded )
            || throwX( "winapi::createProcess: CreateProcess failed" );
        return processInfo;
    }

    inline raw::Handle dup(
        raw::Handle const       h,
        raw::DWord const        desiredAccess,
        bool                    inheritable = false
        )
    {
        raw::Handle result  = 0;
        bool const wasDuplicated = !!DuplicateHandle(
            GetCurrentProcess(), h,
            GetCurrentProcess(), &result,
            desiredAccess,
            inheritable,
            0               // options
            );
        hopefully( wasDuplicated )
            || throwX( "winapi::dup: DuplicateHandle failed" );
        assert( result != 0 );
        return result;
    }

    inline int64_t mSecsFromRelative( raw::FileTime const t )
    {
        ULARGE_INTEGER  asLargeInt;

        asLargeInt.u.HighPart   = t.dwHighDateTime;
        asLargeInt.u.LowPart    = t.dwLowDateTime;

        return asLargeInt.QuadPart/10000;
    }

    SubstringRef filenamePart( SubstringRef const& path )
    {
        wchar_t const*  p     = path.end();

        while( p != path.start() && PathGetCharType( *p ) != GCT_SEPARATOR )
        {
            --p;
        }
        if( PathGetCharType( *p ) == GCT_SEPARATOR ) { ++p; }
        return SubstringRef( p, path.end() );
    }
}  // namespace winapi

winapi::ProcessInfo createProcess( wchar_t const commandLine[], char const errMsg[] )
{
    try{ return winapi::createProcess( commandLine ); }
    catch( exception const& x ) { cpp::throwX( errMsg, x ); }
}

winapi::raw::Handle run( wchar_t const commandLine[] )
{
    namespace raw = winapi::raw;
    using cpp::hopefully;
    using cpp::throwX;
    using winapi::dup;
    using winapi::ProcessInfo;

    static char const* const createErrMsg = "Failed to create process";
    ProcessInfo const process = createProcess( commandLine, createErrMsg );

    // Early handle duplication ensures that one has the required rights.
    raw::Handle const   accessibleHandle    =
        dup( process.handle(), PROCESS_QUERY_INFORMATION | SYNCHRONIZE );

    raw::DWord const waitResult = WaitForSingleObject( process.handle(), INFINITE );
    hopefully( waitResult == WAIT_OBJECT_0 )
        || throwX( "Failed waiting for process termination." );

    return accessibleHandle;
}

class Interval
{
private:
    int     hours_;
    int     minutes_;
    int     seconds_;
    int     milliseconds_;

public:
    int msecs() const       { return milliseconds_; }
    int seconds() const     { return seconds_; }
    int minutes() const     { return minutes_; }
    int hours() const       { return hours_; }

    Interval( int msecs, int seconds = 0, int minutes = 0, int hours = 0 )
        : milliseconds_( msecs )
        , seconds_( seconds )
        , minutes_( minutes )
        , hours_( hours )
    {
        assert( unsigned( hours ) < 24 );
        assert( unsigned( minutes ) < 60 );
        assert( unsigned( seconds ) < 60 );
        assert( unsigned( msecs ) < 1000 );
    }

    static Interval fromMSecs( int msecs )
    {
        int const   totalSeconds    = msecs / 1000;
        int const   totalMinutes    = totalSeconds / 60;
        int const   totalHours      = totalMinutes / 24;

        return Interval(
            msecs % 1000, totalSeconds % 60, totalMinutes %60, totalHours
            );
    }
};

wostream& operator<<( wostream& stream, Interval const& t )
{
    wostringstream  formatter;

    formatter << setfill( L'0' );
    formatter
        << setw( 2 ) << t.hours() << ":"
        << setw( 2 ) << t.minutes() << ":"
        << setw( 2 ) << t.seconds() << "."
        << setw( 3 ) << t.msecs();
    return (stream << formatter.str());
}

string narrowStringFrom( cpp::SubstringRef const& s )
{
    return string( s.start(), s.end() );    // Non-ANSI characters => garbage.
}

void cppMain()
{
    namespace raw = winapi::raw;
    using cpp::hopefully;
    using cpp::implicitCast;
    using cpp::ScopeGuard;
    using cpp::SubstringRef;
    using cpp::throwX;
    using winapi::commandLineArgPart;
    using winapi::filenamePart;
    using winapi::mSecsFromRelative;
    using winapi::nextArgumentIn;

    SubstringRef const      programSpec         = nextArgumentIn( GetCommandLine() );
    SubstringRef const      programName         = filenamePart( programSpec );
    wchar_t const* const    otherCommandLine    = commandLineArgPart();

    hopefully( nextArgumentIn( otherCommandLine ).length() > 0 )
        || throwX( "Usage: " + narrowStringFrom( programName ) + " command" );

    raw::DWord const    startMSecs          = GetTickCount(); 
    raw::Handle const   finishedProcess     = run( otherCommandLine );
    raw::DWord const    endMSecs            = GetTickCount();
    raw::DWord const    realElapsedMSecs    = endMSecs - startMSecs;
    ScopeGuard const    closingHandle( [=]() { CloseHandle( finishedProcess ); } );

    Interval const      realElapsedTime = Interval::fromMSecs( realElapsedMSecs );

    static char const* const    commandLineLabel    = "Command line: ";
    static char const* const    rElapsedTimeLabel   = "External elapsed time:   ";
    static char const* const    pElapsedTimeLabel   = "In-process elapsed time: ";
    static char const* const    kernelTimeLabel     = "In-process kernel time:  ";
    static char const* const    userTimeLabel       = "In-process user time:    ";

    wclog << endl;
    wclog << commandLineLabel << "[" << otherCommandLine << "]" << endl;
    wclog << rElapsedTimeLabel << realElapsedTime << endl;

    raw::FileTime   creationTime;
    raw::FileTime   exitTime;
    raw::FileTime   kernelTime;
    raw::FileTime   userTime;
    bool const  timesWereObtained = !!GetProcessTimes(
        finishedProcess, &creationTime, &exitTime, &kernelTime, &userTime
        );
    hopefully( timesWereObtained )
        || throwX( "cppMain: GetProcessTimes failed" );

    int const   elapsedTimeMSecs= implicitCast<int>(
        mSecsFromRelative( exitTime ) - mSecsFromRelative( creationTime )
        );
    int const   kernelTimeMSecs = implicitCast<int>( mSecsFromRelative( kernelTime ) );
    int const   userTimeMSecs   = implicitCast<int>( mSecsFromRelative( userTime ) );

    wclog << pElapsedTimeLabel << Interval::fromMSecs( elapsedTimeMSecs ) << endl;
    wclog << kernelTimeLabel << Interval::fromMSecs( kernelTimeMSecs ) << endl;
    wclog << userTimeLabel << Interval::fromMSecs( userTimeMSecs ) << endl;
}

int main()
{
    try
    {
        cppMain();
        return EXIT_SUCCESS;
    }
    catch( exception const& x )
    {
        wcerr << "!" << x.what() << endl;
    }
    return EXIT_FAILURE;
}

答案 3 :(得分:0)

请考虑使用AllocConsole为流程显式创建控制台,而不是根据构建类型更改子系统模式。