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 を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

関連記事

PDFアレコレはiTextSharp7も組み込むことにする

PDFiumViewerを使ってPDFアレコレという名のフリーソフトを作ってみたわけだが、PDFiumViewerの限界が見えてきた。

no image

クラッカー

知人が被害を受けました。以下、知人の経験談です。 便利なソフトを作って一儲けしたい! シェアウェアとして公開して、誰でもダウンロードできるようにすることを考えました。まずは使ってもらわないと、その良さ …

Teigha改めODAでDWGをDXFに変換するプログラムをつくってみよう!【Part.2】

Part.1ではあれこれと前提の話を書いたわけだが、今回は実際にVCのプロジェクトを作る準備をしてみよう。 1.ODAモジュールのダウンロード 2019 Update 2のKernelとDrawing …

Teigha改めODAでDWGをDXFに変換するプログラムをつくってみよう!【Part.3】

Part.2の続編。今回はODAプロジェクトがどんな構造になっているのかを、逐一つまびらかにこんこんと説明してみる。 【1.MFCプロジェクトの作成】 ファイル⇒新規作成⇒プロジェクトで、MFCアプリ …

no image

マルチスレッド

ついに出来ました。 マルチスレッド技術を利用した画期的機能が。 って、そんな大したものではありません。 スレッド化技術は、この時代当たり前になりつつありますが、 なかなか難しそうで、手をつけてませんで …