CAD日記

主にAutoCADのことについて書いているけど、近頃は投資系ネタに注力している。自動売買、仮想通貨、PC関係、プログラミングなど。@caddiary

ソフト開発

Windowsアプリで引数を受け取る(改)

投稿日:2021年6月13日 更新日:

MFCを使ったWindowsアプリの実装に関する話。
CWinAppを継承したクラスでは、m_lpCmdLineで起動時のパラメータを取得できる。そのパラメータは1個の文字列だが、半角スペースで区切られていて、半角スペースが内部に混入している場合はダブルコーテーションで括られるという特性がある。

シンプルな例で言うと、デスクトップにあるショートカットアイコンにファイルをドラッグ&ドロップした場合が該当し、そのファイルが半角スペース入りで複数あったら、けっこうメンドウなパラメータ解析が必要になるってこと。

例えばこんな単純な形であれば、半角スペースで区切ればいいだけなので余裕。
D:\work\test1.txt D:\work\test2.txt D:\work\test3.txt
しかし、こんな場合がやっかいこの上ない。
D:\work\test1.txt “D:\work\test1 – コピー (2).txt” “D:\work\test1 – コピー.txt”

ググればすぐに出てきて使えるだろうと考えて、以下記事のものをパクったのが間違いだった。
[MFC] Windowsアプリで引数を受け取る
上記のように、ファイル名に半角スペースが入っている場合にちゃんと分割してくれなかった。
そもそも、何でTrimを2回もやってんのか、最後に空の文字列が追加されてしまうので取り除くなんて書きっぷりが気に入らなかった。そのあたりはまぁ実害がないからいいとしても、ちゃんと分割されないことがあるってのはダメですな。
ということで、以下ちゃんと分割されるようにしたコードを掲載しておく。

void CCmdParamApp::IsCommand(CStringArray& ary)
{
	// コマンドライン引数の取得
	CString cmdParam(m_lpCmdLine);
	cmdParam.Trim();
	if (cmdParam.IsEmpty())
		return;

	// パラメータの分解
	CString param;
	int curPos = 0;
	do {
		if (cmdParam.GetLength() >= curPos && cmdParam.GetAt(curPos) == '\"') {
			// "で括われた引数
			++curPos;
			param = cmdParam.Tokenize(_T("\""), curPos);
			if (cmdParam.GetAt(curPos) == ' ')  // "で閉じた後に半角スペースがあるから次へ進む
				++curPos;
		}
		else {
			// 引数を半角スペースで分解
			param = cmdParam.Tokenize(_T(" "), curPos);
		}
		ary.Add(param.Trim());
	} while (param != "");

	// 最後に空の文字列が追加されてしまうので取り除く
	ary.RemoveAt(ary.GetCount()-1);
}

最初のダブルコーテーションで括られた文字は問題ないが、2つ目で問題が発生する。1つ目の終わりのダブルコーテーションの次にスペースが来た場合(ある意味当然)に、2つ目の始まりのダブルコーテーションを識別できない。その結果、ダブルコーテーション中の半角スペースを区切り文字として認識してしまってアウト。
上記のように、終わりのダブルコーテーションの次が半角スペースだったら、半角スペースを読み飛ばして次に進めばよい。
扱う文字がファイル名やフォルダ名だったら、この処理で間違いないだろう。ダブルコーテーションが2つ続いたら、ダブルコーテーションそのものになるとか、エスケープ処理全般のことを考えたらもっとちゃんと考えなきゃいかんな。

そもそもTokenizeというCStringのメンバー関数が話をわかりにくくしている。MSの説明によると「ターゲット文字列内の次のトークンを検索します」ということで、まぁよく読めばわからないこともないが、こいつを組み込んだソースコードは判読性が低くなる。

ということで、以下はおいらが一から書いたパラメータ解析コード。文字を一文字ずつ見ていって、ダブルコーテーションか半角スペースかその他かで判別しているからわかりやすいでしょ。

void CCmdParamApp::IsCommand(CStringArray& ary)
{
	// コマンドライン引数の取得
	CString cmd(m_lpCmdLine);
	cmd.Trim();
	if (cmd.IsEmpty())
		return;

	CString param;
	bool flg = false;  // ダブルコーテーション内かどうか
	for (int i = 0; i < cmd.GetLength(); i++)
	{
		TCHAR c = cmd.GetAt(i);
		if (c == '\"')
			flg = !flg;
		else if (c == ' ')
		{
			if (flg)  // ダブルコーテーション中なので、スペースをセパレータとして認識しない
				param += c;
			else  // ダブルコーテーション外なので、スペースをセパレータとして認識する
			{
				if (!param.IsEmpty())  // 文字があるときだけ追加
					ary.Add(param);
				param.Empty();
			}
		}
		else
			param += c;
	}
	if (!param.IsEmpty())  // 最後の文字を追加
		ary.Add(param);
}

なお、文字セットはUnicode使用のものである。イマドキ、文字セットにマルチバイトを使っているものはそうそうないと思うけど、もしそんな化石のようなコードがあったなら、日本語の1バイト目の判定を入れないとダメだからね。

-ソフト開発

執筆者:


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

関連記事

no image

テキストファイルの最大行数

【2018/12/24追記】以下の記事も参考になるかな。 扱えるテキストファイルの最大行数は1億行 バカでかいテキストファイルを作る必要があって、結果約2GBにもなった。 CADから出力するんだけど、 …

PDFアレコレで捺印時の印影サイズを変更できるようにした

PDFアレコレをバージョンアップ。約4か月ぶり。 Ver3.04 2021/2/14 ・捺印時の印影サイズを指定できるようにした。指定できるサイズは6mm, 9mm, 10.5mm, 12mm, 13 …

PDFに捺印できるようにしたPDFアレコレを公開

半年ぶりにPDFアレコレをバージョンアップ。変更点は以下の通り。 Ver2.01 2020/3/15 ・プレビューからの捺印機能に対応。捺印用データはとりあえずpdfのみで配置点が固定。  ※そのうち …

no image

家で仕事

そもそも家でパソコンを買ったのは、仕事をするためだ。 う~ん、仕事とも言えるけど仕事とも言えない感じがする。 ドスパラのPrime、22万円なり。 結構な投資だったけど、後悔はしてない。 今、そのNe …

no image

PDF印刷のアプリ指定

PCにAdobe ReaderとAcrobatの両方が入っている場合、PDFファイルを 右クリックメニューから印刷ってやると、どっちで印刷されるのか? Windows XPまでは、拡張子の関連付けのと …