Наконец-то добрался до полезной практической задачи по корректному разбору параметров командной строки. На языках высокого уровня это делается чуть ли не одной командой, а на Ассемблере как обычно приходится все делать самостоятельно. Решение получилось универсальным, подходит как для консольных, так и для GUI-приложений. Для использования функции ParseCmdLine в сегменте данных надо предварительно определить следующую структуру:
; Структура для командной строки
struct CMDLINE
nCount dd ? ; Количество аргументов
lpArgs dd ? ; Указатель на массив адресов строк
lpArgStr dd ? ; Указатель на массив строк
ends
Формат структуры: nCount - количество параметров командной строки, в случае успешного вызова функции это значение обязательно будет ненулевым, так как самый первый параметр - полный путь запуска программы, а остальные аргументы из хвоста командной строки будут расположены, начиная со второго элемента массива. lpArgStr - указатель на массив параметров командной строки. Все строки в этот массив записываются последовательно одна за другой в формате ASCIIZ, если строки были не в кавычках, то с начала и конца строки удаляются избыточные пробелы и символы табуляции. lpArgs - указатель на массив адресов разобранных параметров командной строки. Какого-то отдельного признака окончания массива не предусмотрено, количество элементов берется из значения nCount.
Сама функция разбора командной строки у меня получилась такая. Для работы используется память из кучи (Heap), оригинальная командная строка в памяти не модифицируется.
;------------------------------------------------------------------
; Функция разбора параметров командной строки
; Copyright (C) ManHunter / PCL
; http://www.manhunter.ru
;
; Параметры:
; CmdStr - указатель на структуру CMDLINE
; dQuotFlag - сохранять кавычки: TRUE - сохранять, FALSE - убирать
; На выходе:
; заполненная структура CMDLINE с параметрами командной строки
;------------------------------------------------------------------
proc ParseCmdLine CmdStr:dword, dQuotFlag:dword
local _hheap:DWORD ; Локальные переменные
local _tmp:DWORD
local _nCount:DWORD
local _dAct:DWORD
virtual at 0
localcmd CMDLINE ; Локальная проекция структуры
end virtual
pusha
; Получить адрес кучи
invoke GetProcessHeap
mov [_hheap],eax
mov [_nCount],0 ; Обнулить счетчик аргументов
mov [_dAct],0 ; Обнулить флаг "работаю"
; Выделить память для сохранения обработанных аргументов
invoke HeapAlloc,[_hheap],HEAP_ZERO_MEMORY,1000h
mov edi,eax
mov [_tmp],eax
; Получить командную строку
invoke GetCommandLine
mov esi,eax
xor ebx,ebx ; Флажок-признак, что аргумент в кавычках
.parse_scan:
lodsb
or al,al ; Строка закончилась?
jz .parse_end
cmp al,'"' ; Найдена кавычка в параметрах?
jz .parse_quote
cmp ebx,1 ; Уже обрабатывается строка в кавычках
je .parse_arg_in_quote
cmp al,' ' ; Встретили пробел, записать параметр
je .parse_push_arg
cmp al,09h ; Встретили табулятор, записать параметр
je .parse_push_arg
.parse_arg_in_quote:
stosb ; Записать текущий символ
mov [_dAct],1 ; Установить флаг "работаю"
jmp .parse_scan
.parse_push_arg:
mov al,0 ; Окончание строки
stosb
inc [_nCount]
mov [_dAct],0 ; Обнулить флаг "работаю"
.parse_remove_space:
lodsb ; Пропустить пробелы и табуляторы
or al,al
jz .parse_end
cmp al,'"'
je .parse_start_quote
cmp al,' '
je .parse_remove_space
cmp al,09h
je .parse_remove_space
dec esi
jmp .parse_scan
; Обработка кавычек в командной строке
.parse_quote:
or ebx,ebx
jnz .parse_end_quote
.parse_start_quote:
; Если флаг установлен, то записать и кавычки
cmp [dQuotFlag],TRUE
jne @f
stosb
@@:
; Установить флаг что обрабатывается параметр в кавычках
mov ebx,1
jmp .parse_scan
.parse_end_quote:
; Если флаг установлен, то записать и кавычки
cmp [dQuotFlag],TRUE
jne @f
stosb
@@:
xor ebx,ebx
jmp .parse_push_arg
.parse_end:
; Окончание параметров
xor eax,eax
stosw
mov eax,[_dAct]
add [_nCount],eax
mov ecx,edi
sub ecx,[_tmp]
push ecx
; Выделить память
invoke HeapAlloc,[_hheap],HEAP_ZERO_MEMORY,ecx
pop ecx
mov edi,eax
mov esi,[_tmp]
mov eax,[CmdStr]
mov [eax+localcmd.lpArgStr],edi ; lpArgStr
; Перенести строку параметров
rep movsb
; Прибраться за собой
invoke HeapFree,[_hheap],0,[_tmp]
mov eax,[CmdStr]
mov ebx,[_nCount]
mov [eax+localcmd.nCount],ebx ; nCount
; Размер = _nCount * 4
mov eax,[_nCount]
shl eax,2
; Выделить память
invoke HeapAlloc,[_hheap],HEAP_ZERO_MEMORY,eax
mov edi,eax
mov eax,[CmdStr]
mov [eax+localcmd.lpArgs],edi ; lpArgs
; Заполнить таблицу индексов
mov esi,[eax+localcmd.lpArgStr]
.parse_fill_addr:
invoke lstrlen,esi
or eax,eax
jz .parse_exit
mov [edi],esi
add edi,4
add esi,eax
inc esi
jmp .parse_fill_addr
.parse_exit:
popa
ret
endp
Параметры вызова функции: CmdStr - указатель на структуру CMDLINE, dQuotFlag - флаг, определяющий будут ли сохранены обрамляющие кавычки в массиве аргументов командной строки, значение флага: TRUE - сохранять кавычки, FALSE - убирать. После вызова функции получаем заполненную структуру CMDLINE. Пример использования:
section '.data' data readable writeable
...
struct CMDLINE
nCount dd ? ; Количество аргументов
lpArgs dd ? ; Указатель на массив адресов строк
lpArgStr dd ? ; Указатель на массив строк
ends
myarg CMDLINE ; Это наша командная строка
...
section '.code' code readable executable
...
stdcall ParseCmdLine,myarg,FALSE
; Если не заданы параметры в командной строке, то вывести
; информацию о ключах программы
cmp [myarg.nCount],2
jb show_usage
...
В прилагаемом исходнике функция ParseCmdLine вызывается дважды с разными флагами обработки кавычек, чтобы вы могли увидеть разницу. Для удобства в архиве есть файл demo.bat, запускающий пример сразу с несколькими параметрами командной строки.