MFC内部机制探秘

由于MFC应用程序涵盖了基于SDK的windows程序几乎所有的功能,所以使用MFC AppWizard创建的MFC程序将自动具有WIndows程序的基本功能,我们今天就来探寻一下MFC的框架机制。

首先大家先利用向导制动建立一个基于MFC的单文档应用程序。我取名为MFC_DISCOVER,方便大家自己验证自己的程序。

1.声明全局对象

利用应用程序对象theApp启动应用程序,theApp一般在应用程序类的实现文件中声明,而且为全局对象。在类视图点击CMFC_DISCOVERApp,再点击下面的构造成员函数,

然后在函数对应编辑框中会找到这样一句话:

声明该类的变量显然会调用该类的构造函数,构造函数就在声明该变量的上面,但是我们知道CMFC_DISCOVER类派生于CWinApp类,所以会先调用基类的构造函数,

点击上图中的CWinApp类右键,点击“转到定义”,可以查看到CWinApp类的构造函数

// Constructor
    explicit CWinApp(LPCTSTR lpszAppName = NULL); 

然而我们发现该类依然有派生类CWinThread,继续按照上面做发现有这样的关系:

CObeject->CCmdTarget->CWinTread

我就不一一截图了,我们知道是在这个过程中进入WinMain函数的,那么WinMain在哪儿呢?

我们在项目管理搜索栏目里面输入WinMain,搜索如下:

在tchar.h文件中发现有这样一个定义,也就是说_tWinMain就是WinMain,现在我们需要找到_tWinMain函数。不要设断点,直接按F10键,我们就看到了_tWinMain函数了:

这个函数在MFC的源文件appmodul.cpp中,那这个main怎么被MFC调用的呢?你看那注释,是linkage to this module,也就是被链接器去调用的.准确说是被C-Runtime DLL,C运行时动态链接库调用的.



我们知道在MFC中能从代码里看到的入口点是定义一个全局的继承于CWinApp的类.这样定义下.在C++中全局变量是先于main被执行的,所以先初始化theApp后才接着调用main,WinMain里面又调用了AfxWinMain函数

2.AfxWinMain

要查看AFxWinMain函数的源码,就需要一步你的安装目录下啦,找到安装目录->vc98->MFC->Src(MFC的部分源代码) 搜索 WinMain,然后打开,就会有下面的代码(不要改动这个代码

int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    _In_ LPTSTR lpCmdLine, int nCmdShow)
{
    ASSERT(hPrevInstance == NULL);

    int nReturnCode = -1;
    CWinThread* pThread = AfxGetThread();
    CWinApp* pApp = AfxGetApp();

    // AFX internal initialization
    if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
        goto InitFailure;

    // App global initializations (rare)
    if (pApp != NULL && !pApp->InitApplication())
        goto InitFailure;

    // Perform specific initializations
    if (!pThread->InitInstance())//调用******InitIncetance函数**********
    {
        if (pThread->m_pMainWnd != NULL)
        {
            TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n");
            pThread->m_pMainWnd->DestroyWindow();
        }
        nReturnCode = pThread->ExitInstance();
        goto InitFailure;
    }
    nReturnCode = pThread->Run();//******进入消息循环*********

InitFailure:
#ifdef _DEBUG
    // Check for missing AfxLockTempMap calls
    if (AfxGetModuleThreadState()->m_nTempMapLock != 0)
    {
        TRACE(traceAppMsg, 0, "Warning: Temp map lock count non-zero (%ld).\n",
            AfxGetModuleThreadState()->m_nTempMapLock);
    }
    AfxLockTempMaps();
    AfxUnlockTempMaps(-1);
#endif

    AfxWinTerm();
    return nReturnCode;
}

在AfxWInMain函数中,通过InitInstance函数完成吧了对窗口的创建步骤:设计窗口类,注册窗口类,创建窗口,显示窗口,更新窗口,消息循环,以及窗口函数等。

**根据多态性原理,AfxWinMain函数实际上是调用CMFC_DISCOVERApp的InitInstance函数来完成应用程序的一些初始化工作,包括窗口类的注册,创建,显示和更新等。

