MFC程序逆向 – 消息篇(上)

标 题: 【原创】MFC程序逆向 – 消息篇(上)+(下) 11楼
作 者: szdbg
时 间: 2007-10-31,06:26:02
链 接: http://bbs.pediy.com/showthread.php?t=54150

前言:
记得前一段时间,我刚接触软件破解和逆向这一行时,对于一些软件不知从何处跟踪按钮消息,试了好多方法,就是断不下来,在系统模块中经常转得晕头转向,而一无所获。

MFC程序是一种常见类型的程序,我静下心来,潜心研究了一下MFC消息流程。弄清原委之后,一切豁然开朗,发现跟踪MFC程序和消息处理原来是如此。。。,跟踪按钮事件处理也由此变得特别简单。

于是,我将这些研究整理成文,以备后忘。并希望对和我一样的菜鸟有所帮助,有误之处,请高手指正。

本文目的就是以一个MFC的标准对话框程序为例,同时从源码和反汇编代码两方面来研究MFC消息的流程走向,弄清MFC消息路径的所有站点,这样就可以任意定位MFC的所有消息事件,可以从任一站点切入,进行跟踪分析MFC的处理过程。甚至可以从PumpMessage大本营出发,一直全程跟踪,做到心中有数,不慌不乱。

关于对话框的启动过程,其过程很简单,程序进入WinMain函数之后,会调用对话框的DoModal函数,然后就进入RunModalLoop函数,消息循环在这里就开始了,限于篇幅,本文不作多说,有兴趣者可看看MFC源码。本篇重点在于分析MFC的消息分发处理的过程。
先看一下RunModalLoop函数部分源码:

int CWnd::RunModalLoop(DWORD dwFlags)
{  ...
  for (;;)
  {  ...
    do
    {  if (!AfxGetThread()->PumpMessage())    // pump message, but quit on WM_QUIT
      {
        AfxPostQuitMessage(0);
        return -1;
}
      ...
    } while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));
  }
  ...
}

这里,AfxGetThread()->PumpMessage()是MFC消息处理的大本营,MFC程序的所有消息就是从这里开始,经过重重路径转换,翻山越岭,中途直达Windows系统内核,再返回到MFC地界,又途经不少周折,才找到最终目的地 – 消息函数地址。可谓是山重水复疑无路,柳暗花明又一村。

一个按钮点击事件的过程如下:
CWinThread::PumpMessage -> CWnd::PretranslateMessage -> CWnd::WWalkPreTranslateMessate -> CD1Dlg::PreTranslateMessage -> CDialog::PreTranslateMessage -> CWnd::PreTranslateInput  -> CWnd::IsDialogMessageA -> USER32内核 -> AfxWndProcBase -> AfxWndProc -> AfxCallWndProc -> CWnd::WindowProc -> CWnd::OnWndMsg -> CWnd::OnCommand -> CDialog::OnCmdMsg -> CCmdTarget::OnCmdMsg -> _AfxDispatchCmdMsg -> CD1Dlg::OnButton1()

VC下,可以随手写一个标准的对话框程序,上面放一个按钮,点击按钮后,弹出一个消息框。我们现在就从PumpMessage()开始,来分析这中间的消息流程:

1. CWinThread::PumpMessage函数 (消息泵)
BOOL CWinThread::PumpMessage()
{     //GetMessage 当消息为WM_QUIT时,返回0,其它消息时,返回TRUE,有错误时,返回-1
   if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))  return FALSE;   
   if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
{
    ::TranslateMessage(&m_msgCur);
    ::DispatchMessage(&m_msgCur); 
}
   return TRUE;
}
PumpMessage只有在接收到WM_QUIT消息时,才返回FALSE,其它情况,返回TRUE。由于CWinThread::PumpMessage()函数负责从消息队列中获取消息、翻译消息以及分发消息等,因此习惯将此函数称之为“消息泵”。
在PumpMessage函数中,PreTranslateMessage函数至关重要,正是有了这个PreTranslateMessage(),才使得MFC能够灵活的控制消息的分发模式,可以说,PreTranslateMessage()就是MFC的实现消息分发模式的工具。

