web-dev-qa-db-ja.com

プロセスのargcとargvを取得する別の方法

main()に渡された変数に直接アクセスせずに、プロセスに提供されたコマンドラインパラメーターargcおよびargvを取得する別の方法を探しています。

argcargvを使用するコードに明示的に渡す必要がないように、main()から独立したクラスを作成したいと思います。

編集:いくつかの明確化は整然としているようです。このクラスがあります。

_class Application
{
  int const argc_;
  char const** const argv_;

public:
  explicit Application(int, char const*[]);
};

Application::Application(int const argc, char const* argv[]) :
  argc_(argc),
  argv_(argv)
{
}
_

しかし、どこかからargcargvをプルするデフォルトのコンストラクターApplication::Application()と、いくつかの(おそらく)Cコードが必要です。

21
user1095108

Linuxでは、この情報はプロセスのprocファイルシステム、つまり/proc/$$/cmdlineから取得できます。

int pid = getpid();
char fname[PATH_MAX];
char cmdline[ARG_MAX];
snprintf(fname, sizeof fname, "/proc/%d/cmdline", pid);
FILE *fp = fopen(fname);
fgets(cmdline, sizeof cmdline, fp);
// the arguments are in cmdline
34
fluter

mainへの引数は、Cランタイムによって定義され、コマンドライン引数を取得するための唯一の標準/ポータブルな方法です。システムと戦わないでください。 :)

自分のAPIを使用して、プログラムの他の部分のコマンドラインパラメータへのアクセスを提供することだけが目的の場合は、多くの方法があります。 mainargv/argcを使用してカスタムクラスを初期化するだけで、それ以降はそれらを無視して独自のAPIを使用できます。シングルトンパターンは、この種のものに最適です。

説明するために、最も人気のあるC++フレームワークの1つであるQtは、このメカニズムを使用します。

int main(int argc, char* argv[])
{
    QCoreApplication app(argc, argv);

    std::cout << app.arguments().at(0) << std::endl;

    return app.exec();
}

引数はアプリによってキャプチャされ、QStringListにコピーされます。詳細は QCoreApplication :: arguments() を参照してください。

同様に、Mac上のCocoaには、コマンドライン引数をキャプチャしてフレームワークで使用できるようにする特別な関数があります。

#import <Cocoa/Cocoa.h>

int main(int argc, char *argv[])
{
    return NSApplicationMain(argc, (const char **)argv);
}

その後、引数は NSProcessInfo.arguments プロパティを使用してアプリのどこからでも利用できます。

更新された質問で、クラスにargc/argvのコピーがインスタンスに直接格納されていることに気づきました。

int const argc_;
char const** const argv_;

これは安全であるはずですが(argvポインターの存続期間は、プロセスの存続期間全体で有効でなければなりません)、C++によく似ているわけではありません。文字列のベクトル(std::vector<std::string>)をコンテナとして作成し、文字列をコピーすることを検討してください。そうすれば、(必要に応じて)安全に変更することもできます。

Main()から独立したクラスを作成して、argcとargvを、それらを使用するコードに明示的に渡す必要がないようにします。

この情報をmainから渡すことが、どういうわけか避けられるべき悪いことなのかは明らかではありません。これは、主要なフレームワークが行う方法です。

シングルトンを使用して、Applicationクラスのインスタンスが1つだけであることを確認することをお勧めします。引数はmainを介して渡すことができますが、他のコードでは、これがどこから来たかを知る必要も、気にする必要もありません。

また、reallymainの引数がApplicationコンストラクタに渡されているという事実を隠したい場合は、マクロで非表示にします。

29
gavinb

Windowsに関する部分的な質問に答えるために、コマンドラインは、明示的にアクセスせずに、 ここに記載されているGetCommandLine関数の戻りとして取得できますmain関数の引数に。

15
Codor

@gavinbと完全に同意します。 mainからの引数を実際に使用して保存するか、必要な場所に渡す必要があります。それが唯一の移植可能な方法です。

ただし、教育目的のみで、以下はOS Xではclang、Linuxではgccで動作します。

_#include <stdio.h>

__attribute__((constructor)) void stuff(int argc, char **argv)
{
    for (int i=0; i<argc; i++) {
        printf("%s: argv[%d] = '%s'\n", __FUNCTION__, i, argv[i]);
    }
}

int main(int argc, char **argv)
{
    for (int i=0; i<argc; i++) {
        printf("%s: argv[%d] = '%s'\n", __FUNCTION__, i, argv[i]);
    }
    return 0;
}
_

出力されます:

_$ gcc -std=c99 -o test test.c && ./test this will also get you the arguments
stuff: argv[0] = './test'
stuff: argv[1] = 'this'
stuff: argv[2] = 'will'
stuff: argv[3] = 'also'
stuff: argv[4] = 'get'
stuff: argv[5] = 'you'
stuff: argv[6] = 'the'
stuff: argv[7] = 'arguments'
main: argv[0] = './test'
main: argv[1] = 'this'
main: argv[2] = 'will'
main: argv[3] = 'also'
main: argv[4] = 'get'
main: argv[5] = 'you'
main: argv[6] = 'the'
main: argv[7] = 'arguments'
_

