MFC和WinMain

因为网络上找了很久都没有找到关于MFC的WinMain的清晰解释, 我决定自己写一篇. 大多数文章止步于"WinMain被隐藏在MFC框架中了. ". 但实际情况比这个要复杂得多.

先来看一个简化版, 要将main函数隐藏在库文件中并不难, 我们可以写一个很小的lib来演示一下. 比如定义一个test.lib. 由test.h和test.cpp组成. 如下

// test.h
#include <iostream>
using namespace std;

class Framework {
public:
    Framework();

public:    
    virtual void func() {
        cout << "框架类的实现" << endl;
    }
};

//test.cpp
#include <test.h>
#include <assert.h>

Framework pGlobal;

Framework::Framework() {    
    cout << "assign pGlobal " << endl;
    pGlobal this;

}

int main() {
    
    cout << "main before" << endl;
    
    assert(pGlobal);
    
    pGlobal->func();
    return 0;
    
}

编译方法: 将test.h拷贝到D:\Microsoft Visual Studio 8\VC\include目录下, 然后在test.cpp所在的目录执行:

cl /O2 /c  /EHsc /"_DEBUG" /FD /Fd"test.pdb" /Zi /MTd test.cpp
lib /out:"test.lib" test.obj
copy test.lib "d:\Microsoft Visual Studio 8\vc\lib\test.lib"
del test.lib

现在我们已经有一个包装了main函数的库文件了. 然后新建app.cpp, 来使用这个库.

#include <test.h>

class MyClass public Framework {

public:
    MyClass();
    virtual void func() {
        cout << "func in MyClass" << endl;
    }
};

MyClass::MyClass () {}

MyClass my;

编译app.cpp, 生成可执行文件然后执行

cl /O2 /c  /EHsc /"_DEBUG" /FD /Fd"app.pdb" /Zi /MTd app.cpp
link /OUT:"app.exe" /INCREMENTAL /SUBSYSTEM:CONSOLE /MACHINE:X86  /DEBUG /PDB:"app.pdb" app.obj test.lib
app.exe

输出:

assign pGlobal
main before
func in MyClass

以上代码就是MFC隐藏WinMain的原理的简化版, 实际的实现比较复杂, 但是从原理上看, 和上面没有什么不同. 概括起来是几条: 一是全局实例的构造函数在main函数之前执行, 二是通过对虚函数重载重写框架的函数, 从而替换框架内置的默认实现, 所以MFC程序绝大部分是在重写框架已有的函数, 然后在某个时候被框架调用. 真正属于MFC程序的代码实际上只有一句就是定义全局对象, 即MyClass my; 在MFC中是定义CWinApp继承类的全局对象.

下面开始剖析MFC的实现, 可能会看起来很复杂, 因为从CWinApp的构造函数到WinMain, 再到框架能够调用你的InitInstance这中间经过了很复杂的路径, 再加上MFC对宏近乎滥用的做法, 使得MFC代码像迷宫一样难以理清头绪. 相信很多人都曾经试图阅读MFC的源码但是半途而废, 在这里我们就来理一理头绪.

1. AFX_MODULE_STATE和_AfxGetOleModuleState

这是两个重要的东西, 请记住他们. 代码位于D:\Microsoft Visual Studio 8\VC\atlmfc\src\mfc\dllinit.cpp:611行.

static AFX_MODULE_STATE _afxOleModuleState(TRUE, &AfxWndProcDllOle,
    _MFC_VERTRUE);

AFX_MODULE_STATEAFXAPI _AfxGetOleModuleState()
{
    return &_afxOleModuleState;
}

AFX_MODULE_STATE是一个类, 这里定义了一个该类的静态的全局对象实例, _AfxGetOleModuleState函数则返回这个实例的指针. 查看AFX_MODULE_STATE的成员会发现下面一个成员:

CWinAppm_pCurrentWinApp;

在开头的例子程序中, 我们将this赋值给了pGlobal, 这是MyClass全局实例的指针. 通过这个指针再加上虚函数, 框架就可以调用用户所写的函数. m_pCurrentWinApp就相当于这个pGlobal.

2.CCmdTarget::m_pModuleState

这个成员的类型是AFX_MODULE_STATE* , 参考afxwin.h文件中的定义, 然后找到CCmdTarget的构造函数:

CCmdTarget::CCmdTarget()
{
    // capture module state where object was constructed
    m_pModuleState AfxGetModuleState();
    ASSERT(m_pModuleState != NULL);

    ...
    ...
}

这里提前说明一下AfxGetModuleState的返回值就是前面的静态实例的指针. 即CCmdTarget::m_pModuleState == &_afxOleModuleState. 而CWinApp会继承CCmdTarget::m_pModuleState . 而且会在构造函数中使用.

3._AFX_THREAD_STATE

本来我们应该马上讨论AfxGetModuleState的实现, 怎样返回静态全局实例的指针, 但是首先必须弄清楚_AFX_THREAD_STATE. 这个类也是千万要记住的. 如果观察他的类定义, 会发现如下成员:

AFX_MODULE_STATEm_pModuleState;