PumpMessage函数反汇编代码:
73D31194 >  56                    PUSH ESI
...
73D311A1    FF15 B0B6DC73         CALL DWORD PTR DS:[<&USER32.GetMessageA>]
73D311A7    85C0                  TEST EAX,EAX
73D311A9    74 26                 JE SHORT MFC42.73D311D1    ;收到WM_QUIT,退出程序
73D311AB    817E 38 6A030000      CMP DWORD PTR DS:[ESI+38],36A
73D311B2    74 1A                 JE SHORT MFC42.73D311CE
73D311B4    8B06                  MOV EAX,DWORD PTR DS:[ESI]
73D311B6    57                    PUSH EDI
73D311B7    8BCE                  MOV ECX,ESI
73D311B9    FF50 60               CALL DWORD PTR DS:[EAX+60]    ; PreTranslateMessage (消息预处理)
73D311BC    85C0                  TEST EAX,EAX
73D311BE    75 0E                 JNZ SHORT MFC42.73D311CE
73D311C0    57                    PUSH EDI          ;消息预处理返回FALSE
73D311C1    FF15 ACB6DC73         CALL DWORD PTR DS:[<&USER32.TranslateMessage>]
73D311C7    57                    PUSH EDI
73D311C8    FF15 30B6DC73         CALL DWORD PTR DS:[<&USER32.DispatchMessageA>]
;
73D311CE    6A 01                 PUSH 1            ;返回TRUE
73D311D0    58                    POP EAX
73D311D1    5F                    POP EDI
73D311D2    5E                    POP ESI
73D311D3    C3                    RETN

提示: 
a. OD加载程序后,调出MFC42.dll模块,定位到PumpMessage代码入口处。
b. 在CALL DWORD PTR DS:[EAX+60]这一条语句上设置条件断点[[esp]+4]==202,即可设置鼠标左键释放断点。
   说明:call [eax+60]是调用PreTranslateMessage函数,入口参数为:MSG* pMsg,所以:
   [esp]就是pMsg,而[[esp]]就是pMsg->hWnd  , [[esp]+4]就是pMsg->Message  
c. [[esp]]==002407B4 && [[esp]+4]==202 可以为指定按钮设置点击断点。这里002407B4是目标按钮的句柄.

2. CWinThread::PreTranslateMessage函数
BOOL CWinThread::PreTranslateMessage(MSG* pMsg)
{  // if this is a thread-message, short-circuit this function
  if (pMsg->hwnd == NULL && DispatchThreadMessageEx(pMsg))    return TRUE;
  CWnd* pMainWnd = AfxGetMainWnd();
  // 通过WalkPreTranslateTree 进行消息分发
  if (CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg))  return TRUE;  // 消息分发处理关键
  if (pMainWnd != NULL)
  {
     CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd);
     if (pWnd->GetTopLevelParent() != pMainWnd)
      return pMainWnd->PreTranslateMessage(pMsg);  //程序主框架处理消息
  }
  return FALSE;   // no special processing
}

PreTranslateMessage函数反汇编部分代码   
73D313D0 >   PUSH ESI
73D313D1     PUSH EDI
73D313D2     MOV EDI,DWORD PTR SS:[ESP+C]
73D313D6     CMP DWORD PTR DS:[EDI],0
73D313D9     JE MFC42.73D8E9A1
73D313DF     CALL MFC42.#[email protected]@YGPAVCW>
73D313E4     MOV ESI,EAX
73D313E6     TEST ESI,ESI
73D313E8     JE SHORT MFC42.73D313ED
73D313EA     MOV EAX,DWORD PTR DS:[ESI+20]
73D313ED     PUSH EDI
73D313EE     PUSH EAX
73D313EF     CALL MFC42.#[email protected]>
73D313F4     TEST EAX,EAX
73D313F6     JNZ SHORT MFC42.73D31415
73D313F8 >   TEST ESI,ESI
73D313FA     JE SHORT MFC42.73D3140E
73D313FC     PUSH DWORD PTR DS:[EDI]
73D313FE     CALL MFC42.#[email protected]@@SGPAV>
73D31403     MOV ECX,EAX
73D31405     CALL MFC42.#[email protected]>
73D3140A     CMP EAX,ESI
73D3140C     JNZ SHORT MFC42.73D3141A
73D3140E     XOR EAX,EAX
73D31410     POP EDI
73D31411     POP ESI
73D31412     RETN 4
提示: 
a. OD加载程序后,调出MFC42.dll模块,定位到PreTranslateMessage代码入口处。
b. 在函数入口处设置条件断点[[esp+4]+4]==202,即可设置鼠标左键释放断点。
   说明:此函数的入口参数为:MSG* pMsg,在入口处时,[esp]是函数返回地址,所以:
   [esp+4]就是pMsg,而[[esp+4]]就是pMsg->hWnd  , [[esp+4]+4]就是pMsg->Message  