その理由は、stuff関数が__attribute__((constructor))としてマークされているためです。これにより、ダイナミックリンカーによって現在のライブラリが読み込まれたときに実行されます。つまり、メインプログラムではmainの前でも実行され、同様の環境になります。したがって、引数を取得できます。

繰り返しますが、これは教育目的のみであり、本番用コードでは使用しないでください。ポータブルではなく、いつの時点でも警告なしに壊れる可能性があります。

13
Johannes Weiss

Windowsでは、引数を_wchar_t *_として取得する必要がある場合は、CommandLineToArgvW()を使用できます。

_int main()
{
    LPWSTR *sz_arglist;
    int n_args;
    int result;
    sz_arglist = CommandLineToArgvW(GetCommandLineW(), &n_args);
    if (sz_arglist == NULL)
    {
        fprintf(stderr, _("CommandLineToArgvW() failed.\n"));
        return 1;
    }
    else
    {
        result = wmain(n_args, sz_arglist);
    }
    LocalFree(sz_arglist);
    return result;
}
_

gccint _wmain(int, wchar_t *)を有効なmainプロトタイプとして認識しないため、MinGWを使用する場合に非常に便利です。

5
jdarthenay

値を渡しても、依存関係が作成されるわけではありません。クラスは、それらのargcまたはargvの値がどこから出てくるかを気にしません-渡されたいだけです。ただし、値をどこかにコピーすることをお勧めします。値が変更されないという保証はありません(GetCommandLineなどの代替メソッドにも同じことが当てはまります)。

実際、まったく逆です。GetCommandLineのようなものを使用すると、非表示の依存関係が作成されます。突然、単純な「値を渡す」セマンティクスの代わりに、「魔法のように他の場所から入力を取得する」-前述の「値はいつでも変更できる」と組み合わせると、これにより、コードはさらに脆弱になります。テストすることは不可能です。また、コマンドライン引数の解析は、自動テストが非常に有益な場合の1つです。もしそうなら、それはグローバル変数対メソッド引数アプローチです。

5
Luaan

C/C++では、main()がそれらをエクスポートしない場合、それらに直接アクセスする方法はありません。ただし、これは間接的な方法がないという意味ではありません。多くのPosixライクなシステムは、argcargvenvp_start()で初期化し、 main()通常の呼び出し規約による。これは通常、アセンブリで行われ(スタックポインタを取得するポータブルな方法がまだないため)、「スタートファイル」に入れられ、通常はcrt.oという名前のバリエーションがいくつかあります。

シンボルをエクスポートできるようにmain()にアクセスできない場合は、_start()にアクセスできない可能性があります。では、なぜそれについて言及するのでしょうか。そのため、3番目のパラメーターはenvpです。 environは標準のエクスポートされた変数なので、doesはenv [を使用して_start()中に設定されます。多くのELFシステムでは、environのベースアドレスを取得し、負の配列インデックスを使用して逆方向に歩くと、argcおよびargvパラメーターを推定できます。最初のものに到達するまで、最初のものはNULLで、その後に最後のargvパラメータが続く必要があります。 longにキャストされたポインティングされた値が負のインデックスの負の値に等しい場合、argcがあり、次の(負のインデックスより1つ多い)はargv/argv [0]です。

3
technosaurus

__int argc, char *argv[]_型の引数を必要とする関数の一般的なシナリオはいくつか知られています。そのような明白な例の1つはGLUTです。その初期化関数は、これらの引数をmain()から引き継ぎます。これは、「ネストされたメイン」シナリオの一種です。これは望ましい動作である場合とそうでない場合があります。そうでない場合は、関数に引数パーサーがあり、実行していることがわかっている限り、これらの引数に名前を付けるための規則がないため、必要なことをハードコードして実行できます。

_int foo = 1;
char * bar[1] = {" "};
_

またはユーザー入力から読み取るか、別の方法で生成された、AFAIK。

_int myFunc( int foo, char *bar[]){
//argument parser
 {…   …}
return 0;
}
_

こちらをご覧ください SO post

2
user3078414

最もポータブルな方法は、パラメーターを格納するためにグローバル変数を使用することです。 Singleton(問題のクラスのようですが、メインによって初期化されたシングルトン)または同様の_Service Locator_を使用して、これをより見苦しくないようにすることができます。 、静的にパラメータを渡して保存し、別のまたは同じクラスがそれらにアクセスするようにします。

移植性のない方法は、Windowsで GetCommandLine を使用するか、_/proc/<pid>/cmdline_または(_/proc/self/cmdline_)にアクセスするか、__attribute__((constructor))などのコンパイラ固有の拡張機能を使用します

GetCommandLineと同等の機能でコマンドラインを取得することに注意してください 不可能 (TLDR:コマンドラインはカーネルに渡されませんが、呼び出しプロセスによってすでに解析および分割されています(たとえば、シェル))

1
Flamefire

必要なのはグローバル変数のようです。あなたがしなければならないことは、単に引数としてargcとargvを渡すことです。

1