WINMAIN и main() в C++ (расширенный)

Хорошо, я посмотрел на этот пост: разница между WinMain,main и DllMain в C++

Теперь я знаю, что WINMAIN используется для оконных приложений и main() для консолей. Но чтение поста не говорит мне, почему именно, в чем разница.

Я имею в виду, какой смысл иметь разделение различных функций сети для запуска программы? Это связано с проблемами производительности? Или что это?

4 ответа

Решение

О функциях.

Стандарты C и C++ требуют, чтобы любая программа (для "размещенной" реализации C или C++) имела функцию, называемую main, которая служит функцией запуска программы. main Функция вызывается после нулевой инициализации нелокальных статических переменных, и, возможно, но не обязательно (!, C++11 §3.6.2/4), этот вызов происходит после динамической инициализации таких переменных. Может иметь одну из следующих подписей:

int main()
int main( int argc, char* argv[] )

плюс возможные сигнатуры, определяемые реализацией (C++11 §3.6.1/2), за исключением того, что тип результата должен быть int,

Как единственная такая функция в C++ main имеет значение результата по умолчанию, а именно 0. Если main затем возвращается после обычной функции return exit называется с main значение результата в качестве аргумента. Стандарт определяет три значения, которые гарантированно можно использовать: 0 (указывает на успех), EXIT_SUCCESS (также указывает на успех и обычно определяется как 0), и EXIT_FAILURE (указывает на сбой), где две именованные константы определяются <stdlib.h> заголовок, который также объявляет exit функция.

main Аргументы предназначены для представления аргументов командной строки для команды, используемой для запуска процесса. argc (количество аргументов) - это количество элементов в argv (значения аргумента) массив. В дополнение к этим пунктам argv[argc] гарантированно будет 0. Если argc > 0 - что не гарантировано! - затем argv[0] гарантированно либо указатель на пустую строку, либо указатель на "имя, используемое для вызова программы". Это имя может включать путь, и это может быть имя исполняемого файла.

С использованием main Аргументы для получения аргументов командной строки прекрасно работают в *nix, потому что C и C++ происходят с *nix. Тем не менее, де-факто стандарт Windows для кодирования main Аргументы - это Windows ANSI, которая не поддерживает общие имена файлов Windows (например, для норвежской установки Windows, имена файлов с греческими или кириллическими символами). Поэтому Microsoft решила расширить языки C и C++ с помощью специальной для Windows функции запуска wmain, который имеет широкие символьные аргументы, закодированные как UTF-16, которые могут представлять любое имя файла.

wmain Функция может иметь одну из этих сигнатур, соответствующую стандартным сигнатурам для main:

int wmain()
int wmain( int argc, wchar_t* argv[] )

плюс еще несколько, которые не особенно полезны.

То есть, wmain прямая широкая символьная замена для main,

WinMainchar Основанная функция была введена в Windows, в начале 1980-х годов:

int CALLBACK WinMain(
    HINSTANCE   hInstance,
    HINSTANCE   hPrevInstance,
    LPSTR       lpCmdLine,
    int         nCmdShow
    );

где CALLBACK, HINSTANCE а также LPSTR определяются <windows.h> заголовок (LPSTR просто char*).

Аргументы:

  • hInstance Значением аргумента является базовый адрес образа памяти исполняемого файла, он в основном используется для загрузки ресурсов из исполняемого файла, и его можно альтернативно получить из GetModuleHandle Функция API,

  • hPrevInstance аргумент всегда равен 0,

  • lpCmdLine аргумент может быть альтернативно получен из GetCommandLine API-функция, плюс немного странной логики для пропуска части имени программы в командной строке, и

  • nCmdShow значение аргумента может быть альтернативно получено из GetStartupInfo Функция API, но в современной Windows первое создание окна верхнего уровня делает это автоматически, поэтому оно не имеет никакого практического применения.

Таким образом WinMain функция имеет те же недостатки, что и стандартная mainплюс некоторые (в частности, многословие и нестандартность), и никаких собственных преимуществ, так что это действительно необъяснимо, разве что как вещь, связывающая продавца. Тем не менее, с помощью цепочки инструментов Microsoft он устанавливает компоновщик по умолчанию для подсистемы GUI, что некоторые считают преимуществом. Но, например, с помощью цепочки инструментов GNU, он не имеет такого эффекта, поэтому на него нельзя положиться.

wWinMainwchar_t основанная функция - это широкий символьный вариант WinMain так же, как wmain широкоформатный вариант стандарта main:

int WINAPI wWinMain(
    HINSTANCE   hInstance,
    HINSTANCE   hPrevInstance,
    PWSTR       lpCmdLine,
    int         nCmdShow
    );