c. [[esp+4]]==002407B4 && [[esp+4]+4]==202 可以为指定按钮设置点击断点。这里002407B4是目标按钮的句柄.

3. CWnd::WalkPreTranslateTree函数
CWnd::WalkPreTranslateTree()的所使用的策略很简单,拥有该消息窗口最先获得该消息的处理权,如果它不想对该消息进行处理(该窗口对象的PreTranslateMessage()函数返回FALSE),就将处理权交给它的父亲窗口,如此向树的根部遍历,直到遇到hWndStop(在CWinThread::PreTranslateMessage()中,hWndStop表示的是线程主窗口的句柄)。
记住这个消息处理权的传递方向,是由树的某个一般节点或叶子节点向树的根部传递!

BOOL PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg)
{  for (HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd))  //从当前窗口到父窗口,逐层往上
  {  CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
    if (pWnd != NULL)
    {  // target window is a C++ window
      if (pWnd->PreTranslateMessage(pMsg))  return TRUE;  //消息被某一窗口处理了,返回
    }    
    if (hWnd == hWndStop)  break;    // got to hWndStop window without interest
  }
  return FALSE;       // no special processing

正是这个if (pWnd->PreTranslateMessage(pMsg))  return TRUE; 才实现了MFC灵活的消息分发处理机制。MFC程序各个窗口类中重载的PreTranslateMessage虚函数,都是从这里进来的。
MFC从当前消息窗口类逐级向上搜索执行各个类的PreTranslateMessage函数,只要有一个PreTranslateMessage函数
返回TRUE,WalkPreTranslateTree就中止搜索,并返回TRUE,否则返回FALSE。
   在PumpMessage函数中最终就是根据WalkPreTranslateTree函数的返回值决定是否要由Windows系统进行消息处理与分发。

WalkPreTranslateTree函数反汇编代码如下:
73D31389  MOV EDI,EDI                                        ;  D1.0040308C
73D3138B  PUSH ESI
73D3138C  PUSH EDI
73D3138D  MOV EDI,DWORD PTR SS:[ESP+10]
73D31391  MOV ESI,DWORD PTR DS:[EDI]
73D31393  JMP SHORT MFC42.73D313BD
73D31395  /PUSH ESI
73D31396  |CALL MFC42.#[email protected]@@SGPAV>        ;取得CWnd 指针值
73D3139B  |TEST EAX,EAX              ;CWnd * 值不为NULL时,则调用CWnd::PreTranslateMessage()
73D3139D  |JE SHORT MFC42.73D313AE
73D3139F  |MOV EDX,DWORD PTR DS:[EAX]        
73D313A1  |PUSH EDI
73D313A2  |MOV ECX,EAX
73D313A4  |CALL DWORD PTR DS:[EDX+98]  ;<JMP.&MFC42.#5280_?PreTranslateMessage > ;通过虚函数方式调用
73D313AA  |TEST EAX,EAX
73D313AC  |JNZ SHORT MFC42.73D313C8        ;消息被处理了,返回TRUE
73D313AE  |CMP ESI,DWORD PTR SS:[ESP+C]
73D313B2  |JE SHORT MFC42.73D313C1
73D313B4  |PUSH ESI                                          ; /hWnd
73D313B5  |CALL DWORD PTR DS:[<&USER32.GetParent>]           ; \GetParent
73D313BB  |MOV ESI,EAX
73D313BD   TEST ESI,ESI
73D313BF  \JNZ SHORT MFC42.73D31395
73D313C1  XOR EAX,EAX              ;返回FALSE
73D313C3  POP EDI
73D313C4  POP ESI
73D313C5  RETN 8
73D313C8  XOR EAX,EAX
73D313CA  INC EAX                ;返回 TRUE
73D313CB  JMP SHORT MFC42.73D313C3
跟踪说明:
在上面一句73D313A4  CALL DWORD PTR DS:[EDX+98] 设置按钮点击条件断点: [[esp]]==002407B4 && [[esp]+4]==202
可以发现:当点击按钮后,按钮点击事件函数代码就会在这条语句后执行,当按钮事件函数代码执行完毕后,CALL才会返回TRUE。

