1
\$\begingroup\$

My program can be called like this:

FakeDpi someInteger someExecutable [parametersForSomeExecutable]

For example:

FakeDpi 120 "C:\Program Files (x86)\...\excel.exe" /X "C:\Users\...\myFile.xlsx"

should result in

dpi = 120
exe = C:\Program Files (x86)\...\excel.exe
args = /X "C:\Users\...\myFile.xlsx"

args should be extracted as verbatim as possible, since I don't know which argument parsing method the target program uses.

It works, but it seems like a lot of code for such a seemingly "simple" task. Please tell me if I'm reinventing the wheel somewhere along the way:

#include "stdafx.h"
#include <iostream>
#include <string>
#include <sstream>

void parseCommandLine(int &dpi, std::wstring &exe, std::wstring &args);
void showUsage();
std::wstring parseStringArgument(std::wistringstream& ss);
int parseIntArgument(std::wistringstream& ss);

/* Return values:
   0 = Success
   1 = Invalid arguments
 */
int main()
{
    // Command line parameters
    int dpi;
    std::wstring exe;
    std::wstring args;

    // Parse command line manually, so that we can forward the
    // target program command line completely unmodified.
    try
    {
        parseCommandLine(dpi, exe, args);
    }
    catch (const std::invalid_argument& e)
    {
        std::wcerr << e.what() << "\n\n";
        showUsage();
        return 1;
    }

    // Do work
    ...
    return 0;
}

void showUsage()
{
    std::wcerr << "Usage: FakeDpi Dpi Command [Command-Line Arguments]\n\n"
        << "Example: FakeDpi 120 \"C:\\Program Files\\...\\myApp.exe\" myFile.xls\n";
}

void parseCommandLine(int &dpi, std::wstring &exe, std::wstring &args)
{
    std::wistringstream ss(GetCommandLineW());

    parseStringArgument(ss); // Skip own program name
    dpi = parseIntArgument(ss);
    exe = parseStringArgument(ss);

    std::getline(ss, args);
}

// Parses one space-delimited argument from the stream. If
// the argument is enclosed in double quotes, it may contain
// spaces and it is returned without the quotes.
std::wstring parseStringArgument(std::wistringstream& ss)
{
    if (ss.eof())
    {
        throw std::invalid_argument("End of argument list reached.");
    }

    std::wstring ret;

    wchar_t delim = L' ';
    if (ss.peek() == L'"')
    {
        delim = L'"';
        ss.ignore();    // skip starting quote
    }
    std::getline(ss, ret, delim);
    if (delim == L'"')
    {
        if (ss.eof())
        {
            throw std::invalid_argument("Closing quote not found.");
        }
        ss.ignore();    // skip closing quote
    }

    ss >> std::ws;
    return ret;
}

int parseIntArgument(std::wistringstream& ss)
{
    std::wstring s = parseStringArgument(ss);

    wchar_t *end;
    errno = 0;
    int ret = wcstol(s.c_str(), &end, 10);

    if (errno != 0 
        || s.empty()    /* could be checked earlier, but this is not time critical */ 
        || *end != '\0' /* trailing non-numeric content */)
    {
        throw std::invalid_argument("Input is not a valid integer.");
    }

    return ret;
}

Note: I deliberately ignore the "argument ends with an even/uneven number of backslashes" madness of Windows command-line parsing, since it does not make sense for any of the parameters to end in a backslash.

\$\endgroup\$
  • 1
    \$\begingroup\$ Yes, you are reinventing the wheel here. There are lots of libraries for this, use Google to find some. The most common is probably Boost's program_options. Problem is, you aren't requiring the standard syntax for command-line parameters. They are expected to be preceded with either a / or a - (or, preferably, whichever one the user wants). \$\endgroup\$ – Cody Gray Dec 31 '16 at 12:32
  • \$\begingroup\$ @Cody Would boost::program_options support unicode? \$\endgroup\$ – πάντα ῥεῖ Dec 31 '16 at 12:56
  • \$\begingroup\$ Yes, it is a template library, so you can use the wchar_t type for the parser functions just as easily as char. \$\endgroup\$ – Cody Gray Dec 31 '16 at 13:07
  • \$\begingroup\$ @CodyGray: Thanks for the link. It appears that there is no way to make program_options stop tokenizing the string after a certain point and return the remainder verbatim. \$\endgroup\$ – Heinzi Dec 31 '16 at 13:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.