Альтернативный способ получения argc и argv процесса

Я ищу альтернативные способы получить параметры командной строки argc и argv, предоставленные процессу, без прямого доступа к переменным, переданным в main().

Я хочу создать класс, независимый от main(), чтобы argc и argv не нужно было явно передавать коду, который их использует.

РЕДАКТИРОВАТЬ: Кажется, некоторые разъяснения в порядке. У меня есть этот класс.

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

Но мне нужен конструктор по умолчанию Application::Application() с каким-то (скорее всего) кодом C, который извлекает откуда-то argc и argv.


person user1095108    schedule 03.05.2016    source источник
comment
Что вы имеете в виду под получением? Откуда еще?   -  person user3078414    schedule 03.05.2016
comment
Не существует переносимого или стандартного способа получения аргументов для программы, кроме аргументов для main.   -  person Some programmer dude    schedule 03.05.2016
comment
@PaulR да, да, API-интерфейсы на C, как вы знаете, но класс на C++. Мне не нужен был ответ с питоном, хотя это было бы круто.   -  person user1095108    schedule 03.05.2016
comment
@JoachimPileborg Верно, но вопрос зависит от ОС, непереносимой.   -  person user1095108    schedule 03.05.2016
comment
Что касается пользователя, которому не нужно передавать argc и argv в класс, взгляните на почти все переносимые и независимые от платформы графические интерфейсы, все они требуют, чтобы пользователь фреймворка явно передал argc и argv в фреймворк. Так что это обычное дело, а не то, к чему программисты не привыкли.   -  person Some programmer dude    schedule 03.05.2016
comment
Вы помечаете этот вопрос linux, windows, posix и bsd и говорите, что этот вопрос зависит от ОС? Если бы это было специфично для ОС, вы бы упомянули только одну ОС, ту, на которую вы нацелены.   -  person Some programmer dude    schedule 03.05.2016
comment
@JoachimPileborg Фреймворки содержат код, специфичный для ОС, а не только переносимый код, нет никаких причин, почему бы не сделать сбор командной строки частью ОС. Я пытаюсь собрать как можно больше способов собрать командную строку, но не бесконечных и не основанных на мнениях.   -  person user1095108    schedule 03.05.2016
comment
проблема куриного яйца, если вам нужен код, который предоставляет аргумент, тогда этот код должен полагаться на main, прямо или косвенно у вас будет main.   -  person CoffeDeveloper    schedule 03.05.2016
comment
Мы должны перестать голосовать за неправильно сформулированные вопросы, прежде чем ответить на вопрос правильно. Вопрос мне до сих пор не ясен, вы в основном хотите получить эти параметры без необходимости их передачи? Вы можете создать оболочку, которая при внедрении предоставляет параметры косвенно. Я просто думаю, что как только требования ОП станут ясны, мы сможем просто доказать, возможно ли то, что он хочет.   -  person CoffeDeveloper    schedule 03.05.2016


Ответы (10)