4. CD1Dlg::PreTranslateMessage函数
BOOL CDialog::PreTranslateMessage(MSG* pMsg)
{    ...
  return CDialog::PreTranslateMessage(pMsg);
 }
若接收消息的窗口类重载了PreTranslateMessage函数,则此时会调用它,否则就进入第5步。实际应用中,这里很有可能是消息流程的一个分水岭,可能走向两条不同的道路。这完全取决于应用程序新增的代码,若应用程序在这里返回TRUE,消息流程就返回去了。否则,就会继续往下执行。
    在跟踪按钮消息时,此处应作为一个注意点,而设置断点的最佳位置是在上一步WalkPreTranslateTree函数中所说的位置,跟踪下来,注意消息流程的走向。

5. CDialog::PreTranslateMessage函数
BOOL CDialog::PreTranslateMessage(MSG* pMsg)
{
  if (CWnd::PreTranslateMessage(pMsg)) return TRUE;
  CFrameWnd* pFrameWnd = GetTopLevelFrame();
  if (pFrameWnd != NULL && pFrameWnd->m_bHelpMode) return FALSE;
  if (pMsg->message == WM_KEYDOWN && (pMsg->wParam == VK_ESCAPE || pMsg->wParam == VK_CANCEL) &&
     (::GetWindowLong(pMsg->hwnd, GWL_STYLE) & ES_MULTILINE) &&_AfxCompareClassName(pMsg->hwnd, _T("Edit")))
  {
    HWND hItem = ::GetDlgItem(m_hWnd, IDCANCEL);
    if (hItem == NULL || ::IsWindowEnabled(hItem))
    {
      SendMessage(WM_COMMAND, IDCANCEL, 0);
      return TRUE;
    }
  }
  return PreTranslateInput(pMsg);      // 消息流入此处
}

CDialog::PreTranslateMessage()反汇编代码如下:
73D468A4 >  PUSH ESI
73D468A5    PUSH EDI
73D468A6    MOV EDI,DWORD PTR SS:[ESP+C]
73D468AA    MOV ESI,ECX
73D468AC    PUSH EDI
73D468AD    CALL MFC42.#[email protected]@@[email protected]>
73D468B2    TEST EAX,EAX
73D468B4    JNZ MFC42.73D8D490
73D468BA    MOV ECX,ESI
73D468BC    CALL MFC42.#[email protected]@@[email protected]@>
73D468C1    TEST EAX,EAX
73D468C3    JNZ MFC42.73D8D429
73D468C9    CMP DWORD PTR DS:[EDI+4],100
73D468D0    JE SHORT MFC42.73D468DF
73D468D2    PUSH EDI
73D468D3    MOV ECX,ESI
73D468D5    CALL MFC42.#[email protected]@@[email protected]@@>     ;消息从流入此处
73D468DA    POP EDI
73D468DB    POP ESI
73D468DC    RETN 4

