CommandLineToArgvAってないの???

ちょっとした小さなツールをC++で組むとき、CRTは使わないときはできるだけCRTをリンクしないようにしたいわけです。

でも、エントリポイントに WinMainとかしてしまうと、コマンドライン引数をうまくハンドリングできなくて悩む。#define UNICODE とかして、UNICODEにすると、CommandLineToArgvWというAPIがあるので、簡単に、argc(int) と argv(char**)がとれてラクができますが・・・。

なぜか・・・ANSIバージョンの CommandLineToArgvA がないのはなんでなんでしょうかねぇ・・・。
というわけで、解決方法は3つ。

  1. そもそもANSI文字列を使わない。
  2. 自分でコマンドライン文字列(GetCommandLine API)パーサーを書く
  3. CommandLineToArgvWで得られた引数リストをANSI文字列に変換する

一番ラクそうなのは3かな・・・ということで、やっつけで書いてみましたが・・・これでいいのかな・・・(^^;;;

#define GLOBALALLOC(S) reinterpret_cast(GlobalAlloc(GPTR,(S)))
#define GLOBALFREE(X) {if(X){GlobalFree(reinterpret_cast(X));(X)=NULL;}}

#define HEAPALLOC(S) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(S))
#define HEAPFREE(X) {if(X){HeapFree(GetProcessHeap(),0,reinterpret_cast(X));(X)=NULL;}}

PSTR *CommandLineToArgvA(LPCSTR lpCmdLine,int *pNumArgs)
{
  PSTR *pArgvA = NULL;
  int Argc = 0;

  PWSTR *pArgvW = NULL;
  PWSTR pM2WBuffer = NULL;
  int *pcchLen = NULL;
  int cchBufferLength = 0;

  *pNumArgs = 0;

  if((cchBufferLength = MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,lpCmdLine,-1,NULL,0))     goto cleanup;

  if((pM2WBuffer = (PWSTR)HEAPALLOC((cchBufferLength+1)*sizeof(WCHAR))) == NULL)
    goto cleanup;

  MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,lpCmdLine,-1,pM2WBuffer,cchBufferLength+1);
  pArgvW = CommandLineToArgvW(pM2WBuffer,&Argc);

  //コマンドラインをマルチバイトに変換した後の
  //文字数(配列pcchLen)、文字数合計(cchBufferLength)を求める
  if((pcchLen = (int*)HEAPALLOC(sizeof(int)*Argc)) == NULL)
    goto cleanup;

  cchBufferLength = 0;
  for(int i=0;i    {
      pcchLen[i] = WideCharToMultiByte(CP_ACP,0,pArgvW[i],-1,NULL,0,NULL,NULL);
      cchBufferLength += ++pcchLen[i];
    }

  //コマンドライン文字列の配列を取得して格納する。
  if((pArgvA = (PSTR*)GLOBALALLOC(cchBufferLength + sizeof(PSTR)*Argc)) == NULL)
    goto cleanup;

  for(int i=0;i    {
      pArgvA[i] = (i < 1) ? (PSTR)pArgvA + sizeof(PSTR)*Argc : pArgvA[i-1] + pcchLen[i-1];
      WideCharToMultiByte(CP_ACP,0,pArgvW[i],-1,pArgvA[i],pcchLen[i],NULL,NULL);
    }

cleanup:
  HEAPFREE(pcchLen);
  HEAPFREE(pM2WBuffer);
  GLOBALFREE(pArgvW);  // 解放処理を忘れていたので追加しました。(2010/7/4)
  *pNumArgs = Argc;
  return pArgvA;
}

こんなんでエエのかな・・・。。。


CommandLineToArgvA その2

CommandLineToArgvAってないの???」への4件のフィードバック

  1. たっちん のコメント:

    CommandLineToArgvA() が無いのは、
    __argc、__argv が定義されているからみたいです。

    MinGW でコンパイル/実行してみましたが、うまく取得できました。
    # その他の環境では未検証です。

    //gcc -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 -DUNICODE args.c
    #include 
    #include 
    
    int main()
    {
    	int i;
    	int argc = 0;
    	LPWSTR* argv;
    
    	argv = CommandLineToArgvW(GetCommandLineW(), &argc);
    
    	for ( i = 0; i < argc; i++ ) {
    		char str[256];
    		WideCharToMultiByte(CP_THREAD_ACP, 0, argv[i], -1, str, sizeof(str), NULL, NULL);
    		printf(""%s"rn", str);
    		printf(""%s"rn", __argv[i]);
    		printf("rn");
    	}//for
    
    	GlobalFree(argv);
    
    	return 0;
    }
    

    参考ページ: http://eternalwindows.jp/winbase/window/window06.html

  2. なかがわ のコメント:

    >たっちんさん

    コメント、ありがとうございました。

    ただ、__argc,__argvはCランタイムライブラリで内部的に定義されているグローバル変数だったかと思いますので、
    VisualC++ 2008ExpressでCランタイムライブラリを使わずにWin32APIだけでコードを書くときには使えないんですよね。。。小さなツールだとWin32 APIだけで書いて、以下のようなコードをくっつけてビルドしてます。

    //使用するライブラリの指定
    #pragma comment(lib,"kernel32.lib")
    #pragma comment(lib,"shell32.lib")
    #pragma comment(lib,"user32.lib")
    
    //サブシステムの指定
    #pragma comment(linker,"/SUBSYSTEM:console")
    
    //デフォルトライブラリを使用しない
    #pragma comment(linker, "/nodefaultlib:"libcmt.lib"")
    
    //main関数を書くとCランタイムライブラリが強制されてコンパイルされるので
    //隣家オプションでエントリポイントを指定
    #pragma comment(linker, "/entry:"mainCRTStartup"")
    

    C言語を使ってて、Cランタイムライブラリを使わない、ということ自体がイレギュラーですのでちょっと特殊用途なんですけどね(^^;

    わざわざ検証コードも書いてくれてありがとうざいました。
    参考ページのexternalwindows.jpは昔からよく参考にさせてもらってます(^^)

  3. たっちん のコメント:

    その後、いろいろ試してみた結果、
    > __argc,__argvはCランタイムライブラリで内部的に定義されているグローバル変数
    ということでした。

    標準Cライブラリを抜いてリンクしてみると、
    __argv のリンクエラーが発生しました。

    //gcc -mwindows -nostdlib winmini.c -luser32
    #include <windows.h>
    
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {
    	MessageBox(NULL, __argv[0], "minimum test", MB_OK);
    	//ERROR: undefined reference to `__p___argv'
    
    	return 0;
    }
    

    また、-lmsvcrt オプションを手動で追加してみましたが、
    実行時エラーが発生し失敗しました。

    ヘッダファイルをやさがししたところ、
    残念なことに stdlib.h にしっかり書いてありました。

    //stdlib.h
    
    extern int*  __cdecl __MINGW_NOTHROW   __p___argc(void);
    extern char*** __cdecl __MINGW_NOTHROW  __p___argv(void);
    extern wchar_t***  __cdecl __MINGW_NOTHROW __p___wargv(void);
    
    #define __argc (*__p___argc())
    #define __argv (*__p___argv())
    #define __wargv (*__p___wargv())
    

    ちなみに、Cライブラリを抜いてリンクすると、
    実行ファイルサイズが 12KB→6KB になるという、
    あまり役に立たない情報が得られました。

    # MinGW ばかりで、スミマセン。。
    # VC でも試そうかと思いましたが、体力がありませんでした。。

  4. なかがわ のコメント:

    >たっちんさん

    __argc,__argv は、main 関数がコールされる前に初期化されなければ使えないので、
    Cランタイムライブラリを使わず、ユーザー定義のエントリポイントを使用する場合には使えませんね。

    VisualC++でも、__argv,__argcは使えますが、確かマイクロソフトの公式ドキュメントには一切触れられていないと思うので使えないんですよね。以前はドキュメントにも載っていたんですが・・・なぜか消えた。
    だからこそ、Win32 APIとしてなぜ CommandLineToArgvA がない理由がわからないんですよねぇ~(^^;;;

    MinGWはCランタイムライブラリがあってもサイズが小さいですよね。。。
    VisualC++は、main関数が呼ばれる前に、なにやらいろいろ裏でやってるみたいでサイズがやたらバカでかいです(^^;;;

コメントを残す