Win32 против MFC - Часть I - Основы приложения MFC

ОГЛАВЛЕНИЕ

Основы приложения MFC

Приложение MFC инкапсулирует большую часть Win32 API, упрощая жизнь каждого программиста. Есть много книг и статей о точке входа приложения MFC. Все они в той или иной степени  заявляют, что точкой входа приложения MFC является функция InitInstance программы.
Это порождает новый вопрос: «Где находится функция WinMain, если функция InitInstance является точкой входа?»

Чтобы выяснить, что происходит внутри, создайте программу-пример, чтобы изучить и просмотреть детали, скрытые от программиста MFC. Для создания программы-примера запустите MSVC++ 6.0. В меню «Файл» выберите пункт «Новый». Выбрав вкладку «Проект», выделите Мастер приложений MFC (.exe) и в поле редактирования имени проекта введите "sdisample" и нажмите кнопку Ok. Выберите «Отдельный документ» и нажмите кнопку «Завершить». Нажмите Ok, чтобы позволить мастеру создать каркас приложения.

На первый взгляд выяснится, что приложение содержит следующие классы:
•    CAboutDlg
•    CMainFrame
•    CSdiSampleApp
•    CSdiSampleDoc
•    CSdiSampleView

Один из этих классов по имени CSdiSampleApp унаследован от класса CWinApp:

Вышеуказанный класс имеет функцию-член по имени InitInstance, называемую точкой входа приложения. Встает вопрос, почему эта функция называется точкой входа приложения MFC? Ответ на данный вопрос лежит за архитектурой MFC. Глупо, да?

Было сказано, что каждая программа Windows содержит две функции: WinMain и wndProc. То же самое относится к приложениям MFC. Они тоже имеют функцию WinMain и wndProc.

Запустите программу путем нажатия кнопки F10. Вы вскоре выясните, что выполнение программы останавливается на следующей функции:

extern "C" int WINAPI _tWinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPTSTR lpCmdLine,
    int nCmdShow)
{
    // вызывается общая/экспортированная WinMain
    return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}

Посмотрите внимательно! Функция имеет знакомые параметры, такие же, как у функции WinMain. Но что за мерзость объявлена перед WINAPI? extern "C" сообщает компилятору, как компилировать эту функцию, и WINAPI определено следующим образом:

#define WINAPI __stdcall

А что насчет _tWinMain? Она объявлена следующим образом:

#define _tWinMain WinMain

Оказались снова в том же самом месте! Здесь рассмотренная ранее функция WinMain:

int APIENTRY WinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow)

а здесь функция, сгенерированная MFC:

extern "C" int WINAPI _tWinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPTSTR lpCmdLine,
    int nCmdShow)

Сравнение этих двух функций показывает, что они одинаковые. Ловко, да? Теперь тщательней рассмотрим функцию _tWinMain и ее реализацию:

extern "C" int WINAPI _tWinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPTSTR lpCmdLine,
    int nCmdShow)
{
    // вызывается общая/экспортированная WinMain
    return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}

Как видно, она вызывает и возвращает функцию AfxWinMain, функцию WinMain каркаса приложения! Но какой она является и как реализована? Ниже приведена важная часть функции AfxWinMain:

int AFXAPI AfxWinMain(HINSTANCE hInstance, 
                      HINSTANCE hPrevInstance,
                      LPTSTR lpCmdLine,
                      int nCmdShow)
{
    //Обрезано
    //внутренняя реализация AFX
    if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
        goto InitFailure;

    // Глобальные инициализации приложения (редкие)
    if (pApp != NULL && !pApp->InitApplication())
        goto InitFailure;

    // Выполняются специфические инициализации
    if (!pThread->InitInstance())
    {
        if (pThread->m_pMainWnd != NULL)
        {
            TRACE0("Warning: Destroying non-NULL m_pMainWnd\n");
            pThread->m_pMainWnd->DestroyWindow();
        }
        nReturnCode = pThread->ExitInstance();
        goto InitFailure;
    }

    nReturnCode = pThread->Run();

    //Обрезано
}

Сначала AfxWinMain вызывает AfxWinInit, функцию, вызывающую MFCO42D.DLL (если программа выполняется в отладочной версии), а также инициализирующую переменные-члены надлежащим именем файла выполнения, файла справки и файла .ini. (Правда, AfxWinMain делает немного больше, но это опущено, потому что MFC тут не переделывается).

Затем AfxWinMain вызывает функцию InitApplication, которую можно легко переопределить в классе CSdiSampleApp с помощью мастера класса. Эта функция используется для выполнения однократной инициализации приложения, и через несколько строк она вызывает функцию InitInstance, названную точкой входа приложения MFC. Затем функция AfxWinMain вызывает функцию «Выполнить»! Эта функция вызывает надлежащую функцию CWinApp::Run(), которая в свою очередь вызывает функцию CWinThread::Run().
В этом методе представлен цикл обработки сообщений, реализованный следующим образом:

int CWinThread::Run()
{
    ASSERT_VALID(this);

    // для отслеживания состояния простоя
    BOOL bIdle = TRUE;
    LONG lIdleCount = 0;

    // получает и отправляет сообщения, пока не
    // получено сообщение WM_QUIT.
    for (;;)
    {
        // фаза 1: проверяется, можно ли выполнить работу простоя
        while (bIdle && !::PeekMessage
            (&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))
        {
            // вызывается OnIdle во время пребывания в состоянии bIdle
            if (!OnIdle(lIdleCount++))
                bIdle = FALSE; // принимается состояние отсутствия простоя
        }

        // фаза 2: качаются сообщения, пока доступны
        do
        {
            // качается сообщение, но завершается при WM_QUIT
            if (!PumpMessage())
                return ExitInstance();

            // сбрасывается состояние отсутствия простоя после
            // перекачки сообщения "нормальный"
            if (IsIdleMessage(&m_msgCur))
            {
                bIdle = TRUE;
                lIdleCount = 0;
            }
        } while (::PeekMessage(&m_msgCur,
            NULL, NULL, NULL, PM_NOREMOVE));
    }

    ASSERT(FALSE); // недоступно
}

Как видно, эта функция содержит цикл, и этот цикл прервется, если и только если будет получено сообщение WM_QUIT, и, следовательно, обладает функциональностью вышеназванного цикла обработки сообщений:

MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

Однако между двумя этими циклами есть ряд отличий. Первое и самое важное заключается в том, что CWinThread::Run() вызывает функцию-член OnIdle приложения, когда программе нечего обрабатывать. Во-вторых, он вызывает метод ExitInstance после получения сообщения WM_QUIT и перед выходом из программы. Итак, программист MFC может сделать все, что нужно путем переопределения метода ExitInstance, следя, чтобы этот код вызывался всегда, когда программа собирается завершиться.

PumpMessage, с другой стороны, инкапсулирует API TranslateMessage и DispatchMessage внутри. Теперь имеется цикл обработки сообщений для приложения MFC. Однако еще ничего не было сказано о wndProc приложения MFC. Эта функция является достоинством MFC и будет рассмотрена во второй части данной статьи.