web-dev-qa-db-fra.com

Autre moyen d'obtenir argc et argv d'un processus

Je cherche des moyens alternatifs pour obtenir les paramètres de ligne de commande argc et argv fournis à un processus sans avoir un accès direct aux variables passées dans main().

Je veux créer une classe indépendante de main() afin que argc et argv n'aient pas à être passés explicitement au code qui les utilise.

EDIT: Certaines clarifications semblent être de mise. J'ai cette classe.

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)
{
}

Mais je voudrais un constructeur par défaut Application::Application(), avec du code C (très probablement), qui tire argc et argv de quelque part.

21
user1095108

Sous Linux, vous pouvez obtenir ces informations à partir du système de fichiers proc du processus, à savoir /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

Les arguments de main sont définis par le runtime C et la seule façon standard/portable d'obtenir les arguments de la ligne de commande. Ne combattez pas le système. :)

Si tout ce que vous voulez faire est de donner accès aux paramètres de ligne de commande dans d'autres parties du programme avec votre propre API, il existe plusieurs façons de le faire. Initialisez simplement votre classe personnalisée en utilisant argv/argc dans main et à partir de là, vous pouvez les ignorer et utiliser votre propre API. Le motif singleton est idéal pour ce genre de chose.

Pour illustrer, l'un des frameworks C++ les plus populaires, Qt utilise ce mécanisme:

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

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

    return app.exec();
}

Les arguments sont capturés par l'application et copiés dans un QStringList. Voir QCoreApplication :: arguments () pour plus de détails.

De même, Cocoa sur Mac a une fonction spéciale qui capture les arguments de la ligne de commande et les met à disposition du framework:

#import <Cocoa/Cocoa.h>

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

Les arguments sont ensuite disponibles n'importe où dans l'application à l'aide de la propriété NSProcessInfo.arguments .

Je remarque dans votre question mise à jour que votre classe stocke directement une copie de argc/argv textuellement dans son instance:

int const argc_;
char const** const argv_;

Bien que cela doive être sûr (la durée de vie des pointeurs argv doit être valide pour toute la durée de vie du processus), ce n'est pas très similaire à C++. Pensez à créer un vecteur de chaînes (std::vector<std::string>) en tant que conteneur et copiez les chaînes. Ensuite, elles peuvent même être mutables en toute sécurité (si vous le souhaitez!).

Je veux créer une classe indépendante de main () afin que argc et argv n'aient pas à être passés explicitement au code qui les utilise.

Il n'est pas clair pourquoi transmettre ces informations à partir de main est en quelque sorte une mauvaise chose à éviter. C'est exactement ainsi que les principaux cadres procèdent.

Je vous suggère d'utiliser un singleton pour vous assurer qu'il n'y a qu'une seule instance de votre classe Application. Les arguments peuvent être transmis via main mais aucun autre code n'a besoin de savoir ou de prendre soin de savoir d'où ils viennent.

Et si vous voulez vraiment cacher le fait que les arguments de main sont passés à votre constructeur Application, vous pouvez cachez-les avec une macro.

29
gavinb

Pour répondre en partie à la question, concernant Windows, la ligne de commande peut être obtenue comme le retour de la fonction GetCommandLine, qui est documentée ici , sans accès explicite aux arguments du main fonction.

15
Codor

Je suis totalement d'accord avec @gavinb et d'autres. Vous devriez vraiment utiliser les arguments de main et les stocker ou les passer où vous en avez besoin. C'est le seul moyen portable.

Cependant, à des fins éducatives uniquement, les éléments suivants fonctionnent pour moi avec clang sous OS X et gcc sous Linux:

#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;
}

qui produira:

$ 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'

La raison en est que la fonction stuff est marquée comme __attribute__((constructor)) qui l'exécutera lorsque la bibliothèque actuelle est chargée par l'éditeur de liens dynamique. Cela signifie que dans le programme principal, il s'exécutera même avant main et aura un environnement similaire. Par conséquent, vous pouvez obtenir les arguments.

Mais permettez-moi de répéter: c'est uniquement à des fins éducatives et ne doit pas être utilisé dans un code de production. Il ne sera pas portable et pourrait se casser à tout moment sans avertissement.

13
Johannes Weiss

Sous Windows, si vous avez besoin d'obtenir les arguments sous la forme wchar_t *, Vous pouvez utiliser 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;
}

