友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
富士康小说网 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

深入浅出MFC第2版(PDF格式)-第68部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!





    据图8…3 的数据结构,Scribble 程序的文件读写动作是这么分工的: 



     Framework 调用CSribbleDoc::Serialize,用以对付文件。 



      CScribbleDoc 再往下调用CStroke::Serialize,用以对付线条。 



        

       CStroke 再往下调用CArray::Serialize,用以对付点数组。 

         



     读也由它,写也由它,究竟Serialize 是读还是写?这一点不必我们操心。Framework 呼 

        



     Serialize 时会传来一个CArchive 对象(稍后我会解释CArchive),你可以想象它 



     代表一个文件,透过其IsStoring 成员函数,即可知道究竟要读还是写。图8…5 是各层 



     叫 

     级的Serialize 动作示意图,文字说明已在图片之中。 



      注意:Scribble 程序使用CArray 储存鼠标位置坐标,而CArray 是 



      一个template class ,解释起来比较复杂。所以稍后我挖给各位看的Serialize  函数原始 



      码,采用CDWordArray 的成员函数而非CArray 的成员函数。Visual C++ 1。5 版的 



      Scribble 范例程序就是使用CDWordArray           (彼时还未有template class )。 



      然而,为求完备,我还是在此先把CArray 的Serialize  函数源代码列出: 



     template 

    void AFXAPI SerializeElements (CArchive& ar; TYPE* pElements; int nCount) 

     { 

            ASSERT(nCount == 0 || 



                                                                                              501 


…………………………………………………………Page 564……………………………………………………………

                      第篇    深入  MFC  程式設計 



                                      AfxIsValidAddress(pElements; nCount * sizeof(TYPE))); 



                              // default is bit…wise read/write 

                              if (ar。IsStoring()) 

                                      ar。Write ((void*)pElements; nCount * sizeof(TYPE)); 

                              else 

                                      ar。Read ((void*)pElements; nCount * sizeof(TYPE)); 

                      } 



                      template 

                      void CArray::Serialize(CArchive& ar) 

                      { 

                              ASSERT_VALID(this); 

                              CObject::Serialize(ar); 

                              if (ar。IsStoring()) 

                              { 

                                      ar。WriteCount (m_nSize); 

                              } 

                              else 

                              { 

                                      DWORD nOldSize = ar。ReadCount (); 

                                      SetSize (nOldSize; …1); 

                              } 

                              SerializeElements (ar; m_pData; m_nSize); 

                      } 



                       void CScribbleDoc::Serialize(CArchive& ar) 

                                                                         2 CObList::Serialize(Carchive& ar) 

                       {                                                    { 。。。 }  //源代码请看图8…5b 

                               if (ar。IsStoring()) 

                                   {    } 

                                                                                 m_strokeList 是 

                               else 

                                   {    }                                          CObList  对象 

                             1 

                               m_strokeList。Serialize(ar); 

                       } 

                       //…………………………………………………………………………………………………………                到   的过程并非显而易见, 

                                                                                     

                       void CStroke::Serialize(CArchive& ar) 3                       下一节将有详细说明。 

                       { 

                               if (ar。IsStoring()) { 

                                   ar 》 w;                              5 CDWordArray::Serialize(Carchive& ar) 

                                   m_nPenWidth = w;                         { 。。。 }  //源代码请看图8…5c 

                                   m_pointArray。Serialize(ar); 

                               } 

                       } 



                             图8…5a Scribble Step1 的文件读写 (档)动作 



502 


…………………………………………………………Page 565……………………………………………………………

                                                       第8章    Document…View  深入探討 



void CObList::Serialize(CArchive& ar) 

{ 

    ASSERT_VALID(this); 

    CObject::Serialize(ar); 

    if (ar。IsStoring()) 

    { 

        ar。WriteCount(m_nCount); 

        for (CNode* pNode = m_pNodeHead; pNode != NULL; 

             pNode = pNode…》pNext) 

        {    // J。J。Hou :针对串行中的每一个元素写文件 



            ASSERT(AfxIsValidAddress(pNode; sizeof(CNode))); 

            ar data; 

        } 

                                               这将引发CArchive 的多载 

    } 

    else                                    else 运算子,稍后有深入说明。 

    { 

        DWORD nNewCount = ar。ReadCount(); 

        CObject* newData; 

        while  (nNewCount……) 

        {   // 

              J。J。Hou : 一一读入文件内容,加入串行 

            ar 》》 newData; 

            AddTail(newData); 

        } 

    } 

} 



                     图8…5b CObList::Serialize 源代码 



void CDWordArray::Serialize(CArchive& ar) 

