Pull to refresh

C/C++. Способ разбора командной строки

Reading time3 min
Views24K
Не так давно на работе встала передо мной задача написать прогу в среде C++ Builder, и был в ней момент, когда нужно парсить командную строку. К задаче прилагалось так же волшебное «можешь юзать все исходники, которые есть. Лежат они тут: ...». Первым делом полез, конечно, по адресу… и нашел там жутко ветвящуюся структуру кода, в которой попытался разобраться и решил, что подгонять ее под себя – ад. Поэтому пришло мне в голову написать что-то подобное Qt-шной системе сигналов и слотов, только для C++ Builder’а и аргументов командной строки.
Итак, начнем. Идея такова: анализ командной строки сводится к проверке, есть ли в аргументе спецсимвол (в моем случае – это «-» – был взять стандарт Linux). В зависимости от этого аргумент читается как имя параметра или его значение. Для вызова функций обработки используется ассоциативный массив, т.е. массив в котором в качестве ключей будут имена доступных параметров, а значений – адреса функций обработки конкретного параметра. Вот, в общем-то, и все. Приступим к реализации?

Вперед. Начнем, пожалуй, с изучения приборов. Для обработки ассоциативных массивов в C++ Builder используется тип map (объявлен в map.h)

#include <map.h>

Далее по справке. Объявим свой тип:

typedef map <AnsiString, int*> TFuncTable;

и в классе экземпляр типа:

TFuncTable funcTable;

Здесь, в качестве ключа используется AnsiString (да простит меня std::string), а значения – int* (кстати, может не прокатить для 64-битных систем). Теперь, посмотрим, как сделать insert в этот хеш. Оказалось, это не так-то просто и на это пришлось убить часа 2. А делается это так:

int *addr = (int*)&myFunction;
funcTable.insert(TFuncTable::value_type(AnsiString("-myParam"), addr));

Обратить внимание стоит на TFuncTable::value_type() – зачем так делать, я, честно сказать, так и не понял, но, видимо, это какой-то подгон типов, без которого прога даже не компилится.
Итак, проинициализировали массив – замечательно. Как он выглядеть будет? Да как-то так:

funcTable[“-myParam”] == 0xABCDEF //адрес функции-обработчика параметра

Медленно, но верно подходим к использованию всего этого хозяйства. Сразу стоит упомянуть один минус такого алгоритма: функции обработки должны быть одного вида, то есть мы объявляем функциональный тип:

typedef void (*TFunc)(AnsiString param);

и придерживаемся его в написании своих обработчиков. Пример такого обработчика:

void myFunction(AnsiString name)
{
MainForm->setTestName(name);
}

Но это простой обработчик. Сложнее будет, пожалуй, когда в качестве параметра приходит что-то типа:

"param1,param2,param3"

Для реального примера можно глянуть файлик /etc/fstab – Linux.
Что же, пора рассказать, как же запустить функцию из хеша по имени параметра. Да, но перед этим, думаю, стоит собрать всю процедуру инициализации в кучу:
  1. Включили map
  2. Объявили тип-хеш, объявили экземпляр этого типа.
  3. Объявили функциональный тип и написали функции-обработчики параметров, придерживаясь этого типа.
  4. Заполнили хеш.

Теперь можно приступать к функции обработки самой командной строки. Вот так выглядит алгоритм вычисления, является ли аргумент именем параметра или его значением:

bool TMainForm::parseParams()
{
int argc;
TFunc srvFunc;
LPWSTR *cmdLine = CommandLineToArgvW(GetCommandLineW(), &argc);
int iter = 1;

for(int i = 1; i < argc; i += iter)
{
AnsiString param = "";
AnsiString paramName = cmdLine[i];
if(i < argc - 1)
{
if(AnsiString(cmdLine[i + 1])[1] == '-')
iter = 1;
else
{
param = cmdLine[i + 1];
iter = 2;
}
}

if(funcTable.find(paramName) == funcTable.end())
{
Out("Неизвестный параметр " + paramName);
return false;
}
srvFunc = (TFunc)funcTable[paramName];
srvFunc(param);
}
return true;
}


Что есть что:
LPWSTR *cmdLine = CommandLineToArgvW(GetCommandLineW(), &argc); — с этим в борландовской справке тоже все довольно мутно, но сводится все к тому, что такая последовательность преобразований выдает стандартные argc и argv, поэтому идем дальше.
Out() — специализированная функция ввода / вывода. Для заданной темы никакого интереса не представляет.
Процедуру поиска символа «-» в аргументе строки, думаю, рассказывать не надо – она банальна. Самое интересное тут:

srvFunc = (TFunc)funcTable[paramName];
srvFunc(param);

Так запускается функция обработчик. Просто, не правда ли? И никаких диких ветвлений до 1000-го колена.

Итак, что мы имеем? Работаю я в гос. конторе, и именно поэтому задерживаться там не собираюсь. То есть, пришедший после меня работник с легкостью напишет свои обработчики, загонит адреса в хеш, а насчет анализатора командной строки ему думать не придется. Вот такая арифметика.
Tags:
Hubs:
Total votes 12: ↑6 and ↓60
Comments12

Articles