В 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
person fluter    schedule 03.05.2016
comment
1. вы можете прочитать /proc/self/cmdline 2. аргументы могли быть уничтожены 3. очень странно иметь дело с файлами /proc с абстракцией FILE 4. ARG_MAX — это ограничение только для одного аргумента, а не для всех аргументов в целом 5 .отсутствующие проверки ошибок, возможно, можно было бы опустить в примере. Однако самая большая проблема заключается в том, что OP, вероятно, пытается сделать что-то не так, и такой ответ не следует публиковать в первую очередь без дополнительных разъяснений. - person employee of the month; 03.05.2016
comment
Я считаю, что /proc/*/cmdline усекается до некоторой константы максимальной длины. Это не ограничение размера командной строки процесса, которую может запустить ОС. Вместо этого это ограничение ведения записей списка процессов в Linux. Таким образом, вы можете запустить процесс с более длинным списком аргументов, но ядро ​​не будет запоминать их все, чтобы попытаться прочитать аргументы из списка процессов. - person Noah Spurrier; 03.05.2016
comment
Ядро не запоминает аргументы. Вместо этого он сохраняет адреса для начала и конца указанных аргументов, а затем считывает их из адресного пространства целевого процесса. Я также не вижу никакого кода, усекающего результат (если, конечно, запрошенная сумма не слишком мала). lxr.free-electrons.com/source/fs/proc/ base.c#L199 - person employee of the month; 03.05.2016
comment
Попытка использовать это, но только получение имени команды (с путем, если он был явно указан). Итак, cat /proc/self/cmdline x y z возвращает cat/proc/self/cmdlinexyz, но fgets в этом файле возвращает только cat. Почему это? - person Evgen; 20.03.2019
comment
(отвечая сам себе) - оказывается, что аргументы NULL-separated. Не могу просто использовать строку :( - person Evgen; 20.03.2019

Аргументы main определяются средой выполнения C и являются единственным стандартным/переносимым способом получения аргументов командной строки. Не боритесь с системой. :)

Если все, что вы хотите сделать, это предоставить доступ к параметрам командной строки в других частях программы с помощью вашего собственного API, есть много способов сделать это. Просто инициализируйте свой собственный класс, используя argv/argc в main, и с этого момента вы можете игнорировать их и использовать свой собственный API. Шаблон singleton отлично подходит для такого рода вещей.

Чтобы проиллюстрировать это, Qt, одна из самых популярных сред C++, использует этот механизм:

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

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

    return app.exec();
}

Аргументы перехватываются приложением и копируются в файл QStringList. Дополнительные сведения см. в разделе QCoreApplication::arguments().

Точно так же Cocoa на Mac имеет специальную функцию, которая захватывает аргументы командной строки и делает их доступными для фреймворка:

#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. Аргументы могут быть переданы через main, но никакому другому коду не нужно знать или заботиться о том, откуда они взялись.

И если вы действительно хотите скрыть тот факт, что аргументы main передаются вашему конструктору Application, вы можете скрыть их с помощью макроса.

person gavinb    schedule 03.05.2016
comment
Классы на С? Скажи, что это не так! - person sfdcfox; 03.05.2016
comment
Голосую за это. Типичный метод, который я вижу, используется для API, которым нужен доступ CL для анализа их собственных аргументов (например, QT), — это просто запросить argv и argc. Преимущество использования стандартной идиомы заключается в том, что опытные программисты с первого взгляда поймут, что вы делаете. - person T.E.D.; 03.05.2016
comment
Я понимаю идею использования глобального (это уродливо, но выполняет свою работу), однако ПОЧЕМУ обеспечение уникальности? Что плохого в том, чтобы разрешить кому-либо создавать фальшивый набор аргументов и вместо этого передавать их? - person Matthieu M.; 04.05.2016
comment
Это именно то, чего я не хочу. Может быть неправильно находить argc и argv с помощью альтернативных средств, но почему люди в любом случае должны отговаривать от этого? Помимо той, которую я описал, есть ситуации, когда это кажется полезным. - person user1095108; 04.05.2016
comment
@MatthieuM. Синглтон просто сохраняет ту же семантику, что и данные, которые он инкапсулирует; имеется один набор аргументов только для чтения. Не сильное требование. - person gavinb; 04.05.2016
comment
Со стратегической точки зрения многие пользователи SE считают любое использование синглтона запахом кода и будут отрицать хороший в остальном ответ только за его упоминание. Как человек, который проголосовал за этот ответ, я думаю, что это было бы позором. Если это не главное в вашем ответе, нет смысла предлагать что-то спорное. - person T.E.D.; 04.05.2016
comment
@ user1095108 Вы говорите, что не хотите получать доступ к аргументам стандартным способом, но не почему. Доступ к аргументам из другой части приложения возможен независимо от того, как они были получены изначально. Если вы не хотите использовать определенный механизм, вам нужно обойти код запуска среды выполнения C, который открывает целую банку червей. - person gavinb; 07.05.2016
comment
@gavinb Интересно, что в macOS NSApplicationMain фактически игнорирует переданные в argc и argv и получает их непосредственно от _NSGetArgc и _NSGetArgv. - person saagarjha; 10.03.2018
comment
@SaagarJha Интересно, да, я только что посмотрел имплантацию в opensource.apple.com/source/Libc/Libc-763.13/sys/ . Он опирается на очень конкретные и подробные знания о dyld и libSystem внутреннем устройстве. - person gavinb; 13.03.2018

Чтобы частично ответить на вопрос, касающийся Windows, командную строку можно получить как возврат функции GetCommandLine, которая задокументирована здесь, без явного доступа к аргументам функции main.

person Codor    schedule 03.05.2016
comment
Это популярный ответ. Вы можете рассмотреть возможность добавления примера использования GetCommandLine. - person Cory Klein; 03.05.2016
comment
Или просто с помощью __argc, __argv и __wargv. - person isanae; 03.05.2016

Я полностью согласен с @gavinb и другими. Вы действительно должны использовать аргументы из main и хранить их или передавать там, где они вам нужны. Это единственный портативный способ.

Однако только в образовательных целях у меня работает следующее с clang в OS X и gcc в 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;
}

который выведет:

$ 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 и иметь аналогичную среду. Таким образом, вы можете получить аргументы.

Но позвольте мне повторить: это только для образовательных целей и не должно использоваться ни в каком производственном коде. Он не будет портативным и может сломаться в любой момент без предупреждения.

person Johannes Weiss    schedule 03.05.2016
comment
Это довольно изящно. Гарантированно ли, что функции __attribute__((constructor)) имеют доступ к argv/argc, или это просто злоупотребляет какой-то случайностью представления? - person Patrick Collins; 04.05.2016
comment
Это вообще не гарантировано. - person Johannes Weiss; 04.05.2016
comment
glibc и dyld реализуют это расширение; мусл нет. Возьмите из этого что хотите. - person saagarjha; 11.11.2019

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

Это очень удобно при использовании MinGW, потому что gcc не распознает int _wmain(int, wchar_t *) как действительный прототип main.

person jdarthenay    schedule 03.05.2016

Передача значений не означает создание зависимости. Ваш класс не заботится о том, откуда берутся эти значения argc или argv — он просто хочет, чтобы они были переданы. Однако вы можете захотеть скопировать значения куда-нибудь — нет гарантии, что они не будут изменены (то же самое относится к альтернативным методам, таким как GetCommandLine).

Наоборот, вы создаете скрытую зависимость, когда используете что-то вроде GetCommandLine. Внезапно вместо простой семантики «передать значение» вы «волшебным образом берете свои входные данные откуда-то еще» — в сочетании с вышеупомянутым «значения могут измениться в любое время» это делает ваш код намного более хрупким, не говоря уже о невозможно проверить. И анализ аргументов командной строки, безусловно, является одним из случаев, когда автоматизированное тестирование весьма полезно. Если хотите, это глобальная переменная против подхода аргумента метода.

person Luaan    schedule 03.05.2016
comment
Но в ОП указано, что это на C, поэтому классов нет. Вы можете передать их функции инициализации и сохранить значения в статических переменных в коде модуля. - person jamesqf; 03.05.2016
comment
@jamesqf Ну, я просто использую то же имя, что и OP :) Важной частью является передача значений, а не то, как именно это реализовано. - person Luaan; 03.05.2016

В C/C++, если main() не экспортирует их, то прямого доступа к ним нет; однако это не означает, что нет косвенного пути. Многие Posix-подобные системы используют формат elf, который передает argc, argv и envp в стек, чтобы быть инициализированным _start() и переданным в main() посредством обычного соглашения о вызовах. Обычно это делается на ассемблере (поскольку до сих пор нет переносимого способа получить указатель стека) и помещается в «стартовый файл», обычно с некоторым вариантом имени crt.o.

Если у вас нет доступа к main(), чтобы вы могли просто экспортировать символы, у вас, вероятно, не будет доступа к _start(). Так почему же я вообще упоминаю об этом? Из-за этого третьего параметра envp. Поскольку environ — это стандартная экспортируемая переменная, которая действительно устанавливается во время _start() с помощью envp. Во многих системах ELF, если вы возьмете базовый адрес environ и пройдете его в обратном направлении, используя отрицательные индексы массива, вы можете вывести параметры argc и argv. Первый должен быть NULL, за которым следует последний параметр argv, пока вы не дойдете до первого. Когда указанное значение, приведенное к long, равно отрицательному значению вашего отрицательного индекса, у вас есть argc, а следующим (на единицу больше, чем ваш отрицательный индекс) является argv/argv[0].

person technosaurus    schedule 03.05.2016

Мне известно несколько распространенных сценариев с функциями, требующими аргументов типа int argc, char *argv[]. Одним из таких очевидных примеров является GLUT, где его функция инициализации принимает эти аргументы от main(), что является своего рода «вложенным основным» сценарием. Это может быть или не быть вашим желаемым поведением. Если нет, так как не существует соглашения об именах этих аргументов, пока у вашей функции есть свой анализатор аргументов, и вы знаете, что делаете, вы можете делать все, что вам нужно, жестко запрограммировано:

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

или читать из пользовательского ввода или генерировать иначе, насколько я знаю.

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

См. этот сообщение SO.

person user3078414    schedule 03.05.2016

Наиболее переносимым способом было бы использование глобальной переменной для хранения параметров. Вы можете сделать это менее уродливым, используя Singleton (как ваш класс в вопросе, но синглтон, инициализированный main) или аналогичный Service Locator, который в основном такой же: создайте объект в main, статически передавайте и сохраняйте параметры и к ним обращается другой или тот же класс.

Непереносимые способы используют GetCommandLine в Windows, доступ к /proc/<pid>/cmdline или (/proc/self/cmdline) или использование специфичных для компилятора расширений, таких как __attribute__((constructor))

Обратите внимание, что получить функцию командной строки через эквивалент GetCommandLine невозможно (TLDR: Commandline не передается ядру, но уже анализируется и разделяется вызывающим процессом (например, оболочкой))

person Flamefire    schedule 10.05.2016

Похоже, вам нужна глобальная переменная; что вы должны сделать, это просто передать argc и argv в качестве параметров.

person Johnathan Andersen    schedule 03.05.2016
comment
Это именно то, чего я не хочу делать. - person user1095108; 03.05.2016
comment
Итак, вам нужен геттер для глобальной переменной, но вам не нужна глобальная переменная, а? - person CoffeDeveloper; 03.05.2016