WINMAIN и main() в С++ (Extended)

Правильно, я просмотрел этот пост: Разница между WinMain, main и DllMain в С++

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

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

Ответ 1

О функциях.

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

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

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

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

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

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

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

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

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

I.e., wmain является прямой заменой на основе широкого символа для main.

Функция WinMain char была введена в Windows в начале 1980-х годов:

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

где CALLBACK, HINSTANCE и LPSTR определены заголовком <windows.h> (LPSTR - это просто char*).

Аргументы:

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

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

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

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

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

Функция wWinMain wchar_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 - это та, которая не требует окна консоли. Командный интерпретатор не ждет программы подсистемы GUI, за исключением пакетных файлов. Один из способов избежать ожидания завершения для обоих видов программы - использовать команду start. Один из способов представления текста окна консоли из программы подсистемы GUI - перенаправить свой стандартный выходной поток. Другой способ - явно создать окно консоли из программного кода.

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

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

[D:\dev\test]
> g++ foo.cpp

[D:\dev\test]
> objdump -x a.exe | find /i "subsys"
MajorSubsystemVersion   4
MinorSubsystemVersion   0
Subsystem               00000003        (Windows CUI)
[544](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000004 __major_subsystem_version__
[612](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000003 __subsystem__
[636](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000000 __minor_subsystem_version__

[D:\dev\test]
> g++ foo.cpp -mwindows

[D:\dev\test]
> objdump -x a.exe | find /i "subsys"
MajorSubsystemVersion   4
MinorSubsystemVersion   0
Subsystem               00000002        (Windows GUI)
[544](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000004 __major_subsystem_version__
[612](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000002 __subsystem__
[636](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000000 __minor_subsystem_version__

[D:\dev\test]
> _

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 subsystem version
               3 subsystem (Windows CUI)

[D:\dev\test]
> cl foo.cpp /link user32.lib /subsystem:windows
foo.cpp

[D:\dev\test]
> dumpbin /headers foo.exe | find /i "subsys"
            6.00 subsystem version
               2 subsystem (Windows GUI)

[D:\dev\test]
> _

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

.

Следующий основной код является общим как для инструментальной цепочки 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\test]
> g++ bar.cpp
d:/bin/mingw/bin/../lib/gcc/i686-pc-mingw32/4.7.1/../../../libmingw32.a(main.o):main.c:(.text.startup+0xa3): undefined reference to `WinMain
@16'
collect2.exe: error: ld returned 1 exit status

[D:\dev\test]
> _

Сообщение об ошибке связи здесь, 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\test]
> g++ bar.cpp wmain_support.cpp

[D:\dev\test]
> objdump -x a.exe | find /i "subsystem"
MajorSubsystemVersion   4
MinorSubsystemVersion   0
Subsystem               00000003        (Windows CUI)
[13134](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000004 __major_subsystem_version__
[13576](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000003 __subsystem__
[13689](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000000 __minor_subsystem_version__

[D:\dev\test]
> g++ bar.cpp wmain_support.cpp -mwindows

[D:\dev\test]
> objdump -x a.exe | find /i "subsystem"
MajorSubsystemVersion   4
MinorSubsystemVersion   0
Subsystem               00000002        (Windows GUI)
[13134](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000004 __major_subsystem_version__
[13576](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000002 __subsystem__
[13689](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000000 __minor_subsystem_version__

[D:\dev\test]
> _

wmain с инструментальной цепочкой Microsoft.

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

[D:\dev\test]
> set link=/entry:mainCRTStartup

[D:\dev\test]
> cl bar.cpp user32.lib
bar.cpp
LIBCMT.lib(crt0.obj) : error LNK2019: unresolved external symbol _main referenced in function ___tmainCRTStartup
bar.exe : fatal error LNK1120: 1 unresolved externals

[D:\dev\test]
> set link=

[D:\dev\test]
> cl bar.cpp user32.lib
bar.cpp

[D:\dev\test]
> _

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

[D:\dev\test]
> cl bar.cpp /link user32.lib /entry:wmainCRTStartup
bar.cpp

[D:\dev\test]
> dumpbin /headers bar.exe | find /i "subsystem"
            6.00 subsystem version
               3 subsystem (Windows CUI)

[D:\dev\test]
> cl bar.cpp /link user32.lib /entry:wmainCRTStartup /subsystem:windows
bar.cpp

[D:\dev\test]
> dumpbin /headers bar.exe | find /i "subsystem"
            6.00 subsystem version
               2 subsystem (Windows GUI)

[D:\dev\test]
> _

Ответ 2

Согласно @RaymondChen

Имя WinMain - это просто соглашение

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

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

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

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

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

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

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

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

Ответ 3

Стандартная программа 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 для более

Ответ 4

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

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

Ответ 5

У меня был 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));
}