где WINAPI такой же как CALLBACK, а также PWSTR это просто wchar_t*,

Нет веских причин использовать какие-либо нестандартные функции, кроме наименее известных и наименее поддерживаемых из них, а именно wmain, а затем просто для удобства: это позволяет избежать использования GetCommandLine а также CommandLineToArgvW API-функции для сбора аргументов в кодировке UTF-16.

Чтобы компоновщик Microsoft не работал (компоновщик цепочки инструментов GNU не работает), просто установите LINK переменная среды для /entry:mainCRTStartup или укажите эту опцию напрямую. Это функция точки входа библиотеки времени выполнения Microsoft, которая после некоторой инициализации вызывает стандарт main функция. Другие функции запуска имеют соответствующие функции точки входа, названные таким же систематическим образом.


Примеры использования стандарта main функция.

Общий исходный код:

foo.cpp

#undef UNICODE
#define UNICODE
#include <windows.h>

int main()
{
    MessageBox( 0, L"Press OK", L"Hi", MB_SETFOREGROUND );
}

В приведенных ниже примерах (сначала с помощью цепочки инструментов GNU, а затем с цепочкой инструментов Microsoft) эта программа сначала создается как программа подсистемы консоли, а затем как программа подсистемы GUI. Программа консольной подсистемы, или, вкратце, просто консольная программа, требует консольного окна. Это подсистема по умолчанию для всех линкеров Windows, которые я использовал (правда, не очень много), возможно, для всех периодов линкеров Windows.

Для консольной программы Windows создает окно консоли автоматически, если это необходимо. Любой процесс Windows, независимо от подсистемы, может иметь связанное окно консоли и самое большее одно. Кроме того, интерпретатор команд Windows ожидает завершения программы консольной программы, чтобы текстовое представление программы завершилось.

И наоборот, программа с графическим интерфейсом не требует консольного окна. Интерпретатор команд не ожидает программы подсистемы GUI, кроме как в пакетных файлах. Один из способов избежать ожидания завершения для обоих видов программ - использовать start команда. Одним из способов представления текста окна консоли из программы подсистемы GUI является перенаправление стандартного потока вывода. Другой способ - явно создать консольное окно из кода программы.

Подсистема программы закодирована в заголовке исполняемого файла. Это не отображается в проводнике Windows (за исключением того, что в Windows 9x можно было "быстро просмотреть" исполняемый файл, который представлял примерно ту же информацию, что и Microsoft). dumpbin инструмент сейчас делает). Не существует соответствующей концепции C++.

main с помощью цепочки инструментов GNU.

[D:\ DEV \ тест]
> g ++ foo.cpp

