不要在版本中显示控制台窗口,而是在debugging中显示

我想创build一个将作为最终产品在后台运行的程序。 为了debugging的目的,我希望它显示一个控制台。

我知道有一个ShowWindow(hWnd,SW_HIDE); 函数,但如果我在“标准”主函数中使用它,控制台窗口仍会popup一会儿。 我试图像这样工作(是的,我知道这是蹩脚的):

#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 

在这里,我设法防止窗口窗体popup,但我不能传递参数给程序。

我相信有一个更好的解决scheme。 你可以分享吗?

PS

我不想使用.NET。

这是问题的第一部分的答案,“在这里,我设法防止窗口窗体弹出”,即如何在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 = GetmoduleeHandle( 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 ); } 

您仍然可以将参数传递给常规的非控制台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来做到这一点。

这是问题的第二部分的答案,“但我不能传递参数到程序”,即如何获得Visual C ++ Windows应用程序中的命令行参数。

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

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

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

C ++ 11§3.6.1/ 2

“如果argc不为零,那么这些参数应该在argv[0]argv[argc-1] ,指向以NULL结尾的多字节字符串(NTMBS)(17.5.2.1.4.2)和argv[0]的起始字符作为NTMBS的初始字符的指针,表示用于调用程序的名称或""

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

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

另一方面,提供命令行CommandLineToArgvW的标准解析的API函数至少有一个傻瓜 ,可能还有更多…大概非标准的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; } 

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