那么这个成员和前面谈到的几个概念有什么关系? 这里先预先说明: 会有一个_AFX_THREAD_STATE类的实例, 他的m_pModuleState成员 == CCmdTarget::m_pModuleState == &_afxOleModuleState . 现在最重要的就是记清楚_AFX_THREAD_STATE是一个类名.

4._afxThreadState

定义如下:

EXTERN_THREAD_LOCAL(_AFX_THREAD_STATE_afxThreadState)

展开后

extern CThreadLocal<_AFX_THREAD_STATE_afxThreadState;

extern是声明, 真正定义的地方是afxstate.cpp 117行.

THREAD_LOCAL(_AFX_THREAD_STATE_afxThreadState)
展开
AFX_COMDAT CThreadLocal<_AFX_THREAD_STATE_afxThreadState;

所以_afxThreadState是一个全局的CThreadLocal<_AFX_THREAD_STATE>类型的实例. 显然CThreadLocal是一个模板类, 来看看这个模板类:

template<class TYPE>
class CThreadLocal public CThreadLocalObject
{
// Attributes
public:
    AFX_INLINE TYPEGetData()
    {
        TYPEpData = (TYPE*)CThreadLocalObject::GetData(&CreateObject);
        ENSURE(pData != NULL);
        return pData;
    }
    
    AFX_INLINE operator TYPE*()
    
        return GetData(); 
    }

// Implementation
public:
    static CNoTrackObjectAFXAPI CreateObject()
        return new TYPE; }
};

为了方便查看, 删掉了几个函数, 只保留了3个, 因为现在的重点是这三个函数. 从这几个函数来看, 该模板类的作用是托管TYPE类型的实例, 并返回TYPE的实例的指针. 现在应该把TYPE当成_AFX_THREAD_STATE来看待. 所以_afxThreadState就是用来托管_AFX_THREAD_STATE的实例的. 虽然还没有研究ThreadLocalObject::GetData的实现, 大致的猜想如下, 获取_AFX_THREAD_STATE实例的指针, 如果实例已存在, 直接返回指针, 如果不存在就使用CreateObject新建实例, 新建之后应该会保存在某个地方, 下次再调用的时候就会直接返回.

还有一个值得注意的是类型转换操作符重载函数, 当需要从CThreadLocal<_AFX_THREAD_STATE> 转换为 _AFX_THREAD_STATE* 类型的时候, 就会调用GetData来完成类型转换. 因此对于GetData只需要记住一点: 第一次调用的时候会new一个实例, 之后的每次调用都是直接返回第一次创建的实例. 所以必须知道这个实例第一次创建是什么时候发生的, 创建之后对实例做了哪些初始化的工作也必须知道.

5. DllMain

前面知道全局对象的构造函数在main函数之前执行, 而比构造函数执行的更早的是DllMain. 在dllinit.cpp中有RawDllMain函数, 这是dll的入口点, 其中有三行代码很关键.

            // set module state before initialization
            AFX_MODULE_STATEpModuleState _AfxGetOleModuleState();
            _AFX_THREAD_STATEpState AfxGetThreadState();
            pState->m_pPrevModuleState AfxSetModuleState(pModuleState);

第一行参考上面的1. AFX_MODULE_STATE和_AfxGetOleModuleState, 第二行GetData是首次调用, 因此会执行new操作, 第三行:

AFX_MODULE_STATEAFXAPI AfxSetModuleState(AFX_MODULE_STATEpNewState)
{
    _AFX_THREAD_STATEpState _afxThreadState;
    ASSERT(pState);
    if(pState)
    {
        AFX_MODULE_STATEpPrevState pState->m_pModuleState;
        pState->m_pModuleState pNewState;
        return pPrevState;
    }
    else
    {
        return NULL;
    }
}

此时_afxThreadState的赋值发生隐式类型转换, 调用GetData, 得到是初次创建的实例, 然后pState->m_pModuleState = pNewState; 这样_afxThreadState所代表的那个_AFX_THREAD_STATE实例的成员m_pModuleState就是第一点中提到的_afxOleModuleState实例的指针.

所以有下面的等式

_afxThreadState.GetData->m_pModuleState ==
CCmdTarget::m_pModuleState ==
CWinApp::m_pModuleState ==
_AfxGetOleModuleState ==
&_afxOleModuleState

最后再来看AfxGetApp就比较清楚了.

_AFXWIN_INLINE CWinAppAFXAPI AfxGetApp()
    return afxCurrentWinApp; }

#define afxCurrentWinApp    AfxGetModuleState()->m_pCurrentWinApp

AFX_MODULE_STATEAFXAPI AfxGetModuleState()
{
    _AFX_THREAD_STATEpState _afxThreadState;
    ENSURE(pState);
    AFX_MODULE_STATEpResult;
    if (pState->m_pModuleState != NULL)
    {
        // thread state's module state serves as override
        pResult pState->m_pModuleState;
    }
    else
    {
        // otherwise, use global app state
        pResult _afxBaseModuleState.GetData();
    }
    ENSURE(pResult != NULL);
    return pResult;
}