InitInstance函数代码如下:

BOOL CMFC_DISCOVERApp::InitInstance()
{
    // 如果一个运行在 Windows XP 上的应用程序清单指定要
    // 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
    //则需要 InitCommonControlsEx()。否则,将无法创建窗口。
    INITCOMMONCONTROLSEX InitCtrls;
    InitCtrls.dwSize = sizeof(InitCtrls);
    // 将它设置为包括所有要在应用程序中使用的
    // 公共控件类。
    InitCtrls.dwICC = ICC_WIN95_CLASSES;
    InitCommonControlsEx(&InitCtrls);

    CWinApp::InitInstance();

    // 初始化 OLE 库
    if (!AfxOleInit())
    {
        AfxMessageBox(IDP_OLE_INIT_FAILED);
        return FALSE;
    }

    AfxEnableControlContainer();

    EnableTaskbarInteraction(FALSE);

    // 使用 RichEdit 控件需要  AfxInitRichEdit2()
    // AfxInitRichEdit2();

    // 标准初始化
    // 如果未使用这些功能并希望减小
    // 最终可执行文件的大小,则应移除下列
    // 不需要的特定初始化例程
    // 更改用于存储设置的注册表项
    // TODO: 应适当修改该字符串,
    // 例如修改为公司或组织名
    SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
    LoadStdProfileSettings(4);  // 加载标准 INI 文件选项(包括 MRU)

    // 注册应用程序的文档模板。文档模板
    // 将用作文档、框架窗口和视图之间的连接
    CSingleDocTemplate* pDocTemplate;
    pDocTemplate = new CSingleDocTemplate(
        IDR_MAINFRAME,
        RUNTIME_CLASS(CMFC_DISCOVERDoc),
        RUNTIME_CLASS(CMainFrame),       // 主 SDI 框架窗口
        RUNTIME_CLASS(CMFC_DISCOVERView));
    if (!pDocTemplate)
        return FALSE;
    AddDocTemplate(pDocTemplate);

    // 分析标准 shell 命令、DDE、打开文件操作的命令行
    CCommandLineInfo cmdInfo;
    ParseCommandLine(cmdInfo);

    // 调度在命令行中指定的命令。如果
    // 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。
    if (!ProcessShellCommand(cmdInfo))
        return FALSE;

    // 唯一的一个窗口已初始化,因此显示它并对其进行更新
    m_pMainWnd->ShowWindow(SW_SHOW);
    m_pMainWnd->UpdateWindow();
    return TRUE;
}

不需要用完全看完,注意到在上面的代码中有这句:

if (!ProcessShellCommand(cmdInfo))
        return FALSE;

程序会通过上面的代码调用CMainFram::LoadFrame函数进行注册和创建窗口。

3.LoadFrame

该函数具体代码如下:


BOOL CFrameWnd::LoadFrame(UNIT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext)
{
/*主窗口的菜单、图标、加速键、及标题都以nIDResource标识。除创建窗口外,还要做许多工作,如设置帮助上下文ID、装入加速键、初始化子窗口。所以在文档/视图框架程序中,总是使用LoadFrame()创建主窗口。*/
ASSERT_VALID_IDR(nIDResource);
ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource);
m_nIDHelp = nIDResource; // ID for help context (+HID_BASE_RESOURCE)
CString strFullString;
if (strFullString.LoadString(nIDResource))
AfxEXtractSubString(m_strTitle, strFullString, 0); //取得窗口标题
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));//**装入图标,注册窗口类**
LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
LPCTSTR lpszTitle = m_strTitle;//调用CFrameWnd::Create()
if (!Create(lpszClass, lpszTitle, dwDefaultStyle, rectDefault, pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext))//**创建窗口**
return FALSE;//存储菜单句柄
ASSERT(m_hWnd != NULL);
m_hMenuDefault = ::GetMenu(m_hWnd);//装入加速键
LoadAccelTable(MAKEINTRESOURCE(nIDResource));
if (pContext == NULL) //初始化子窗口
SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);
return TRUE;
}