6. CWnd::PreTranslateInput函数
BOOL CWnd::PreTranslateInput(LPMSG lpMsg)
{
  if ((lpMsg->message < WM_KEYFIRST || lpMsg->message > WM_KEYLAST) &&
    (lpMsg->message < WM_MOUSEFIRST || lpMsg->message > WM_MOUSELAST))  // 过滤消息
    return FALSE;
  return IsDialogMessage(lpMsg);
}
从源码中可以看出,这个函数是对消息进行过滤,对于按键消息和鼠标消息,直接返回FALSE,然后再返回到PumpMessge函数中,调用TranslageMessage()和DispatchMessage()函数,进行消息转换和分发,再进入MFC。对于其它消息,则调用CWnd::IsDialogMessage()函数进行下一步处理。
CWnd::PreTranslateInput()函数反汇编代码如下:
73D34009 >  MOV EDX,DWORD PTR SS:[ESP+4]
73D3400D    MOV EAX,DWORD PTR DS:[EDX+4]
73D34010    CMP EAX,100
73D34015    JNB SHORT MFC42.73D34023
73D34017    CMP EAX,200
73D3401C    JNB SHORT MFC42.73D34032
73D3401E    XOR EAX,EAX
73D34020    RETN 4
73D34023    CMP EAX,108
73D34028  ^ JA SHORT MFC42.73D34017
73D3402A    PUSH EDX
73D3402B    CALL MFC42.#[email protected]@@[email protected]@@Z
73D34030  ^ JMP SHORT MFC42.73D34020
73D34032    CMP EAX,209
73D34037  ^ JBE SHORT MFC42.73D3402A
73D34039  ^ JMP SHORT MFC42.73D3401E
7. CWnd::IsDialogMessageA函数
BOOL CWnd::IsDialogMessage(LPMSG lpMsg)
{
  if (m_nFlags & WF_OLECTLCONTAINER)
    return afxOccManager->IsDialogMessage(this, lpMsg);
  else
    return ::IsDialogMessage(m_hWnd, lpMsg);
}
这里会转进User32.IsDialogMessageA函数,从而转入系统内核,由Windows系统再来负责将消息的分发传送到各个目标窗口。
注:User32.IsDialogMessage并不是象它的名字那样用来检查对话框消息的,而是用来解释或转换消息的。更贴切的名字应该是TranslateDialogMessage。CWnd::IsDialogMessage实际上是一个以LPMSG作为参数,再加上内部的m_hWnd参数来调用User32.IsDialogMessage的打包函数。这样,MFC中每一个对话框都会解释自己的输入。所以,若同时运行五个对话框,每一个对话框的PreTranslateMessage都会自动调用User32.IsDialogMessage,而且运转良好,完全可以不用我们编程处理,MFC真是太牛了。

CWnd::IsDialogMessageA函数反汇编代码:
73D468F5 > PUSH ESI
73D468F6   MOV ESI,ECX
73D468F8   TEST BYTE PTR DS:[ESI+29],1
73D468FC   JNZ MFC42.73D8E273
73D46902   PUSH DWORD PTR SS:[ESP+8]
73D46906   PUSH DWORD PTR DS:[ESI+20]
73D46909   CALL DWORD PTR DS:[<&USER32.IsDialogMessageA>]      ;进入系统内核
73D4690F   POP ESI
73D46910   RETN 4
提示: 
1 . OD中设断: bp IsDialogMessageA MSG==202 , 则当鼠标左键释放时,会中断在User32.IsDialogMessageA函数入口上。
2.  若已知按钮的句柄,且要求当点击该按钮时,程序中断在IsDialogMessageA上,则可以作如下设断: 
bp IsDialogMessageA [[esp+8]]==00060350 && MSG==202.
  3. 中断后,可以通过堆栈返回到CWnd::IsDialogMessageA函数代码处。
=========================================================================================================
8. User32 内核处理,不分析
这里面的过程,我们就当作一个黑匣子吧,不管它,一般情况下,也无需管它。因为我们百分之百相信它。
=========================================================================================================
当消息到达此处时,又进入了MFC地界 .第8步之前,可以说是经常峰回路转,山重水复。第8步之后,是柳暗花明,可以一路高歌,直奔目的地了。

欲知后事如何,且听下回分解!

时间: 2024-11-14 22:12:51

MFC程序逆向 – 消息篇(上)的相关文章

MFC程序逆向 – 消息篇(下)

上篇啰里啰嗦地说了一大堆,其实所说的消息都是PostMessage方式的.MFC中还有另外一种很常见的消息发送方式,就是SendMessage函数.这个消息起始路径和上篇所讲的完全不一样.这种方式下,前面的7个站点均不执行,而是直接进入第8站点:User32内核,从第8站点出来后,这两种消息方式走上了同一条道路,进入第9个站点或第10个站点了,真是殊道同归.对于MFC窗口程序,所有窗口都使用同一窗口过程 : AfxWndProcBase(第9个站点)或AfxWndProc(第10个站点).如果程

MFC程序中消息以及函数的处理顺序简介[转]

MFC应用程序中处理消息的顺序 1.AfxWndProc()      该函数负责接收消息,找到消息所属的CWnd对象,然后调用AfxCallWndProc 2.AfxCallWndProc()  该函数负责保存消息(保存的内容主要是消息标识符和消息参数)供应用程序以后使用,                    然后调用WindowProc()函数 3.WindowProc()      该函数负责发送消息到OnWndMsg()函数,如果未被处理,则调用DefWindowProc()函数 4.

