|
Introduction
Getting list of arguments from command line is a common task which is required
by a lot of applications. However, there is no standard solution (as far as I know ;). So I wrote class CCmdLineParser, which can
parse arguments from command line, if they are typed in folowing form:
- /Key
- /KeyWithValue:Value
- /KeyWithComplexValue:"Some really complex value of /KeyWithComplexValue"
Of course, multiple keys, Unicode and long (up to 32Kb) command lines are supported.
This implementation requires the MFC or ATL CString class, or some clone with
similar interface as well as the STL class map.
Usage
First, you should construct the object and call the Parse function (from constructor or CCmdLineParser parser(::GetCommandLine());
or CCmdLineParser parser;
parser.Parse(_T("/Key /Key2:Val /Key3:\"Complex-Value\" -Key4"));
Then, there are two ways of working with results. You can check if some
particular key was specified in the command line:
if(parser.HasKey(_T("Key")) {
}
if(parser.HasKey(_T("Key2")) {
LPCTSTR szKey2Value = parser.GetVal(_T("Key2"));
}
LPCTSTR szKey3Value = parser.GetVal(_T("Key3"));
if(szKey3Value) {
} else {
}
LPCTSTR szKey4Value = parser.GetVal(_T("Key4"));
Another way to use is to enumerate all keys in command line:
CString sKey, sValue;
CCmdLineParser::POSITION pos = parser.getFirst();
while(!realParser.isLast(pos)) {
realParser.getNext(pos, sKey, sValue);
}
Customization and "how it works"
Repeated keys
If several different values are specified with same key, only the first value is stored.
So, if user passes command line /Add:One /Add:Two, /Add:Two will be ignored
and will not be added to parsed list.
Case sensitive/insensitive
By default, keys are not case-sensitive. So, /KeyOne is equal to
-keyONE. This is done by converting all keys to lowercase before storing them.
If you want to change this behaviour, call setCaseSensitive(true) or call
the constructor with the second argument set to true: CCmdLineParser parser(::GetCommandLine(), true);
This will switch the parser to case-sensitive mode, and if the user passes -key,
then GetKey(_T("Key")) will return false
Syntax
Formally, command line should be in following form: CommandLine::=[<Key> [,<Key>...]]
<Key>::=<Delimeter>KeyName[<Separator><Value>]
<Value> ::= { KeyValue | <QuoteChar>Quoted Key Value<QuoteChar>} ][
<Delimeter>::= { - | / }
<Separator>::= { : }
<QuoteChar>::= { " }
Values for <Delimeter>, <Separator> and
<QuoteChar> are stored in static variables
m_sDelimeters, m_sValueSep and m_sQuotes
respectively. If you want to change them (for instance, allow user to specify
quoted values in apostrophes), you can do it in the beginning of
CmdLineParser.cpp:
const TCHAR CCmdLineParser::m_sQuotes[] = _T("\"\'");
Note: If you want to change m_sDelimeters, space must be the first character of
this string.
Also, if you have your own CString class with other name than CString,
you can change it in the beginning of CmdLineParser.h:
typedef MyOwnCString CCmdLineParser_String;
That's it! ;)
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 31 (Total in Forum: 31) (Refresh) | FirstPrevNext |
|
 |
|
|
Could we easily find out some filepath string in cmdline? It's easy if we used /FileNameKey:"THE PATH OF FILE",like the sample in your article. But in normally, filepath string be used don't have the marked part /FileNameKey . and then there is a easy way to find out it?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Good job! thanks very much. But when the execute file is in one folder which include the Delimeter character "-", the Parse() function will take it as a key mistakenly. I think this is a bug. And why don't you use the argc & argv? Below is my implementation of CCmdLineParser::CCmdLineParser(). I think this is simpler and easier to understand than yours.
const TCHAR CCmdLineParser::m_sDelimeters[] = _T("/"); // Can be _T("-/"), for instance const TCHAR CCmdLineParser::m_sValueSep[] = _T(":"); // Can be _T("=:"), for instance. Space need NOT be in set. //const TCHAR CCmdLineParser::m_sQuotes[] = _T("\""); // No need any more! so I comment out this line.
CCmdLineParser::CCmdLineParser(int argc, TCHAR* argv[], bool bCaseSensitive) : m_bCaseSensitive(bCaseSensitive) { const CString sEmpty; m_ValsMap.clear();
for (int i=1; i<argc; i++) { LPCTSTR sArg = _tcspbrk(argv[i], m_sDelimeters); if ( (NULL == sArg) || (sArg != argv[i]) || ('\0' == sArg[1]) ) { // The m_sDelimeters chacracter not found || The first character is not in m_sDelimeters set || cmdline ends with / continue; } sArg = _tcsinc(sArg); LPCTSTR sVal = _tcspbrk(sArg, m_sValueSep);
if( NULL == sVal ) { // cmdline ends with /Key CString csKey(sArg); if(!m_bCaseSensitive) { csKey.MakeLower(); } m_ValsMap.insert(CValsMap::value_type(csKey, sEmpty)); } else { // cmdline ends with /Key:* CString csKey(sArg, sVal - sArg); if(!csKey.IsEmpty()) { // Prevent /: case if(!m_bCaseSensitive) { csKey.MakeLower(); } if ( 1 == _tcslen(sVal) ) { // cmdline ends with /Key: sVal = sEmpty; } else { // cmdline ends with /Key:Val sVal = _tcsinc(sVal); } m_ValsMap.insert(CValsMap::value_type(csKey, sVal)); } } } }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Sorry, there was a bug in my code, too. When the "Val" is enclosed in double-quotation, the last charater of argv[i] became '\"'. Below is the revised version.
CCmdLineParser::CCmdLineParser(int argc, TCHAR* argv[], bool bCaseSensitive) : m_bCaseSensitive(bCaseSensitive) { m_ValsMap.clear();
for (int i=1; i<argc; i++) { LPCTSTR sArg = _tcspbrk(argv[i], m_sDelimeters); if ( (NULL == sArg) || (sArg != argv[i]) || ('\0' == sArg[1]) ) { // The m_sDelimeters chacracter not found || The first character is not in m_sDelimeters set || cmdline ends with / continue; } sArg = _tcsinc(sArg); LPTSTR sVal = _tcspbrk(sArg, m_sValueSep);
if(sVal == NULL) { // cmdline ends with /Key CString csKey(sArg); if( !m_bCaseSensitive ) { csKey.MakeLower(); } m_ValsMap.insert(CValsMap::value_type(csKey, "")); } else { // cmdline ends with /Key:* CString csKey(sArg, sVal - sArg); if( !csKey.IsEmpty() ) { // Prevent /: case if( !m_bCaseSensitive ) { csKey.MakeLower(); } size_t stLen = _tcslen(sVal); if ( 1 == stLen ) { // cmdline ends with /Key: sVal = _T(""); } else { // cmdline ends with /Key:Val if ('\"' == sVal[stLen-1]) { sVal[stLen-1] = '\\'; } sVal = _tcsinc(sVal); } m_ValsMap.insert(CValsMap::value_type(csKey, sVal)); } } } }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
A very easy to use and simple interface. I like the fact that you integrated easily with GetCommandLine().
Thanks! Kevin
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I have been using this class with modifications in my project, so thought I'd contribute something back.
Added support for parameters with two dashes (like --foobar) or actually any number of delimiter combinations (-/-foobar):
LPCTSTR pszArg = _tcspbrk(pszCurrent, m_pszDelimeters); if (! pszArg) { // No delimeters found break; }
LPCTSTR pszInArg; do { pszArg = _tcsinc(pszArg); pszInArg = _tcspbrk(pszArg, m_pszDelimeters); } while (pszInArg == pszArg);
...
I also derived from CMapStringToString as many have suggested already and removed all the find and iterator functions to use those of the base class. Made code a lot simpler.
In addition, for those that use CppUnit for unit testing, here's a unit test:
typedef struct { LPCTSTR pszName; LPCTSTR pszValue; } CCmdLineTestDataPair;
typedef struct { LPCTSTR pszCmdLine; BOOL fCaseSensitive; ULONG ulParameters; CCmdLineTestDataPair testDataPairs[3]; } CCmdLineTestData;
void CCmdLineUnitTest::testParseCmdLine(void) { CCmdLineTestData testdata[] = { { _T(""), FALSE, 0, { { NULL, NULL }, { NULL, NULL }, { NULL, NULL } } }, { _T("/test"), FALSE, 1, { { _T("test"), NULL }, { NULL, NULL }, { NULL, NULL } } }, { _T("--test"), FALSE, 1, { { _T("test"), NULL }, { NULL, NULL }, { NULL, NULL } } }, { _T("/-/test"), FALSE, 1, { { _T("test"), NULL }, { NULL, NULL }, { NULL, NULL } } }, { _T("----test"), FALSE, 1, { { _T("test"), NULL }, { NULL, NULL }, { NULL, NULL } } }, { _T("/p1 /p2"), TRUE, 2, { { _T("p1"), NULL }, { _T("p2"), NULL }, { NULL, NULL } } }, { _T("/P1 /p2"), FALSE, 2, { { _T("p1"), NULL }, { _T("p2"), NULL }, { NULL, NULL } } }, { _T("/P1 /p2 /p2"), FALSE, 2, { { _T("p1"), NULL }, { _T("p2"), NULL }, { NULL, NULL } } }, { _T("/p1=p1value /p2=p2value /p3=p3value"), FALSE, 3, { { _T("p1"), _T("p1value") }, { _T("p2"), _T("p2value") }, { _T("p3"), _T("p3value") } } }, { _T("/p1=\"p1value\" /p2=\'p2value\' /p3:`p3value`"), FALSE, 3, { { _T("p1"), _T("p1value") }, { _T("p2"), _T("p2value") }, { _T("p3"), _T("p3value") } } } };
for (int i = 0; i < sizeof(testdata) / sizeof(CCmdLineTestData); i++) { // std::wcout << testdata[i].pszCmdLine << std::endl; CCmdLine c(testdata[i].pszCmdLine, testdata[i].fCaseSensitive); CPPUNIT_ASSERT_MESSAGE("Invalid command line parsed.", c.GetCmdLine().Compare(testdata[i].pszCmdLine) == 0); CPPUNIT_ASSERT_MESSAGE("Invalid case sensitive parameter.", c.GetCaseSensitive() == testdata[i].fCaseSensitive); CPPUNIT_ASSERT_MESSAGE("Invalid parameter count.", c.GetCount() == testdata[i].ulParameters);
for (int j = 0; j < sizeof(testdata[i].testDataPairs) / sizeof(CCmdLineTestDataPair); j++) { if (testdata[i].testDataPairs[j].pszName == NULL && testdata[i].testDataPairs[j].pszValue == NULL) break;
CString strValue; CPPUNIT_ASSERT_MESSAGE("Expected parameter not found.", c.Lookup(testdata[i].testDataPairs[j].pszName, strValue)); CPPUNIT_ASSERT_MESSAGE("Parameter value does not match.", ((strValue.GetLength() == 0) && (testdata[i].testDataPairs[j].pszValue == NULL)) || strValue.Compare(testdata[i].testDataPairs[j].pszValue) == 0); } }
}
cheers dB.
|
| Sign In·View Thread·PermaLink | 3.00/5 (1 vote) |
|
|
|
 |
|
|
Someone asked me for full code.
CmdLine.h:
/*++
Notes: Based on CCmdLine Copyright (c) Pavel Antonov, 2002 http://www.codeproject.com/cpp/cmdlineparser.asp Changes by dB. (www.dblock.org)
Formats: CommandLine::=[ [,...]] ::=KeyName[] ::= { KeyValue | Quoted Key Value} ][ ::= { - | / } ::= { : } ::= { " } --*/
#pragma once
class CCmdLine : public CMapStringToString { DECLARE_DYNAMIC(CCmdLine) public: CCmdLine(LPCTSTR sCmdLine = NULL, BOOL bCaseSensitive = FALSE); virtual ~CCmdLine(void); void Parse(LPCTSTR pszCmdLine); inline const CString& GetCmdLine(void) const { return m_strCmdLine; } inline void SetCaseSensitive(BOOL bSensitive) { m_bCaseSensitive = bSensitive; } inline BOOL GetCaseSensitive(void) const { return m_bCaseSensitive; } inline BOOL HasKey(LPCTSTR pszKey) const { CString strValue; return CMapStringToString::Lookup(pszKey, strValue); } private: CString m_strCmdLine; BOOL m_bCaseSensitive; static const TCHAR m_pszDelimeters[]; static const TCHAR m_pszValueSep[]; static const TCHAR m_pszQuotes[]; };
#ifdef CPPUNIT_API class CCmdLineUnitTest : public CppUnit::TestCase { public: void testParseCmdLine(void); public: CPPUNIT_TEST_SUITE(CCmdLineUnitTest); CPPUNIT_TEST(testParseCmdLine); CPPUNIT_TEST_SUITE_END(); };
CPPUNIT_TEST_SUITE_REGISTRATION(CCmdLineUnitTest); #endif
CmdLine.cpp:
#include "stdafx.h"
IMPLEMENT_DYNAMIC(CCmdLine, CMapStringToString)
const TCHAR CCmdLine::m_pszDelimeters[] = _T("-/"); const TCHAR CCmdLine::m_pszQuotes[] = _T("\"\'`"); const TCHAR CCmdLine::m_pszValueSep[] = _T(" :="); // Space MUST be in set, but is not a name/value delimiter.
CCmdLine::CCmdLine(LPCTSTR pszCmdLine, BOOL bCaseSensitive) : m_bCaseSensitive(bCaseSensitive) { if (pszCmdLine != NULL) { Parse(pszCmdLine); } }
CCmdLine::~CCmdLine() {
}
void CCmdLine::Parse(LPCTSTR pszCmdLine) { if (! pszCmdLine) return; m_strCmdLine = pszCmdLine; RemoveAll();
int nArgs = 0; LPCTSTR pszCurrent = pszCmdLine; while(TRUE) { // /Key:"arg" if (_tcslen(pszCurrent) == 0) { // No data left break; }
LPCTSTR pszArg = _tcspbrk(pszCurrent, m_pszDelimeters); if (! pszArg) { // No delimeters found break; }
LPCTSTR pszInArg; do { pszArg = _tcsinc(pszArg); pszInArg = _tcspbrk(pszArg, m_pszDelimeters); } while (pszInArg == pszArg); // Key:"arg" if (_tcslen(pszArg) == 0) { // String ends with delimeter break; }
LPCTSTR pszVal = _tcspbrk(pszArg, m_pszValueSep); if (pszVal == NULL) { // Key ends command line CString strKey(pszArg); if(! m_bCaseSensitive) { strKey.MakeLower(); } SetAt(strKey, _T("")); break; } else if (pszVal[0] == _T(' ') || _tcslen(pszVal) == 1 ) { // Key with no value or cmdline ends with /Key: CString strKey(pszArg, (int) (pszVal - pszArg)); if(! strKey.IsEmpty()) { // Prevent /: case if(!m_bCaseSensitive) { strKey.MakeLower(); } SetAt(strKey, _T("")); }
pszCurrent = _tcsinc(pszVal); continue; } else { // Key with value CString strKey(pszArg, (int) (pszVal - pszArg)); if (! m_bCaseSensitive) { strKey.MakeLower(); }
pszVal = _tcsinc(pszVal); // "arg" LPCTSTR pszQuote = _tcspbrk(pszVal, m_pszQuotes), pszEndQuote(NULL); if (pszQuote == pszVal) { // Quoted String pszQuote = _tcsinc(pszVal); pszEndQuote = _tcspbrk(pszQuote, m_pszQuotes); } else { pszQuote = pszVal; pszEndQuote = _tcschr(pszQuote, _T(' ')); }
if (pszEndQuote == NULL) { // No end quotes or terminating space, take rest of string CString strValue(pszQuote); if(! strKey.IsEmpty()) { // Prevent /:val case SetAt(strKey, strValue); } break; } else { // End quote or space present if(! strKey.IsEmpty()) { // Prevent /:"val" case CString strValue(pszQuote, (int) (pszEndQuote - pszQuote)); SetAt(strKey, strValue); } pszCurrent = _tcsinc(pszEndQuote); continue; } } } }
#ifdef CPPUNIT_API typedef struct { LPCTSTR pszName; LPCTSTR pszValue; } CCmdLineTestDataPair;
typedef struct { LPCTSTR pszCmdLine; BOOL fCaseSensitive; ULONG ulParameters; CCmdLineTestDataPair testDataPairs[3]; } CCmdLineTestData;
void CCmdLineUnitTest::testParseCmdLine(void) { CCmdLineTestData testdata[] = { { _T(""), FALSE, 0, { { NULL, NULL }, { NULL, NULL }, { NULL, NULL } } }, { _T("/test"), FALSE, 1, { { _T("test"), NULL }, { NULL, NULL }, { NULL, NULL } } }, { _T("--test"), FALSE, 1, { { _T("test"), NULL }, { NULL, NULL }, { NULL, NULL } } }, { _T("/-/test"), FALSE, 1, { { _T("test"), NULL }, { NULL, NULL }, { NULL, NULL } } }, { _T("----test"), FALSE, 1, { { _T("test"), NULL }, { NULL, NULL }, { NULL, NULL } } }, { _T("/p1 /p2"), TRUE, 2, { { _T("p1"), NULL }, { _T("p2"), NULL }, { NULL, NULL } } }, { _T("/P1 /p2"), FALSE, 2, { { _T("p1"), NULL }, { _T("p2"), NULL }, { NULL, NULL } } }, { _T("/P1 /p2 /p2"), FALSE, 2, { { _T("p1"), NULL }, { _T("p2"), NULL }, { NULL, NULL } } }, { _T("/p1=p1value /p2=p2value /p3=p3value"), FALSE, 3, { { _T("p1"), _T("p1value") }, { _T("p2"), _T("p2value") }, { _T("p3"), _T("p3value") } } }, { _T("/p1=\"p1value\" /p2=\'p2value\' /p3:`p3value`"), FALSE, 3, { { _T("p1"), _T("p1value") }, { _T("p2"), _T("p2value") }, { _T("p3"), _T("p3value") } } } };
for (int i = 0; i < sizeof(testdata) / sizeof(CCmdLineTestData); i++) { // std::wcout << testdata[i].pszCmdLine << std::endl; CCmdLine c(testdata[i].pszCmdLine, testdata[i].fCaseSensitive); CPPUNIT_ASSERT_MESSAGE("Invalid command line parsed.", c.GetCmdLine().Compare(testdata[i].pszCmdLine) == 0); CPPUNIT_ASSERT_MESSAGE("Invalid case sensitive parameter.", c.GetCaseSensitive() == testdata[i].fCaseSensitive); CPPUNIT_ASSERT_MESSAGE("Invalid parameter count.", c.GetCount() == testdata[i].ulParameters);
for (int j = 0; j < sizeof(testdata[i].testDataPairs) / sizeof(CCmdLineTestDataPair); j++) { if (testdata[i].testDataPairs[j].pszName == NULL && testdata[i].testDataPairs[j].pszValue == NULL) break;
CString strValue; CPPUNIT_ASSERT_MESSAGE("Expected parameter not found.", c.Lookup(testdata[i].testDataPairs[j].pszName, strValue)); CPPUNIT_ASSERT_MESSAGE("Parameter value does not match.", ((strValue.GetLength() == 0) && (testdata[i].testDataPairs[j].pszValue == NULL)) || strValue.Compare(testdata[i].testDataPairs[j].pszValue) == 0); } }
} #endif
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Your coding style is too verbose.
You have created wrapper functions for every member variable. There are even member functions that simply call the appropriate stl::map method.
You have mixed CString with stl code - not good.
And your coding style reminds me of a C coder who once told me he could write an editor in 3 lines.
Go figure.
Still thanks for the code, I have chopped most of it but the algortihm works.
|
| Sign In·View Thread·PermaLink | 1.00/5 (2 votes) |
|
|
|
 |
|
|
I see absoultly nothing wrong with the coding style and for the most part I believe that it is written quite well.
John
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
douglash1 wrote: You have created wrapper functions for every member variable.
So?
douglash1 wrote: There are even member functions that simply call the appropriate stl::map method. Encapsulation of the data structures used. Makes sense.
douglash1 wrote: You have mixed CString with stl code - not good.
Why? CString gives stronger performance guarantees than std::string. I find them necessary in many circumstances.
we are here to help each other get through this thing, whatever it is Vonnegut jr. boost your code || Fold With Us! || sighist | doxygen
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Nice class. You should consider changing the CValsMap from deriving from std::map to just a typedef so from :
class CValsMap : public map {};
to
typedef std::map CValsMap;
You're not supposed to be able to derive from a class like map, which doesn't have a virtual destructor, as, according to the C++ standard, the behaviour is undefined when the object is destroyed.
Cheers
Jim
¡El diablo está en mis pantalones! ¡Mire, mire!
Real Mentats use only 100% pure, unfooled around with Sapho Juice(tm)!
SELECT * FROM User WHERE Clue > 0 0 rows returned
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Jim Crafton wrote: You're not supposed to be able to derive from a class like map, which doesn't have a virtual destructor, as, according to the C++ standard, the behaviour is undefined when the object is destroyed.
That's not strictly true, the standard does not make restriction on what you can derive from. There is only a problem if there is a pointer somewhere in the code to the base class and then the code "deletes" that base class. And then that is only a problem if the derived class has destructable (i.e. class based) data members. If he were to change the inheritance to private or protected that would make it safer.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks for your article & code, i've changed the std::map to CMap (I have to run it on PPC) which I think you used before (looking at how things are defined).
Your class will be used in our mobile solution in an intermediate app that activates a route planning tool from our main app.(application is called FieldAssist (http://www.fieldassist.nl/[^])
Regards
Rutger
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Pavel,
Your class runs great! What license have you issued your code under? Is anyone free to use it in any application, open or closed source? I'd like to use it in an non-free application where I work.
Thanks! 
R. Douglas Barbieri
dougbarbieri@yahoo.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Simple command parsing that you would expect and have seen everywhere doesn't work.. This code doesn't handle simple args/flags like "myprog -v -g input_file". One the code doesn't handle the -v arg and it doesn't handle a dangling filename which isn't associated with an arg. This a common method of usage. I'd like to see something like CCommandLineInfo but with the ability to handle spaces passed with an arg, i.e. quoted arg. What I don't need is something that creates its own arg mechansism; i.e. /key:value. MLS
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Try using argtable. It is an ANSI C lib for parsing GNU style command line arguments like you have described. It adheres to the GNU getopt standard. The homepage is http://argtable.sourceforge.net.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Command-line parsing is one of these problems you'd think is well known and has been solved nicely by hundreds, but actually discover all (ok, most) implementations are either obscure, difficult to use, plain wrong, or all of these ! Then you find the CCmdLineParser and the sky is blue again  Congratulations for the very good job and thanks for making it available to all of us.
Serge
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
first i must say, really good work..
but one problem:
if i get a command line with a filename like this: /file:c:\new folder\index.html
the parser only gets chars until the space. Is there any solution ?
thx,
jogi
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanx for your thanks )))
And about spaces -- it will work fine if you enclose value with spaces with quotes, like this: /file:"c:\new folder\index.html"
Pavel
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Hi Pavel;
Very nice work. I have a question - do you know how to grab the ">" redirection token from a command line. My app needs to determine the right output file, but I don't get any parameters beyond the ">". For example:
test.exe input.txt > output.txt
... I don't get the '>' or 'output.txt'
Thanks well in advance!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello, lets see if my first post actually succeeds 
The reason you don't get the "> output.txt" or any other piping or redirection tokens, is that they arent passed to the program. Redirection is the work of the command interpreter, like in unix Bash or sh, and in Windows command.com or cmd. It works simply by redirecting everything you print to stdout into the textfile, so in fact you don't have to do anything, just print like you always do with:
cout << "your text" << endl;
and it gets redirected by the interpreter to the user specified file. if you really want to do the printing yourself you have to use your own commandline switch like /outputfile:"output.txt" or somesuch.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks for the reply. I am experimenting with at least one (untested) way to see if redirection has occurred (which would be fine with me)
In InitInstance I do a
HANDLE hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );
If there was no redirection of stdout on the command line, then hStdOut is always 0x00010001. If there was redirection to a file name, the handle is something other value than 0x00010001.
...now the question: is "0x00010001" a standard handle. I suspect it might be the handle for "CONOUT$".
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|