CommandLineToArgvA その2

CommandLineToArgvAってないの??? の別解

メモです。

CommandLineToArgvWで得られたargv,argcをANSIに変換する方法と違い、GetCommandLine() APIで得られたコマンドライン文字列をパースします。要は二重引用符を考慮した、空白で区切られたトークンに分割し、↓のような感じのメモリーブロックを確保して、埋めていけばいいはず。

どっかネットで探せばコードは見つかると思うけど、面倒なので自分で書き書き。

/*
 CommandLineToArgv()
 エラーチェック・バッファチェックはしていない不完全版

 ex.)
  int argc = 0;
  LPTSTR* argv = CommandLineToArgv(GetCommandLine(),&argc);
  ・・・
  GlobalFree((HGLOBAL)argv);
*/

#include <windows.h>
#include <strsafe.h>
#include "CommandLineToArgv.h"

//プロトタイプ宣言
static int commandline_parser(PCTSTR pCommandLine,PTSTR *pIndex,size_t *pcchIndex,PTSTR pBlock,size_t *pcchBlock);

//トークンの区切文字
const TCHAR SPLIT = TEXT(' ');

//各種文字
const TCHAR ZERO = TEXT('&#092;&#048;');
const TCHAR BACKSLASH = TEXT('\\');
const TCHAR QUOTES = TEXT('\"');

//コマンドラインの文字数制限
const size_t COMMANDLINE_LENGTH = 1024;

//コマンドライン・トークンの最大数
const size_t INDEX_LENGTH = 256;

TCHAR** __stdcall CommandLineToArgv(PCTSTR pCommandLine,int *pArgc)
{
  TCHAR** ppArgv = NULL;
  size_t cchArgv = 0;
  PTSTR pIndex[INDEX_LENGTH] = {NULL};
  PTSTR pBlock = NULL;
  size_t cchCommandLine = 0;
  size_t cchBlock = 0;
  size_t nIndex = sizeof(pIndex);

  if(pCommandLine == NULL)
    {
      *pArgc = 0;
      return NULL;
    }

  //バッファを用意
  StringCchLength(pCommandLine,COMMANDLINE_LENGTH,&cchCommandLine);
  cchBlock = cchCommandLine + 1;
  pBlock = reinterpret_cast<PTSTR>(new BYTE[sizeof(TCHAR)*cchBlock]);

  //
  //コマンドライン解析(cchIndex,cchBlockには正確なサイズが返る)
  //
  commandline_parser(pCommandLine,pIndex,&nIndex,pBlock,&cchBlock);
  //
  //
  
  //正確なバッファブロックのサイズ
  SIZE_T cbSize = sizeof(PTSTR) * nIndex + sizeof(TCHAR) * cchBlock;
  
  //pBlockとpIndex から argv を構築する。
  ppArgv = (TCHAR**)GlobalAlloc(GPTR,cbSize);

  PTSTR *array = ppArgv;
  PTSTR next = (PTSTR)((PBYTE)ppArgv + sizeof(PTSTR)*nIndex);

  CopyMemory((PVOID)next,(const PVOID)pBlock,cchBlock*sizeof(TCHAR));

  for(int i=0;i<nIndex;i++)
    {
      ppArgv[i] = next;
      
      size_t cchToken = 0;
      StringCchLength(pIndex[i],cchBlock,&cchToken);
      
      next += (cchToken + 1);
    }
  
  if(pBlock)
    delete [] pBlock;

  *pArgc = nIndex;

  return ppArgv;
}

/******************************************************************************
  二重引用符を考慮したトークン分割
  ---------------------------------------------------------------------------
  pCommandLine : [in    ]解析対象の文字列
  pIndex       : [in,out]ポインタ配列
  pcchIndex    : [in,out]配列のサイズ
  pBlock       : [in,out]分割したトークンを格納するメモリブロック
  pcchBlock    : [in,out]メモリブロックの文字列サイズ
*****************************************************************************/
static int commandline_parser(PCTSTR pCommandLine,PTSTR *pIndex,size_t *pnIndex,PTSTR pBlock,size_t *pcchBlock)
{
  PCTSTR pSrc = pCommandLine;
  PTSTR pDest = NULL;

  int argc = 0;
  
  //バッファサイズをカウント
  int size = 0;
  
  //文字列スキャン中の二重引用符フラグ
  bool bQuote = false;

  //トークン終了フラグ
  bool bTokenEndFlag = false;
  
  size_t len = 0;
  StringCchLength(pSrc,COMMANDLINE_LENGTH,&len);
  if(pSrc == NULL || (len > *pcchBlock - 1))
    return 0;
  
  //先頭の区切りをスキップ
  while(*pSrc == SPLIT) pSrc++;

  //初期化
  pDest = pBlock;
  bTokenEndFlag = true;

  //コマンド文字列パース・スタート
  while(*pSrc != ZERO && argc < *pnIndex)
    {
      switch(*pSrc)
        {
        case QUOTES:
          bQuote = !bQuote;
          pSrc++;
          break;

        case SPLIT:
          if(!bQuote)
            {
              if(bTokenEndFlag)
                {
                  while(*pSrc == SPLIT)
                    pSrc++;
                }
              else
                {
                  //文字列終末端をバッファにコピー
                  *pDest++ = ZERO;
                  size++;             
                  pSrc++;
                }

              //トークン終了フラグを立てる
              bTokenEndFlag = true;
              break;
            }

        case BACKSLASH:
          if(*pSrc == BACKSLASH)
            {
              switch(*(pSrc+1))
                {
                case QUOTES:
                case SPLIT:
                  pSrc++;
                }
            }

        default:
          if(bTokenEndFlag)
            { 
              //トークン開始位置をインデックスに保存
              pIndex[argc++] = pDest;
              
              //トークン終了フラグをクリア
              bTokenEndFlag = false;
            }
          
          //文字をコピー
          *pDest++ = *pSrc++;
          size++;
        }
    }
  
  //文字列末端をヌル文字で埋める
  if(*(pDest-1) != ZERO)
    {
      *pDest++ = ZERO;
      size++;
    }

  //バッファブロックの末端をヌル文字で閉じる
  *pDest = ZERO;
  size++;

  *pnIndex = argc;
  *pcchBlock = size;

  return argc;
}