VS2013生成Release版本MFC程序在其他机器上运行

对于自己机器安装了VS开发环境,生成MFC的exe文件能够在自己机器上运行,复制到其他目标机器可能出现不能运行的情况.下面就个人经历将发布的两中情况简要说明. 1.工程属性中:配置属性-常规,MFC使用类型选择“在共享的DLL中使用MFC”:C/C++ - 代码生成-MFC的使用 选择“多线程DLL(/MD)”这种情况下,若直接将exe文件放到目标机器运行,则需要目标机器安装相应的开发平台.或者将生成exe程序的依赖dll文件复制到目标机器的程序运行目录下:若用到第三方库,也要复制相应的dll文

MFC逆向-消息响应函数的定位

MFC  ==   Microsoft Foundation Class,微软基础类库,他封装了Windows API以便用户更快速的开发界面功能程序然而该库及其庞大而复杂,需要有C++的功底否则很难解决bug,逆向起来也是需要一定技巧.本人曾总结过Windows消息大全,他截取自winuser.h  commctrl.h,如果将这些消息以及通知码(LVN_??这样的)总结一下,对分析很有好处:#define WM_NULL                                   

深入跟踪MFC程序的执行流程

来源: http://blog.csdn.net/ljianhui/article/details/8781991 在MFC程序设计的学习过程中最令人感到难受,甚至于有时会动摇学习者信心的就是一种对于程序的一切细节都没有控制权的感觉.这种感觉来源于学习者不知道一个MFC程序是如何运行起来的(即一个MFC程序的执行流程)和MFC程序的设计思想和机制,即使是写过Windows程序的学习者,也会感到非常迷惘并且无从下手.而这种感觉的出现会使大家认为自己离开了书本上的例子就无法设计编制程序.下面我就来说

VB程序逆向反汇编常见的函数(修改版)

VB程序逆向常用的函数 1) 数据类型转换: a) __vbaI2Str    将一个字符串转为8 位(1个字节)的数值形式(范围在 0 至 255 之间) 或2 个字节的数值形式(范围在 -32,768 到 32,767 之间). b)__vbaI4Str   将一个字符串转为长整型(4个字节)的数值形式(范围从-2,147,483,6482,147,483,647) c)__vbar4Str  将一个字符串转为单精度单精度浮点型(4个字节)的数值形式 d)__vbar8Str   将一个字符

MFC 程序入口和执行流程

一 MFC程序执行过程剖析 1)我们知道在WIN32API程序当中,程序的入口为WinMain函数,在这个函数当中我们完成注册窗口类,创建窗口,进入消息循环,最后由操作系统根据发送到程序窗口的消息调用程序的窗口函数.而在MFC程序当中我们不在能找到类似WinMain这样的程序入口,取而代之的是一系列派生类的声明和定义以及一个冲CWinApp类派生而来的类的全局对象.CWinApp类被称之为应用程序对象,在一个MFC程序当中只允许有一个应用程序对象.由于CWinApp的派生对象是全局的,因此这个对

VS2010+WinXP+MFC程序 无法定位程序输入点于动态链接库

1.问题描述 原开发环境:Win7 64位旗舰版,VS2010,ThinkPad T460 出现问题:自己开发的MFC程序在WinXP环境下无法正常运行,弹框"无法定位程序输入点InitializeConditionVariable于动态链接库kernel32.dll" 重新搭建开发环境:WinXP SP3 专业版,VS2010,GIGABYTE某motherboard(原谅办公室没别的空闲主机了) 问题依旧存在:重新编译通过,但运行时还是弹框"无法定位程序输入点Initia

【笔记】《深入浅出MFC》第6章 MFC程序的生死因果

一.头文件说明 STDAFX.H 这个文件用来作为Precompile header file,其内只是载入其他的MFC头文件.应用程序通常会准备自己的头STDAFX.H. AFXWIN.H 每一个Windows MFC程序都必须载入它,因为它以及它所载入的文件声明了所有的MFC类. 在WINDEF.H中有CALLBACK的定义 #define CALLBACK _stdcall //是一种函数调用习惯 在AFXWIN.H中有afx_msg的定义 #define afx_msg   //故意安排