C'est très pratique lorsque vous utilisez MinGW car gcc ne reconnaît pas int _wmain(int, wchar_t *) comme un prototype main valide.

5
jdarthenay

La transmission de valeurs ne constitue pas la création d'une dépendance. Votre classe ne se soucie pas d'où viennent ces valeurs argc ou argv - elle veut juste qu'elles soient passées. Vous voudrez peut-être copier les valeurs quelque part, cependant - il n'y a aucune garantie qu'elles ne sont pas modifiées (la même chose s'applique aux méthodes alternatives comme GetCommandLine).

Au contraire, en fait - vous créez une dépendance cachée lorsque vous utilisez quelque chose comme GetCommandLine. Du coup, au lieu d'une simple sémantique de "passer une valeur", vous avez "magiquement prendre leurs entrées d'ailleurs" - combiné avec ce qui précède "les valeurs peuvent changer à tout moment", cela rend votre code beaucoup plus fragile, sans oublier impossible à tester. Et l'analyse des arguments de ligne de commande est certainement l'un des cas où les tests automatisés sont très bénéfiques. C'est une variable globale vs une approche d'argument de méthode, si vous voulez.

5
Luaan

En C/C++, si main() ne les exporte pas, il n'y a pas de moyen direct d'y accéder; cependant, cela ne signifie pas qu'il n'y a pas de moyen indirect. De nombreux systèmes de type Posix utilisent le format elf qui passe argc, argv et envp sur la pile afin d'être initialisé par _start() et passé dans main() via la convention d'appel normale. Cela se fait généralement dans Assembly (car il n'y a toujours pas de moyen portable pour obtenir le pointeur de pile) et placé dans un "fichier de démarrage", généralement avec une certaine variation du nom crt.o.

Si vous n'avez pas accès à main() pour pouvoir simplement exporter les symboles, vous n'aurez probablement pas accès à _start(). Alors pourquoi, est-ce que je le mentionne même? À cause de ce 3ème paramètre envp. Puisque environ est une variable standard exportée qui ne est définie pendant _start() en utilisant envp. Sur de nombreux systèmes ELF, si vous prenez l'adresse de base de environ et la marchez en arrière en utilisant des indices de tableau négatifs, vous pouvez déduire les paramètres argc et argv. Le premier doit être NULL suivi du dernier paramètre argv jusqu'à ce que vous arriviez au premier. Lorsque la valeur pointée exprimée en long est égale au négatif de votre index négatif, vous avez argc et le suivant (un de plus que votre index négatif) est argv/argv [0].

3
technosaurus

Il existe peu de scénarios courants avec des fonctions nécessitant des arguments de type int argc, char *argv[] Connus de moi. Un tel exemple évident est GLUT, où sa fonction d'initialisation reprend ces arguments de main(), qui est une sorte de scénario "principal imbriqué". Cela peut ou non être votre comportement souhaité. Sinon, comme il n'y a pas de convention pour nommer ces arguments, tant que votre fonction a son analyseur d'arguments, et que vous savez ce que vous faites, vous pouvez faire tout ce dont vous avez besoin, codé en dur:

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

ou lu à partir de l'entrée de l'utilisateur ou généré autrement, AFAIK.

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

S'il vous plaît, voyez ceci SO post .

2
user3078414

Le moyen le plus portable serait d'utiliser une variable globale pour stocker les paramètres. Vous pouvez rendre cela moins laid en utilisant un Singleton (comme votre classe dans la question, mais un singleton initialisé par main) ou un Service Locator Similaire qui est fondamentalement le même: créez un objet dans main, passez et stockez les paramètres statiquement, et demandez à une autre ou à la même classe d'y accéder.

Les méthodes non portables utilisent GetCommandLine dans Windows, accèdent à /proc/<pid>/cmdline Ou (/proc/self/cmdline), Ou utilisent des extensions spécifiques au compilateur comme __attribute__((constructor))

Notez que l'obtention de la ligne de commande en fonction via un équivalent de GetCommandLinen'est pas possible (TLDR: la ligne de commande n'est pas transmise au noyau, mais déjà analysée et divisée par le processus d'appel (par exemple Coquille))

1
Flamefire

Cela ressemble à ce que vous voulez est une variable globale; ce que vous devez faire, c'est simplement passer argc et argv comme paramètres.

1