在上面的代码中,首先通过AfxEndDeferRegisterClass函数实现窗口注册,再调用Create函数实现窗口创建。该函数定义如下:

BOOL CFrameWnd::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, LPCTSTR lpszMenuName, DWORD dwExStyle, CCreateContext* pContext)
{
/*可见,参数列表与CWnd::Create()稍有不同。因为目的是创建主窗口,所以第6个参数要求菜单资源名*/
HMENU hMenu = NULL;
if (lpszMenuName != NULL)
{
//搜索包含该菜单资源的实例(当前进程或者按进行装入的DLL)
HINSTANCE hInst = AfxFindResourceHandl(lpszMenuName, RT_MENU);
//装入菜单资源
if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL)
{
TRACE0(“Warning: failed to load menu for CFrameWnd.\n”);
PostNcDestroy(); //perhaps delete to C++ object
return FALSE;
}
}
m_strTitle = lpszWindowName; //存储窗口标题,以备后用(如刷新显示)
// 调用CWnd::CreateEx()
if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle, rect.left, rect.top, rect.right – rect.left, rect,bottom – rect.top, pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))
{
if (hMenu != NULL)
DestroyMenu(hMenu); //如果创建失败,释放菜单资源
return FALSE;
}
return TRUE;
}

其中又调用了CreateEx函数来完成实际的创建工作,

4.CreateEx

BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
 LPCTSTR lpszWindowName, DWORD dwStyle,
 int x, int y, int nWidth, int nHeight,
 HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
 // allow modification of several common create parameters
 CREATESTRUCT cs;
 cs.dwExStyle = dwExStyle;
 cs.lpszClass = lpszClassName;
 cs.lpszName = lpszWindowName;
 cs.style = dwStyle;
 cs.x = x;
 cs.y = y;
 cs.cx = nWidth;
 cs.cy = nHeight;
 cs.hwndParent = hWndParent;
 cs.hMenu = nIDorHMenu;
 cs.hInstance = AfxGetInstanceHandle();
 cs.lpCreateParams = lpParam;

 if (!PreCreateWindow(cs))
 {
  PostNcDestroy();
  return FALSE;
 }

 AfxHookWindowCreate(this);//核心过程***********挂钩开始
 HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
   cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
   cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);

#ifdef _DEBUG
 if (hWnd == NULL)
 {
  TRACE1("Warning: Window creation failed: GetLastError returns 0x%8.8X/n",
   GetLastError());
 }
#endif

 if (!AfxUnhookWindowCreate())//挂钩结束
  PostNcDestroy();        // cleanup if CreateWindowEx fails too soon

 if (hWnd == NULL)
  return FALSE;
 ASSERT(hWnd == m_hWnd); // should have been set in send msg hook
 return TRUE;
}

我们知道创建窗口、显示窗口是需要响应WM_PAINT消息的,也就是调用窗口函数,从上面的代码,我们看不出调用了DefWindowProc函数的代码,它是隐藏在AfxHookWindowCreate函数之中,该函数源码如下:

void AFXAPI AfxHookWindowCreate(CWnd* pWnd)
{
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
if (pThreadState->m_pWndInit == pWnd)
   return;

if (pThreadState->m_hHookOldCbtFilter == NULL)
{
   pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,//设置CBT钩子
    _AfxCbtFilterHook/*回调函数*/, NULL, ::GetCurrentThreadId());
   if (pThreadState->m_hHookOldCbtFilter == NULL)
    AfxThrowMemoryException();
}
ASSERT(pThreadState->m_hHookOldCbtFilter != NULL);
ASSERT(pWnd != NULL);
ASSERT(pWnd->m_hWnd == NULL);   // only do once

ASSERT(pThreadState->m_pWndInit == NULL);   // hook not already in progress
pThreadState->m_pWndInit = pWnd;
}

在CreateEX源码中我们看到AfxHookWindowCreate函数是先于CreatWindowEx函数被调用的,CreateWindowEx是创建真正的窗口对象函数,AfxHookWindowCreate调用了SetWindowHookEx函数设置了钩子,这样有满足设置的消息时,系统就发送给设置函数。这样每次创建窗口时,该函数就将窗口函数修改为AfxWinProc函数,这是消息循环的起点。