[D:\ DEV \ тест]
> objdump -x a.exe | найти / я "subsys"
MajorSubsystemVersion 4
MinorSubsystemVersion 0
Подсистема 00000003        (Windows CUI)
[544](с -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000004 __major_subsystem_version__
[612](с -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000003 __subsystem__
[636](с -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000000 __minor_subsystem_version__

[D:\ DEV \ тест]
> g ++ foo.cpp -mwindows

[D:\ DEV \ тест]
> objdump -x a.exe | найти / я "subsys"
MajorSubsystemVersion 4
MinorSubsystemVersion 0
Подсистема 00000002        (Windows GUI)
[544](с -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000004 __major_subsystem_version__
[612](с -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000002 __subsystem__
[636](с -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000000 __minor_subsystem_version__

[D:\ DEV \ тест]
> _

main с помощью набора инструментов Microsoft:

[D:\ dev \ test]> set LINK =/ entry: mainCRTStartup [D:\ dev \ test]> cl foo.cpp user32.lib foo.cpp [D:\ dev \ test]> dumpbin / headers foo.exe | find / i "subsys" 6.00 подсистема версии 3 (Windows CUI)

[D:\ dev \ test]> cl foo.cpp / link user32.lib / subsystem: windows foo.cpp [D:\ dev \ test]> dumpbin / заголовки foo.exe | find / i "subsys" 6.00 подсистема версии 2 (Windows GUI)

[D:\dev\test]
> _ 

Примеры использования Microsoft wmain функция.

Следующий основной код является общим для демонстраций инструментальной цепи GNU и Microsoft:

bar.cpp

#undef UNICODE
#define UNICODE
#include <windows.h>

#include <string>       // std::wstring
#include <sstream>      // std::wostringstream
using namespace std;

int wmain( int argc, wchar_t* argv[] )
{
    wostringstream  text;

    text << argc - 1 << L" command line arguments:\n";
    for( int i = 1;  i < argc;  ++i )
    {
        text << "\n[" << argv[i] << "]";
    }

    MessageBox( 0, text.str().c_str(), argv[0], MB_SETFOREGROUND );
}

wmain с помощью цепочки инструментов GNU.

Набор инструментов GNU не поддерживает Microsoft wmain функция:

[D:\ DEV \ тест]
> g ++ bar.cpp
д: / бен / MinGW / бен /../ Библиотека / GCC /i686-PC-mingw32/4.7.1/../../../libmingw32.a(main.o):main.c:(.text.startup+0xa3): неопределенная ссылка на `WinMain
@16'
collect2.exe: ошибка: ld вернул 1 состояние выхода

[D:\ DEV \ тест]
> _

Ссылка на сообщение об ошибке здесь, о WinMain, потому что набор инструментов GNU действительно поддерживает эту функцию (предположительно потому, что ее использует очень много древнего кода), и ищет ее в качестве крайней меры после того, как не может найти стандарт main,

Тем не менее, добавить модуль со стандартным main что вызывает wmain:

wmain_support.cpp

extern int wmain( int, wchar_t** );

#undef UNICODE
#define UNICODE
#include <windows.h>    // GetCommandLine, CommandLineToArgvW, LocalFree

#include <stdlib.h>     // EXIT_FAILURE

int main()
{
    struct Args
    {
        int n;
        wchar_t** p;

        ~Args() {  if( p != 0 ) { ::LocalFree( p ); } }
        Args(): p(  ::CommandLineToArgvW( ::GetCommandLine(), &n ) ) {}
    };

    Args    args;

    if( args.p == 0 )
    {
        return EXIT_FAILURE;
    }
    return wmain( args.n, args.p );
}

Сейчас,

[D:\ DEV \ тест]
> g ++ bar.cpp wmain_support.cpp

[D:\ DEV \ тест]
> objdump -x a.exe | найти / я "подсистема"
MajorSubsystemVersion 4
MinorSubsystemVersion 0
Подсистема 00000003        (Windows CUI)
[13134](с -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000004 __major_subsystem_version__
[13576](с -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000003 __subsystem__
[13689](с -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000000 __minor_subsystem_version__

[D:\ DEV \ тест]
> g ++ bar.cpp wmain_support.cpp -mwindows

[D:\ DEV \ тест]
> objdump -x a.exe | найти / я "подсистема"
MajorSubsystemVersion 4
MinorSubsystemVersion 0
Подсистема 00000002        (Windows GUI)
[13134](с -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000004 __major_subsystem_version__
[13576](с -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000002 __subsystem__
[13689](с -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000000 __minor_subsystem_version__

[D:\ DEV \ тест]
> _

wmain с инструментарием Microsoft.

С помощью набора инструментов Microsoft компоновщик автоматически выводит wmainCRTStartup точка входа, если точка входа не указана и wmain функция присутствует (неясно, что происходит, если стандарт main также присутствует, я не проверял это в последние годы):

[D:\ DEV \ тест]
> установить ссылку =/ запись: mainCRTStartup

[D:\ DEV \ тест]
> cl bar.cpp user32.lib
bar.cpp
LIBCMT.lib (crt0.obj): ошибка LNK2019: неразрешенный внешний символ _main, на который ссылается функция ___tmainCRTStartup
bar.exe: фатальная ошибка LNK1120: 1 неразрешенная внешность

[D:\ DEV \ тест]
> установить ссылку =

[D:\ DEV \ тест]
> cl bar.cpp user32.lib
bar.cpp

[D:\ DEV \ тест]
> _

С нестандартной функцией запуска, такой как wmain однако, вероятно, лучше всего указать точку входа в явном виде, чтобы иметь четкое представление о намерении:

[D:\ DEV \ тест]
> cl bar.cpp / ссылка user32.lib / entry: wmainCRTStartup
bar.cpp

[D:\ DEV \ тест]
> свалка / заголовки bar.exe | найти / я "подсистема"
            6.00 версия подсистемы
               3 подсистема (Windows CUI)

[D:\ DEV \ тест]
> cl bar.cpp / ссылка user32.lib / entry: wmainCRTStartup / subsystem: windows
bar.cpp

[D:\ DEV \ тест]
> свалка / заголовки bar.exe | найти / я "подсистема"
            6.00 версия подсистемы
               2 подсистема (Windows GUI)

[D:\ DEV \ тест]
> _

По словам @RaymondChen

Название WinMain это просто соглашение

Хотя функция WinMain описана в Platform SDK, на самом деле она не является частью платформы. Скорее, WinMain - это условное имя для пользовательской точки входа в программу Windows.

Реальная точка входа находится в библиотеке времени выполнения C, которая инициализирует среду выполнения, запускает глобальные конструкторы, а затем вызывает вашу функцию WinMain (или wWinMain, если вы предпочитаете точку входа Unicode).

DllMain и WinMain отличаются друг от друга своими прототипами. WinMain принимает аргумент командной строки, а другой говорит о том, как он присоединен к процессу.

Согласно документации MSDN

По умолчанию начальный адрес является именем функции из библиотеки времени выполнения C. Компоновщик выбирает его в соответствии с атрибутами программы, как показано в следующей таблице.

  • mainCRTStartup (или же wmainCRTStartup) Приложение, использующее/SUBSYSTEM:CONSOLE; звонит главный (или wmain)

  • WinMainCRTStartup (или же wWinMainCRTStartup) Приложение, использующее/SUBSYSTEM:WINDOWS; звонки WinMain (или же wWinMain), который должен быть определен с __stdcall

  • _DllMainCRTStartup DLL; звонки DllMain, который должен быть определен с __stdcallесли он существует

Стандартной программе на C передается 2 параметра командной строкой при запуске:

int main( int argc, char** argv ) ;
  • char** argv это массив строк (char*)
  • int argc это число char* в argv

Функция загрузки WinMain То, что программисты должны писать для программы Windows, немного отличается. WinMain принимает 4 параметра, которые передаются программе Win O/S при запуске:

int WINAPI WinMain( HINSTANCE hInstance,    // HANDLE TO AN INSTANCE.  This is the "handle" to YOUR PROGRAM ITSELF.
                    HINSTANCE hPrevInstance,// USELESS on modern windows (totally ignore hPrevInstance)
                    LPSTR szCmdLine,        // Command line arguments.  similar to argv in standard C programs
                    int iCmdShow )          // Start window maximized, minimized, etc.

Смотрите мою статью Как создать базовое окно в C для более

Windows CRT предоставляет 5 символов maincrtstartup, wmainCRTStartup, wWinMainCRTStartup, _DllMainCRTStartup, WinMainCRTStartup

Если параметр /DLL или /SUBSYSTEM не указан, компоновщик MSVC выбирает подсистему и точку входа в зависимости от того, определено ли main или WinMain. Итак, по-видимому, компоновщик делает адрес входа одной из этих 5 функций в зависимости от того, присутствует ли main, WinMain или DllMain в таблице символов. Функции статически связаны через gcc через crt0.o.

MainCRTStartup позвонит GetStartupInfo() / получить доступ к PEB для получения дескрипторов stdin/out и аргументов командной строки. _init term() также называется, а также _init_atexit(). Возвращаемое значение main передается вExitProcess() после набора atexit рутина называется.

Я смутно припоминаю, что где-то читал, что программы Windows имеют main() функция. Он просто где-то спрятан в заголовке или библиотеке. Я верю этому main() Функция инициализирует все переменные, необходимые для WinMain() а потом называет это.

Конечно, я новичок в WinAPI, поэтому я надеюсь, что другие, кто более осведомлен, исправят меня, если я ошибаюсь.

У меня был exe-файл с использованием _tWinMain и Configuration Properties.Linker.System.Subsystem: Windows (/SUBSYSTEM:WINDOWS). Позже я хотел, чтобы он поддерживал аргументы cmdline и печатал на консоль, поэтому я добавил:

// We need to printf to stdout and we don't have one, so get one
AllocConsole();
// Redirect pre-opened STDOUT to the console
freopen_s((FILE **)stdout, "CONOUT$", "w", stdout);

но это работало только при печати в другом окне консоли, которое исчезло, так что это было не очень полезно. Ниже описан способ, которым я изменил его для работы с Консолью (/SUBSYSTEM:CONSOLE) таким образом, чтобы я мог переходить туда-сюда, если мне было нужно.

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
  UNREFERENCED_PARAMETER(argc);
  UNREFERENCED_PARAMETER(argv);
  UNREFERENCED_PARAMETER(envp);
  return (_tWinMain(NULL, NULL, ::GetCommandLineW(), 0));
}

Главная vs WinMain

Как я прочитал по многим ссылкам:

WinMain() это Cфункция точки входа любого приложения Windows. Как обычноDOS/console приложение на основе, которое имеет main() функционировать как C точка входа, в окнах у нас есть WinMain() вместо. WinMain() это функция, которая вызывается системой во время создания процесса.

Первый аргумент - это дескриптор экземпляра текущего процесса.

Далее идет предыдущий экземпляр.

Аргументы командной строки идут как следующий аргумент.

Наконец, оболочка передает атрибут show/display главного окна.

Примечание: WinMain возвращает успех как ноль, а ошибку как ненулевое.

Другие вопросы по тегам