发布时不显示控制台窗口,但在调试时显示

3
我想创建一个能够作为最终产品在后台运行的程序,但是出于调试目的,我希望它能显示控制台。
我了解到有一个名为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。


嗯,我要回答这个问题。同时,如果有其他想回答的人,请注意行为与是否使用标准的 main 函数无关。也就是说,如果你正在阅读这篇文章并认为如此,请先慢下来。 - Cheers and hth. - Alf
我不明白问题所在。为什么你在WinMain中调用GetConsoleWindow()?这是没有必要的。如果你不想在Release版本中显示窗口,那就不要创建一个窗口。你不需要使用#pragma comment来进行设置,Debug和Release配置的项目设置是分开的。 - Hans Passant
你应该考虑使用DebugView。http://technet.microsoft.com/en-us/sysinternals/bb896647 它只会在调试模式下显示调试字符串。请注意,由于只能附加一个调试器到正在运行的程序上,因此如果您在IDE下进行调试,则无法使用它。 - Steve Wellens
为什么需要显示控制台进行调试?如果您正在显示调试信息,应该使用日志记录。 - Kendall Frey
4个回答

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++编译器不支持链接器#pragma,因此使用项目设置更具可移植性)。

如果您希望对子系统进行编程检查而不仅仅是进行检查(即,而不是注意上面的程序是否产生控制台),则可以这样做:

// 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 );
}

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非零,则这些参数应作为指向以空字符结尾的多字节字符串(NTMBSs)(17.5.2.1.4.2)的初始字符的指针,由argv[0]到argv[argc-1]提供,并且argv[0]应该是表示用于调用程序的名称的NTMBS的初始字符的指针或""。”
然而,在第一个C++标准发布时(1998年),*nix世界约定和Windows约定都不是这样做。相反,约定是使用某种特定于区域设置的字符编码传递参数。Linux世界几乎立即开始向UTF-8迁移,而Windows没有,因此截至2012年,在Windows中,标准的main参数仍然不足以传递任意文件名等信息。

在Windows中,传递给进程的命令行可以通过GetCommandLine API函数获得,并且采用UTF-16编码,这意味着任何文件名(事实上任何文本)都可以被传递。

另一方面,提供命令行标准解析的API函数CommandLineToArgvW存在至少一个愚蠢的错误, 可能还有更多... 而非标准的Visual C++ Unicode C++启动函数wmain是由该函数提供参数的。因此,为了获得最佳结果,在此问题得到解决之前,应使用一些正确的自制命令行解析方式,例如下面程序中所示的方法(我只是选择了一个上周制作的“个人工具”程序,它类似于Windows 2000资源工具包中的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;
}

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来实现。


两个注意事项。(1) CommandLineToArgvW 有一个愚蠢的错误。(2) 如果应该避免使用 Visual C++ 特定的 #pragma 来设置 Windows 子系统,而是使用项目设置,那么为了保持一致性,我认为也应该避免使用 Visual C++ 特定的启动函数来检索 Unicode 参数,而是使用 GetCommandLine 函数+适当的解析(从而也避免上述错误)。 - Cheers and hth. - Alf

0

不要根据构建类型更改您的子系统模式,考虑使用AllocConsole来显式地为您的进程创建控制台。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接