web-dev-qa-db-ja.com

C ++のWINMAINおよびmain()(拡張)

右、私はこの投稿を見ました: C++でのWinMain、mainとDllMainの違い

WINMAINがウィンドウアプリケーションに、main()がコンソールに使用されていることがわかりました。しかし、この投稿を読んでも、違いが何であるかを正確に説明することはできません。

プログラムを開始するために異なる主電源機能を分離する意味は何ですか?パフォーマンスの問題が原因ですか?それとも何ですか?

54
Danny

機能について。

CおよびC++標準では、(「ホストされた」CまたはC++実装の)すべてのプログラムにmainという関数が必要です。これは、プログラムのスタートアップ関数として機能します。 main関数は、非ローカル静的変数のゼロ初期化の後に呼び出されます。必ずしもそうではありませんが(!、C++ 11§3.6.2/ 4)、この呼び出しは動的初期化の後に行われますそのような変数。次の署名のいずれかを使用できます。

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

さらに、結果の型がintでなければならないことを除いて、可能な実装定義の署名(C++ 11§3.6.1/ 2)。

C++のそのような関数はmainにのみデフォルトの結果値、つまり0を持っているため、mainが戻った場合、通常の関数の戻り値exitが結果としてmainを引数として呼び出します。規格では、使用できることが保証されている3つの値を定義しています:0(成功を示す)、EXIT_SUCCESS(成功も示し、通常0と定義されます)、およびEXIT_FAILURE( 2つの名前付き定数は、exit関数も宣言する<stdlib.h>ヘッダーによって定義されます。

main引数は、プロセスの開始に使用されるコマンドのコマンドライン引数を表すことを目的としています。 argc(引数カウント)は、argv(引数値)配列内のアイテムの数です。これらの項目に加えて、argv[argc]は0であることが保証されています。argc> 0の場合–保証されていません! – argv[0]は、空の文字列へのポインタ、または「プログラムの呼び出しに使用される名前」へのポインタのいずれかであることが保証されています。この名前にはパスが含まれる場合があり、実行可能ファイルの名前になる場合があります。

main引数を使用してコマンドライン引数を取得すると、* nixで正常に機能します。これは、CおよびC++が* nixで作成されているためです。しかし デファクト main引数のエンコードのWindows標準はWindows ANSIです。これは一般的なWindowsファイル名(ノルウェー語Windowsインストールの場合、ギリシャ文字またはキリル文字を含むファイル名など)をサポートしません。そのため、Microsoftはwmainと呼ばれるWindows固有のスタートアップ関数でCおよびC++言語を拡張することを選択しました。これは、任意のファイル名を表すことができるUTF-16としてエンコードされるワイド文字ベースの引数を持ちます。

wmain関数には、mainの標準署名に対応する これらの署名の1つ を含めることができます。

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

さらに、特に有用ではないものがいくつかあります。

つまり、wmainは、mainの直接のワイド文字ベースの置換です。

WinMaincharベースの関数は、1980年代初期にWindowsで導入されました。

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

ここで、CALLBACKHINSTANCE、およびLPSTRは、<windows.h>ヘッダーによって定義されます(LPSTRchar*のみです)。

引数:

  • 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
    );

ここで、WINAPICALLBACKと同じであり、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サブシステムプログラムを待機しません。両方の種類のプログラムで完了待機を回避する1つの方法は、startコマンドを使用することです。 GUIサブシステムプログラムからコンソールウィンドウテキストを表示する1つの方法は、その標準出力ストリームをリダイレクトすることです。別の方法は、プログラムのコードからコンソールウィンドウを明示的に作成することです。

プログラムのサブシステムは、実行可能ファイルのヘッダーにエンコードされます。 Windowsエクスプローラーには表示されません(Windows 9xでは、実行可能ファイルを「クイックビュー」できることを除き、Microsoftのdumpbinツールとほぼ同じ情報が表示されます)。対応するC++の概念はありません。

mainとGNUツールチェーン。

 [D:\ dev\test] 
> g ++ foo.cpp
 
 [D:\ dev\test] 
> objdump -x a.exe |/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 |/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] 
> lINK =/entry:mainCRTStartupを設定します
 
 [D:\ dev\test] 
> cl foo.cpp user32.lib
 foo.cpp 
 
 [D:\ dev\test] 