{ 

    ASSERT_VALID(this); 

    CObject::Serialize(ar); 

    if (ar。IsStoring()) 

    { 

        ar。WriteCount(m_nSize);             //  

                                                  把数组大小(元素个数)写入ar 

        ar。Write(m_pData; m_nSize * sizeof(DWORD));  //把整个数组写入ar 

    } 

    else 

    { 

        DWORD nOldSize = ar。ReadCount(); 

        SetSize(nOldSize);                    // 从 ar 中读出数组大小(元素个数) 

        ar。Read(m_pData; m_nSize * sizeof(DWORD));   //  ar ar ar  

                                                              从     中读出整个数组 

    } 

} 



                     图8…5c CDWordArray::Serialize 源代码 



                                                                                            503 


…………………………………………………………Page 566……………………………………………………………

                 第篇    深入  MFC  程式設計 



                            File/Save 

                           【      】 



                  CScribbleDoc 要求线条串行CObList 储存它自己 



                  线条串行要求每个元素(也就是线条CStroke)储存它自己 



                 经过多载运算子、虚拟函数等机制的运作;调用到CStroke::Serialize 



                  CStroke 储存笔宽;并要求CArray 数组储存它自己 



                                    图8…5a则是以CDWordArray 为例来做解说(为了简化) 



                  CArray::Serialize 将一个个CPoint写入磁盘中  



                             Disk 



                    图8…5d Scribble Document 的Serialize 动作细部分解。 



               实际看看储存在磁盘中的。SCB 文件内容,对Serialize 将会有深刻的体会。图8…6a 是 



               使用者在Scribble Step1 程序的绘图画面及存盘内容(以Turbo Dump 观察获得),图8



               6b 是文件内容的解释。我们必须了解隐藏在MFC 机制中的serialization 细部动作,才 



               能清楚这些二进制数据的产生原由。如果你认为看倾印码(dump code )是件令人头晕的 



               事情,那么你会错失许多美丽事物。真的,倾印码使我们了解许多深层结构。 



                我在Scribble 中作画并存盘。为了突显笔宽的不同,我用了第10 章的Step3 版本,该版 



                本的Document 格式与Step1 的相同,但允许使用者设定笔宽。图8…6a 第一条线条的笔 



                宽是2 ,第二条是5,第三条是10,第四条是20 。文件储存于PENWIDTH。SCB 文件中。 



504 


…………………………………………………………Page 567……………………………………………………………

                                                      第8章    Document…View  深入探討 



                                                               其实STEP1 并不能够改变 



                                                               笔宽(但数据结构中已有笔 



                                                               宽的考量),STEP3 才可 



                                                               以改变笔宽。 



CArchive::WriteObject 

                                                              CArchive::ReadObject 



 Turbo Dump  Version 3。1 Copyright (c) 1988; 1992 Borland International 

                    Display of File PENWIDTH。SCB 



 000000:              01 00 07 00  43 53 74 72 6F 6B 65 02 。。。。。。。。CStroke。 

         04 00 FF FF 

 000010: 00 02 00 19 00 00 00 16  00 00 00 19 00 00 00 16 。。。。。。。。。。。。。。。。 

 000020: 00 00 00 01 80 05 00 03  00 18 00 00 00 2B 00 00 。。。。。。。。。。。。。+。。 

 000030: 00 18 00 00 00 2C 00 00  00 18 00 00 00 2C 00 00 。。。。。;。。。。。。。;。。 

 000040: 00 01 80 0A 00 02 00 18  00 00 00 48 00 00 00 18 。。。。。。。。。。。H。。。。 

 000050: 00 00 00 48 00 00 00 01  80 14 00 02 00 18 00 00 。。。H。。。。。。。。。。。。 

 000060: 00 64 00 00 00 18 00 00  00 64 00 00 00          。d。。。。。。。d。。。 



   图8…6a 在Scribble 中作画并存盘。PENWIDTH。SCB 文件全长109 个字节。 



 数值(hex)                       说明 



 0004                      表示此文件有四个CObList 元素。 



 FFFF                                     FFFF 亦即…1,表示New Class Tag  (稍后详述)。既然是新类别, 



                         就得记录一些相关信息(版本号码和类别名称) 



 0001                                      这是Schema no。,代表对象的版本号码。此数值由 



                         IMPLEMENT_SERIAL 宏的第三个参数指定。 



 0007                                     表示后面接着的「类别名称」有7 个字符。 



 43 53 74 72 6F 6B 65         〃CStroke〃  (类别名称)的ASCII 码。 



 0002                                     第一条线条的宽度。 



 0002                                     第一条线条的点数组大小(点数)。 



 00000019;00000016       第一条线条的第一个点坐标(CPoint 对象)。 



 00000019;00000016       第一条线条的第二个点坐标(CPoint 对象)。 



                                                                                          505 