这里我们发现创建了一个CBT钩子,回调函数为_AfxCbtFilterHook,这个函数有点长,我们来看一下(看有注释的地方即可)

LRESULT CALLBACK
_AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)
{
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
if (code != HCBT_CREATEWND)//专门用来钩创建窗口的钩子
{
   // wait for HCBT_CREATEWND just pass others on...
   return CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,
    wParam, lParam);
}

ASSERT(lParam != NULL);
LPCREATESTRUCT lpcs = ((LPCBT_CREATEWND)lParam)->lpcs;
ASSERT(lpcs != NULL);

CWnd* pWndInit = pThreadState->m_pWndInit;
BOOL bContextIsDLL = afxContextIsDLL;
if (pWndInit != NULL || (!(lpcs->style & WS_CHILD) && !bContextIsDLL))
{
   // Note: special check to avoid subclassing the IME window
   if (_afxDBCS)
   {
    // check for cheap CS_IME style first...
    if (GetClassLong((HWND)wParam, GCL_STYLE) & CS_IME)
     goto lCallNextHook;

    // get class name of the window that is being created
    LPCTSTR pszClassName;
    TCHAR szClassName[_countof("ime")+1];
    if (DWORD_PTR(lpcs->lpszClass) > 0xffff)
    {
     pszClassName = lpcs->lpszClass;
    }
    else
    {
     szClassName[0] = ‘\0‘;
     GlobalGetAtomName((ATOM)lpcs->lpszClass, szClassName, _countof(szClassName));
     pszClassName = szClassName;
    }

    // a little more expensive to test this way, but necessary...
    if (::AfxInvariantStrICmp(pszClassName, _T("ime")) == 0)
     goto lCallNextHook;
   }

   ASSERT(wParam != NULL); // should be non-NULL HWND
   HWND hWnd = (HWND)wParam;
   WNDPROC oldWndProc;//用于保存原window回调函数
   if (pWndInit != NULL)
   {
    AFX_MANAGE_STATE(pWndInit->m_pModuleState);

    // the window should not be in the permanent map at this time
    ASSERT(CWnd::FromHandlePermanent(hWnd) == NULL);

    // connect the HWND to pWndInit...
    pWndInit->Attach(hWnd);
    // allow other subclassing to occur first
    pWndInit->PreSubclassWindow();

    WNDPROC *pOldWndProc = pWndInit->GetSuperWndProcAddr();
    ASSERT(pOldWndProc != NULL);

    // subclass the window with standard AfxWndProc
    WNDPROC afxWndProc = AfxGetAfxWndProc();
    oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,
     (DWORD_PTR)afxWndProc);//设置新的回调函数afxWndProc
    ASSERT(oldWndProc != NULL);
    if (oldWndProc != afxWndProc)
     *pOldWndProc = oldWndProc;

    pThreadState->m_pWndInit = NULL;
   }
   else
   {
    ASSERT(!bContextIsDLL);   // should never get here

    static ATOM s_atomMenu = 0;
    bool bSubclass = true;   

    if (s_atomMenu == 0)
    {
     WNDCLASSEX wc;
     memset(&wc, 0, sizeof(WNDCLASSEX));
     wc.cbSize = sizeof(WNDCLASSEX);
     s_atomMenu = (ATOM)::AfxCtxGetClassInfoEx(NULL, _T("#32768"), &wc);
    }

    // Do not subclass menus.
    if (s_atomMenu != 0)
    {
     ATOM atomWnd = (ATOM)::GetClassLongPtr(hWnd, GCW_ATOM);
     if (atomWnd == s_atomMenu)
       bSubclass = false;
    }
    else
    {
     TCHAR szClassName[256];
     if (::GetClassName(hWnd, szClassName, 256))
     {
      szClassName[255] = NULL;
      if (_tcscmp(szClassName, _T("#32768")) == 0)
       bSubclass = false;
     }
    }
    if (bSubclass)
    {
     // subclass the window with the proc which does gray backgrounds
     oldWndProc = (WNDPROC)GetWindowLongPtr(hWnd, GWLP_WNDPROC);
     if (oldWndProc != NULL && GetProp(hWnd, _afxOldWndProc) == NULL)
     {
      SetProp(hWnd, _afxOldWndProc, oldWndProc);
      if ((WNDPROC)GetProp(hWnd, _afxOldWndProc) == oldWndProc)
      {
       GlobalAddAtom(_afxOldWndProc);
       SetWindowLongPtr(hWnd, GWLP_WNDPROC, (DWORD_PTR)_AfxActivationWndProc);
       ASSERT(oldWndProc != NULL);
      }
     }
    }
   }
}