> dumpbin/headers foo.exe |/i "subsys"を見つけます
 6.00サブシステムバージョン
 3サブシステム(Windows CUI)
 
 [D:\ dev\test] 
> cl foo.cpp/link user32.lib/subsystem:windows
 foo.cpp 
 
 [D:\ dev\test] 
> dumpbin/headers foo.exe |/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\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): `WinMain 
 @ 16 '
 collect2.exeへの未定義の参照:エラー:ldが1つの終了ステータス
 [.____を返しました。 ] [D:\ dev\test] 
> _ 

ここでのWinMainに関するリンクエラーメッセージは、GNUツールチェーンがthat関数をサポートしているため(おそらく多くの古代のコードで使用されているため))、標準の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 |/i "サブシステム"を見つける
 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 |/i "サブシステム"を見つける
 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] 
> 設定link =/entry:mainCRTStartup
 
 [D:\ dev\test] 
> cl bar.cpp user32.lib
 bar.cpp 
 LIBCMT.lib(crt0.obj):エラーLNK2019:関数___ tmainCRTStartup 
 bar.exeで参照されている未解決の外部シンボル_main:致命的なエラー.____。] 
 [D:\ dev\test] 
> リンクを設定=
 
 [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 |/i "サブシステム"を見つける
 6.00サブシステムバージョン
 3サブシステム(Windows CUI)
 
 [D:\ dev\test] 
> cl bar.cpp/link user32.lib/entry:wmainCRTStartup/subsystem:windows
 bar.cpp 
 
 [D:\ dev\test] 
> dumpbin/headers bar.exe |/i "サブシステム"を見つける
 6.00サブシステムバージョン
 2サブシステム(Windows GUI)
 
 [D:\ dev\test] 
> _ 
164

@RaymondChenによると

WinMainという名前は単なる慣習です

関数WinMainはPlatform SDKに文書化されていますが、実際にはプラットフォームの一部ではありません。むしろ、WinMainは、Windowsプログラムへのユーザー提供のエントリポイントの従来の名前です。

実際のエントリポイントはCランタイムライブラリにあり、ランタイムを初期化し、グローバルコンストラクターを実行してから、WinMain関数(またはUnicodeエントリポイントが必要な場合はwWinMain)を呼び出します。

DllMainとWinMainは、プロトタイプ自体が異なります。 WinMainはコマンドライン引数を受け入れますが、他の引数はプロセスにどのように接続されているかについて話します。

MSDNドキュメント

デフォルトでは、開始アドレスはCランタイムライブラリの関数名です。次の表に示すように、リンカはプログラムの属性に従ってそれを選択します。

  • mainCRTStartup(またはwmainCRTStartup)_/SUBSYSTEM:CONSOLE;_を使用するアプリケーションはmain(またはwmain)を呼び出します

  • WinMainCRTStartup(またはwWinMainCRTStartup)_/SUBSYSTEM:WINDOWS;_を使用するアプリケーションは、WinMain(またはwWinMain)を呼び出します。これは___stdcall_で定義する必要があります

  • __DllMainCRTStartup_ DLL; DllMainを呼び出します。これは、___stdcall_で定義する必要があります(存在する場合)

8
sarat

標準Cプログラムには、起動時にコマンドラインから2つのパラメーターが渡されます。

int main( int argc, char** argv ) ;
  • char** argvは文字列の配列です(char*
  • int argcはargvのchar*の数です

プログラマーがWindowsプログラム用に作成する必要があるブート関数WinMainはわずかに異なります。 WinMainは、起動時にWin O/Sによってプログラムに渡される4つのパラメーターを取ります。

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で基本的なウィンドウを作成する方法 詳細

3
bobobobo

Windowsプログラムにはmain()関数があることをどこかで読んだことを漠然と思い出します。ヘッダーまたはライブラリのどこかに隠されているだけです。このmain()関数は、WinMain()が必要とするすべての変数を初期化してから呼び出します。

もちろん、私はWinAPI初心者なので、私が間違っていれば、より知識のある人が私を修正してくれることを願っています。

1
Code-Apprentice

_tWinMainとConfiguration Properties.Linker.System.Subsystem:Windows(/ SUBSYSTEM:WINDOWS)を使用したexeがありました。後でコマンドライン引数をサポートし、コンソールに出力したかったので、次を追加しました:

// 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));
}
0
markyoung