这是对问题的第二部分的答复,“但我无法将参数传递给程序”,即如何在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
):
#undef UNICODE
#define UNICODE
#undef STRICT
#define STRICT
#undef NOMINMAX
#define NOMINMAX
#include <windows.h>
#include <shlwapi.h>
#include <assert.h>
#include <functional>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <stddef.h>
#include <stdexcept>
#include <stdint.h>
#include <string>
#include <type_traits>
#include <utility>
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
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;
}
};
};
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& );
ScopeGuard& operator=( ScopeGuard const& );
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 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;
}
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 );
}
inline wchar_t const* commandLineArgPart()
{
SubstringRef const programSpec = nextArgumentIn( GetCommandLine() );
return theAfterWhitespacePart( programSpec.end() );
}
class ProcessInfo
{
private:
raw::ProcessInformation info_;
ProcessInfo( ProcessInfo const& );
ProcessInfo& operator=( ProcessInfo const& );
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();
}
};
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,
&mutableCommandLine[0],
nullptr,
nullptr,
true,
NORMAL_PRIORITY_CLASS,
nullptr,
nullptr,
&startupInfo,
&processInfo.raw()
);
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
);
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() );
}
}
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 );
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() );
}
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;
}
main
函数无关。也就是说,如果你正在阅读这篇文章并认为如此,请先慢下来。 - Cheers and hth. - Alf