lCallNextHook:
LRESULT lResult = CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,
   wParam, lParam);

#ifndef _AFXDLL
if (bContextIsDLL)
{
   ::UnhookWindowsHookEx(pThreadState->m_hHookOldCbtFilter);
   pThreadState->m_hHookOldCbtFilter = NULL;
}
#endif
return lResult;
}

接下来就可以准备进入消息循环了


5.消息循环

大家回到前面的AfxWinMain函数中可以看到有这么一行代码:

nReturnCode = pThread->Run();//******进入消息循环*********

Run函数的源码如下:

virtual int CWinThread::Run()
{
ASSERT_VALID(this);
//是否要做空闲处理
BOOL bIdle = TRUE;
//用户记录空闲处理已经连接执行的次数
LONG lIdleCount = 0;
//acquire and dispatch messages until a WM_QUIT message is received.
//消息循环
for (;;)
{
//如果空闲状态为真,且消息队列为空,则进行空闲处理
while(bIdle && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))
{
//PeekMessage()用于检查消息队列是否为空,但并不处理消息
//调用空闲处理程序,如果返回零,说明空闲处理已全部完成
    if (!OnIdle(lIdleCount++))
        bIdle = FALSE;
}
//空闲处理循环结束,进入消息泵循环
    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); //不可能执行的语句
}

可以看到进入消息循环后,由PumpMessage函数处理消息,是处理消息的关键,源码如下:

//省略了调试信息的输出功能
BOOL CWinThread::PumpMessage()
{
ASSERT_VALID(this);
//取出消息队列中的第一个消息,直到取得消息,该函数才返回
if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
{
//收到WM_QUIT消息
return FALSE;
}
//处理消息,但不处理WM_KICKIDLE
if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
{
//转换虚键消息到字符消息
::TranslateMessage(&m_msgCur);
//分发消息
::DispatchMessage(&m_msgCur);
}
return TRUE;
}
阅读PumpMessage代码可知,消息泵并不处理WM_KICKIDLE消息,收到该消息后,直接返回。其实,WM_KICKIDLE消息被用来刺激空闲处理的执行,它作为一个空消息促使::GetMessage()返回。
虽然Run()是虚拟函数,但很少被重载。

CWinThread类是MFC用来封装线程的,包括UI线程和工作者线程。因此每个MFC程序至少使用一个CWinThread派生类。

可见Run函数和PumpMessage函数实现了Win32程序中的WinProc函数的功能。

至此MFC的机制就基本讲完了,如果觉得没说清楚的可以去看《深入浅出MFC》前几章就好,那本书讲得很细。本文中的所有代码,都可以在百度上搜到,源码只需要看有注释的关键几行。不得不说Windows将这个机制隐藏的很深,对于初学者学习,真的不是很友好。它这么做必然有它的道理,我想这与它的系统有个很深的关系,MFC也不是一下子就搞出来的,微软也走过错路,有兴趣的读者可以取百度一下这个历史。

参考链接:

(http://www.cnblogs.com/lidabo/archive/2012/07/04/2576832.html)

http://www.cnblogs.com/chinazhangjie/archive/2011/09/19/2181954.html

http://blog.csdn.net/weiwenhp/article/details/8455471

http://www.cnblogs.com/liuweilinlin/archive/2012/08/16/2643272.html

http://blog.csdn.net/misskissc/article/details/8555105

时间: 2024-11-25 14:37:42

MFC内部机制探秘的相关文章

SQL Server 内存中OLTP内部机制概述(一)

----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory OLTP Internals Overview>:http://technet.microsoft.com/en-us/library/dn720242.aspx ----------------------------我是分割线------------------------------- SQL S

MYSQL的InnoDB Buffer Pool内部机制

1. 基本结构:INNODB用least recently used (LRU) 算法来管理他的buffer_pool. buffer_pool在内部被分隔为两个list. a young list 和 a old list. Young list 存储那些高频使用的缓存数据(默认占整个BUFFER的5/8) Old list 存储那些低频使用的数据(默认占整个BUFFER的3/8) 2.使用机制:当一个新块数据被从磁盘缓存到buffer当中,它默认被放在Old list的头部,即midpoin

MFC 六大机制 (2) RTTI(运行时类型识别)

RTTI(Runtime Type Identification,运行时类型识别) 程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型.MFC 早在编译器支持 RTTI 之前,就具有了这项能力.承接上一章,我们现在要在 Console 程序中将 RTTI 仿真出来.我希望我的类库具备 IsKindOf() 的能力,能够在执行器检查某个对象是否"属于某种类",并传回 TRUE 或 FALSE.为了更直观地查看结果,我在 IsKindOf() 中加入了输出,使其达到如

委托的内部机制

委托是一种定义方法签名的类型,是对方法的抽象.封装.与委托的签名(由返回类型和参数组成)匹配的任何可访问类和结构中的任何方法都可以分配给该委托,方法可以是静态方法,也可以是实例方法.将一个方法绑定到委托时,C#和CLR允许引用类型的协变性和逆变性. 协变性是指方法的返回类型可以派生自委托的返回类型. 逆变性是指委托的参数类型可以派生自方法的参数类型. 协变性和逆变性只能用于引用类型,不能用于值类类型或void.所以不能将下面的方法与委托绑定: delegate Object DelegateCa

搭建高可用mongodb集群(三)—— 深入副本集内部机制

http://www.lanceyan.com/tech/mongodb_repset2.html 在上一篇文章<搭建高可用mongodb集群(二)—— 副本集> 介绍了副本集的配置,这篇文章深入研究一下副本集的内部机制.还是带着副本集的问题来看吧! 副本集故障转移,主节点是如何选举的?能否手动干涉下架某一台主节点. 官方说副本集数量最好是奇数,为什么? mongodb副本集是如何同步的?如果同步不及时会出现什么情况?会不会出现不一致性? mongodb的故障转移会不会无故自动发生?什么条件会

mongodb副本集的内部机制(借鉴lanceyan.com)

针对mongodb的内部机制提出以下几个引导性的问题: 副本集故障转移,主节点是如何选举的?能否手动干涉下架某一台主节点. 官方说副本集数量最好是奇数,为什么? mongodb副本集是如何同步的?如果同步不及时会出现什么情况?会不会出现不一致性? mongodb的故障转移会不会无故自动发生?什么条件会触发?频繁触发可能会带来系统负载加重? Bully算法 mongodb副本集故障转移功能得益于它的选举机制.选举机制采用了Bully算法,可以很方便从分布式节点中选出主节点.一个分布式集群架构中一般

SQL Server 内存中OLTP内部机制概述(二)

----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory OLTP Internals Overview>:http://technet.microsoft.com/en-us/library/dn720242.aspx 译者水平有限,如有翻译不当之处,欢迎指正. ----------------------------我是分割线---------------

Android驱动学习-内部机制_回顾binder框架关键点

内部机制_回顾binder框架关键点server注册服务时, 对每个服务都提供不同的ptr/cookie,在驱动程序里对每个服务都构造一个binder_node, 它也含有ptr/cookie client使用服务前要先getService:会在驱动程序里对该服务构造一个binder_ref, binder_ref含有desc, node成员, desc是整数, node指向对应服务的binder_node 使用服务时, client构造数据,调用ioctl:数据里含有handle 驱动程序根据

SQL Server 内存中OLTP内部机制概述(三)

----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory OLTP Internals Overview>:http://technet.microsoft.com/en-us/library/dn720242.aspx 译者水平有限,如有翻译不当之处,欢迎指正. ----------------------------我是分割线---------------