…………………………………………………………Page 568……………………………………………………………

               第篇    深入  MFC  程式設計 



                数值(hex)                 说明 



              8001             这是(wOldClassTag | nClassIndex )的组合结果,表示接下来的对象 



                               仍旧使用旧类别(稍后详述) 



              0005             第二条线条的宽度。 



              0003             第二条线条的点数组大小(点数)。 



                00000018;0000002B  第二条线条的第一个点坐标(CPoint 对象)。 



                00000018;0000002C  第二条线条的第二个点坐标(CPoint 对象)。 



                00000018;0000002C  第二条线条的第三个点坐标(CPoint 对象)。 



              8001             表示接下来的对象仍旧使用旧类别。 



                000A           第三条线条的宽度。 



              0002             第三条线条的点数组大小(点数)。 



                00000018;00000048  第三条线条的第一个点坐标(CPoint 对象)。 



                00000018;00000048  第三条线条的第二个点坐标(CPoint 对象)。 



              8001            表示接下来的对象仍旧使用旧类别。 



              0014            第四条线条的宽度。 



              0002            第四条线条的点数组大小(点数) 。 



                00000018;00000064  第四条线条的第一个点坐标(CPoint 对象)。 



                00000018;00000064  第四条线条的第二个点坐标(CPoint 对象)。 



                图8…6b PENWIDTH。SCB 文件内容剖析。别忘了Intel 采用〃little…endian〃 



                      字节排列方式,每一个字组的前后字节系颠倒放置。 



506 


…………………………………………………………Page 569……………………………………………………………

                                          第8章    Document…View  深入探討 



台面下的Serialize 写档奥秘 



    你属于打破砂锅问到底,不到黄河心不死那一型吗?我会满足你的好奇心。 



    从应用程序代码的层面来看,关于文件的读写,我们有许多环节无法打通,类别的层层呼 



     叫动作似乎有几个缺口,而图8…6a 文件档倾印码中神秘的FF FF 01 00 07 00 43 53 74 72 



    6F 6B 65 也暧昧难明。现在让我来抽丝剥茧。 



    在挖宝过程之中,我们当然需要一些工具。我不选用昂贵的电钻、空压机或怪手(因为 



    你可能没有),我只选用简单的鹤嘴锄和铲子:一个文字搜寻工具,一个文件倾印工具, 



    一个Visual C++  内含的除错器。 



     ■  GREP。 :UNIX 上赫赫有名的文字搜寻工具,Borland C++ 编译器套件附 



       了一个DOS 版。此工具可以为我们搜寻文件中是否有特定字符串。PC Tools 也 



      有这种功能,但PC Tools 属于重量级装备,不符合我的选角要求。GREP  的 



      使用方式如下: 



       E:MSDEVMFCSRC》 grep …d Serialize *。cpp  



                                                   搜寻对象 



                                      欲搜寻之字符串(如果中有空白, 

                                      可用双含号整个括起来) 



                                      …d 表示子目录一并搜寻(此为选项) 



    ■  TDUMP。EXE:Turbo Dump ,Borland C++ 所附工具,可将任何文件以16 进位 



      码显示。使用方式如下: 



       C:》 tdump penwidth。scb  (输出结果将送往屏幕) 



     或 



       C:》 tdump penwidth。scb 》 filename (输出结果将送往文件) 



    ■  Visual C++  除错器:我已在第4章介绍过这个除错器。我假设你已经懂得如 



      何设定断点、观察变量值,并以Go 、Step Into、Step Over、Step Out 、Step to 



      Cursor 进行除错。这里我要补充的是如何观察〃Call Stack〃。 



                                                                   507 


…………………………………………………………Page 570……………………………………………………………

               第篇    深入  MFC  程式設計 



                如果我把断点设在CScribbleDoc::OnOpenDocument 函数中的第一行, 



                 然后以Go 进入除错程序,当我在Scribble 中打开一份文件(首先面对一个对话框, 



                 然后指定文件名),程序停留在断点上,然后我选按【View/Call Stack 】,出现【Call 



                 Stack】窗口,把断点之前所有未结束的函数列出来。这份资料可以帮助我们挖掘 



                 MFC 。 



                 好,图8…5a 的函数流程使图8…6a 的文件档倾印码曙光乍现,但是其中有些关节仍还模 



                 模糊糊,旋明旋暗。那完全是因为CObList 在处理每一个元素(一个CObject 衍生类别 



                 之对象实体)的文件动作时,有许多幕后的、不易观察到的机制。让我们从使用者按下 



                 【Save As】菜�
返回目录 上一页 下一页 回到顶部 9 10
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!