msgbartop
DoneVII CET & CPPLITE
msgbarbottom

17 May 09 在BREW程序中调用另一个mod的分析

近日读到这样一段程序。可以在程序运行时调用另一个文件并执行。这样可以做到应用自升级,压缩程序文件等实用的功能。

 

 要想理解这点,先看一下正常的BREW程序加载过程。

 BREW程序的入口,即相当于程序的main,是位于AEEModGen.中的AEEMod_Load函数。这可以从mak文件的连接选项中看出来。

 

LINK_ORDER = -first AEEMod_Load

 

 这个选项使得链接程序将该函数放在程序文件的0地址处。函数的定义如下:

 

int AEEMod_Load(IShell *pIShell, void *ph, IModule **ppMod)

{

   // Invoke helper to do the actual loading.

   return AEEStaticMod_New(sizeof(AEEMod),pIShell,ph,ppMod,NULL,NULL);

}

  

  BREW底层调用这个函数,需要提供三个参数。pIShell是ISHELL接口的指针,有了它就可以创建和访问其它接口。ph是helper 类函数的函数列表指针,有了它就可以调用MALLOC等函数。ppMod是用于返回给BREW底层的地址,存储生成的module的信息。

  AEEMod_Load函数调用了AEEStaticMod_New函数。在这个函数中初始化了module数据结构,ppMod就是指向它的。函数结束后返回BREW底层。

  BREW底层稍后通过存储在module数据结构中的函数指针,调用同样位于AEEModGen.中的AEEMod_CreateInstance函数,生成该Module的某一个Applet实例。在这个函数中会调用到我们所编写的AEEClsCreateInstance函数。在我们这个函数中注册了HandleEvent函数和FreeAppDate函数。这些信息同样通过一个applet数据结构指针返回给BREW底层。这之后,BREW会通过给HandleEvent发各种Event,来驱动程序运行。

 

  通过这整个过程,可以总结出BREW程序运行的必要条件。

  程序必须得到IShell指针,Helper 指针,这样程序中对BREW底层各种函数的调用才能进行。BREW底层必须知道两个地址,通过它们,可以将Event传递给程序,调用FreeAppDate函数。

  因此,想在程序中调用另外一个,只需要想办法把程序里的IShell指针,Helper 指针传递给,同时得到中的HandleEvent函数,FreeAppDate函数的地址就可以了。

  剩下的问题就是,程序怎样和文件交互呢,文件的格式是怎样的?

 

  生成的mak文件最后一般有这样两句话,

 

ld *.o a.elf

fromelf –bin a.elf a.

 

  链接器ld生成ELF格式,之后用格式转换工具fromelf将ELF文件转换成文件。

  ELF文件是带格式的可执行文件,对它的执行要靠操作系统的解析来进行。而通过–bin选项生成的文件,格式却是plain binary,即赤裸裸的二进制机器指令,其实是无格式的。只要将文件载入内存,跳转到它的0地址处,就可以一条条指令的执行下来。和文件交互,也就是要安排好文件0地址处的内容,知道文件的调用者会传入什么东西,文件会返回什么东西。

 

  

  下面来看一种实际的调用方法吧。代码如下:

 

typedef int (*RunLoadMod)(IShell *pIShell, void *ph, IModule **ppMod);

 

pData = MyLoadZip(pIShell);

if( !pData )

return EFAILED;

DBGPRINTF(”to RunLoadMod”);

if( SUCCESS != ((RunLoadMod)pData)(pIShell,ph,&pOrgMod) )

{

DBGPRINTF(”RunLoadMod_Err”);

goto Crt_Err;

}

  

  这段程序将文件读入内存,放在pData缓冲区内,用((RunLoadMod)pData)(pIShell,ph,&pOrgMod)一句执行之。RunLoadMod只不过是用typedef定义的一种函数指针类型。这句话的意思相当于是为待调用的文件准备好pIShell,ph,pOrgMod三个数据后,直接跳到内存中的文件的第一个字节处,将它当做普通机器指令一样执行。这种方法调用的文件,应是一个用完整的BREW框架编译后生成的。0地址处放下的是AEEMod_Load函数。文件直接将Applet信息传递给BREW底层,注册HandleEvent函数。实际上是文件取代了调用者的位置。

  

  但是这段程序很可能会运行失败。因为BREW底层除了传参外,还做了其它事情。这在AEEStdlib.h中可以看出。

 

#define GET_HELPER()      (*(((AEEHelperFuncs **)AEEMod_Load) – 1))

#define GET_HELPER_VER()  (*((uint32 *)(((byte *)AEEMod_Load) – sizeof(AEEHelperFuncs *) – sizeof(uint32))))

 

  这两个宏定义可以看成是两个全局常量。第一个是BREW Helper 的函数指针表,第二个是函数指针表的版本号。上面提到的通过参数传递Helper 函数指针,是只在模拟器上成立的。手机上其实是通过全局常量形式传递的。为保证可以正常运行,也应在缓冲区前保存这两个常量。

 

  这种“冒名顶替”的方式,对于只想压缩一下的应用来说是完全可以的。但有时,我们希望新调用的只是整个应用的一小部分,调用者继续存在。这就需要调用者充当BREW底层的一部分角色,记住返回的各种信息,在适当的时候调用中的相应函数。新调用的如果想要取得主的信息,类似于上面提到的HelperFuncs全局常量指针的方法是个可行的选择。

 

  其实知道了BREW程序的加载过程,就可以比较灵活的达到调用的目的,甚至BREW程序架构都是可以改动的。但是改动之后,如何在模拟器上进行调试就又成了一个问题。

 

  程序中调用的技巧,在国外几年前就已得到了应用。甚至有家公司专门开发了利用这个技巧压缩的工具。

 

http://www.s-cradle.com/english/products/sophiacompress_brew/index.html

 

参考文章:

 

1.How to build A to load B ?

 

http://brewforums.qualcomm.com/showthread.php?t=11637

 

2.How to run file that is loaded in the HEAP…

 

http://brewforums.qualcomm.com/showthread.php?t=18413&highlight=modloader

 

3.深入BREW模块加载机制

 

http://nicefuture.ycool.com/post.722858.html

 

4.深度剖析BREW实现原理收藏

 

http://blog.csdn.net/Gemsea/archive/2006/09/07/1190206.aspx

Tags: , ,

24 Apr 09 [转] Symbian OS 9 可执行文件格式

原译者:陈啸天(cxt_programmer)
请关注www.cpplite.com及bbs.cpplite.com

声明:原文出自大牛——ScreenShot作者A ntony  P ranata 。本人英语很烂,之所以瞎译出来一是为了以后参考方便一些,二是在翻译的过程中自己能更仔细的看~。强烈建议大家对照英文原版来看哈。另外有些地方没有翻译,大家意会哈。

原文地址:

http://www.antonypranata.com/articles/new-symbian-os-9-executable-file-format-e32image

前言:

Symbian OS 9已经发布,与之前的版本相比,OS 9增加了许多新特性并有了一些改变,其中之一就是新的可执行文件格式(E32Image)。本文主要讨论了这个新的文件格式,如果你想了解OS 9之前的可执行文件格式,可以阅读一下我之前关于此话题的文章。

在开始之前,请注意我们仅仅讨论的是真机环境下的可执行文件格式(ARM)。为什么不讨论模拟器环境呢?因为运行在模拟器的可执行文件使用了一种不同的文件格式(Cxt注:模拟器下的可执行文件应该是windows的PE格式),如果你之前不了解任何一个平台的可执行文件格式,下面的介绍会更好的帮助你理解E32文件格式。

这篇文章是在Symbian网站或者其他拥有Symbian OS licensees的厂商(例如Nokia)提供的公开信息(文档)之上完成的。虽然我目前在一家手机厂商工作,但这篇文章涉及内容的正确性并未经过公司的核对。尽管我已经紧握最大的努力确保这篇文章内容的正确,但我仍然无法100%保证(Cxt音:看看,大牛总是很谦虚的,我们要做一个谦虚的人哈)。

EABI介绍( Introduction to EABI ):

ABI(应用程序二进制接口,Application Binary Interface)是由ARM及其合作伙伴们制定的一个标准,它定义了如何编译、链接,以及其他工具怎样生成obj文件和可执行文件。这个标准可以让不同编译器生成的obj文件互通,例如可以把不同编译器生成的obj文件组合在一起。EABI(嵌入式应用程序二进制接口, Embedded Application Binary Interface )也是这样的标准,它就是嵌入式平台的ABI。

写这篇文章的时候,有两种编译器可以用于Symbian OS 9——RVCT( RealView Compilation Tools )和GCCE。RVCT是ARM公司开发的编译器,license费用可达数千美元;与此不同,GCCE是一个由 CodeSourcery 开发的免费编译器;顾名思义,GCCE基于GNU编译器。

    EABI编译器生成的格式为ELF( Executable and Linking Format ),它不同于Symbian OS 9之前的PE格式。然而Symbian OS并不使用ELF格式,因为Symbian OS通常在大小受限的ROM上,而标准ELF文件的尺寸通常又很大。所以Symbian把标准的ELF转换为Symbian特有的E32Image格式。如果你有安装Symbian SDK,可以在epoc32\tools目录下找到用于把ELF转换为E32Image的工具,它叫elf2e32.exe。

下图展示了Symbian OS 9的新工具链。如你所见,最后生成的是由elf2e32.exe转换完的Symbian特有格式——E32Image。Symbian网站和SDK中有更多关于这方面的介绍。(Cxt注:SDK help:   »   Symbian OS v9.1   »   Symbian OS Tools And Utilities   »   Build tools guide   »  The native build targets 目录下有全面详细的文档介绍)。通常你不会看到下图所示的流程,因为Symbian OS使用一些脚本工具(例如bldmake、abld等)自动完成。

如果你不熟悉.dso文件也没关系,它其实与Symbian OS 9以及其他平台的.lib文件是一样的。(Cxt注:.dos提供导出的函数的名称和位置,DLL包含实际的函数和数据。)

我在前面说过,ELF文件通常都很大,不适合用于手机。减少ELF尺寸的方法之一是把函数名字替换为序号。例如可以把MyFunction()替换为1,正如你期望的,这种方法可以极大的减小ELF的尺寸,特别是当函数名称很长时(例如 ThisIsVeryLongFunction ())这种效果更加明显。额等等!如此一来,如何找到序号和函数名称之间的对应关系呢?别急,我们有一个.def文件,它提供了序号与函数名之间的对应关系。(Cxt:呼 ……

E32Image概述( Overview of E32Image ):

现在让我们来看看E32Image吧。与其他标准的可执行文件类似,它也包含头(Header)、代码段(Code Section)、数据段(Data Section)、导入段(Import Section)等等。

上图展示了E32文件格式,与其他可执行文件格式一样,最开始的部分是header。下面我会着重介绍E32 header里面都有些什么东东。在Header之下还有一些其它section(Cxt:这部分大家意会哈^_^):

·  Code section, contains all the object files (.o) of your source code as well as export address table that lists all the exported functions.

·  BSS section, contains un-initialized data.

·  Data section, contains initialized data.

·  Import section, contains the information about all imported functions used by your program.

· Relocation section, contains relocation table needed by Symbian OS loader to load your program.

E32Image头( Header of E32Image ):

头信息也许是最有趣的部分,因为它包含了可执行文件的很多信息。E32ImageHeader的声明可以在SDK的\epoc32\include\f32image.h中找到。看看class  E32ImageHeaderV 的声明,这可是E32Image文件格式完整的头信息。如果你仔细看了class  E32ImageHeaderV ,你会发现它继承了E32HeaderComp(E32HeaderComp继承了E32ImageHeader)。下图对它们的关系做了更好的说明:

下面的代码片段展示了 EImageHeader EImageHeaderComp E32ImageHeaderV 的声明,请注意我删减了部分函数以及注释以便于更清晰的查看:

class  E32ImageHeader

{

public :

  TUint32 iUid1;

  TUint32 iUid2;

  TUint32 iUid3;

  TUint32 iUidChecksum;

  TUint iSignature;  // ’EPOC’

  TUint32 iHeaderCrc;  // CRC-32 of entire header

  TUint32 iModuleVersion;  // Version number for this executable (used in link resolution)

  TUint32 iCompressionType;  // Type of compression used (UID or 0 for none)

  TVersion iToolsVersion;  // Version of PETRAN/ELFTRAN which generated this file

  TUint32 iTimeLo;

  TUint32 iTimeHi;

  TUint iFlags;  // 0 = exe, 1 = dll, 2 = fixed address exe

  TInt iCodeSize;  // size of code, import address table, constant data and export dir

  TInt iDataSize;  // size of initialised data

  TInt iHeapSizeMin;

  TInt iHeapSizeMax;

  TInt iStackSize;

  TInt iBssSize;

  TUint iEntryPoint;  // offset into code of entry point

  TUint iCodeBase;  // where the code is linked for 

  TUint iDataBase;  // where the data is linked for

  TInt iDllRefTableCount;  // filling this in enables E32ROM to leave space for it

  TUint iExportDirOffset;  // offset into the file of the export address table

  TInt iExportDirCount;

  TInt iTextSize;  // size of just the text section, also doubles as the offset for the

  // iat w.r.t. the code section

  TUint iCodeOffset;  // file offset to code section, also doubles as header size

  TUint iDataOffset;  // file offset to data section

  TUint iImportOffset;  // file offset to import section

  TUint iCodeRelocOffset;  // relocations for code and const

  TUint iDataRelocOffset;  // relocations for data

  TUint16 iProcessPriority;  // executables priority

  TUint16 iCpuIdentifier;  // 0×1000 = X86, 0×2000 = ARM

};

class  E32ImageHeaderComp :  public  E32ImageHeader

{

public :

  TUint32 iUncompressedSize;  // Uncompressed size of file

  // For J format this is file size - sizeof(E32ImageHeader)

  // and this is included as part of the compressed data :-(

  // For other formats this is file size - total header size

};

class  E32ImageHeaderV :  public  E32ImageHeaderComp

{

public :

  SSecurityInfo iS;

  // Use iSpare1 as offset to Exception Descriptor

  TUint32 iExceptionDescriptor;  // Offset in bytes from start of code section to Exception Descriptor,

  // bit 0 set if valid

  TUint32 iSpare2;

  TUint16 iExportDescSize;  // size of bitmap section

  TUint8 iExportDescType;  // type of description of holes in export table

  TUint8 iExportDesc[1];  // description of holes in export table - extend

};

     我将逐一解释上面这些字段。从下面的列表中,左端的16进制数表示这个字段在文件中的偏移量,例如iUid2的位置是从文件头部开始0×04的位置。换句话说,如果你把可执行文件用二进制编辑器打开,然后找到偏移量0×04的位置你就可以找到iUid2。注意E32Image使用小端格式(little-endian order)。

E32ImageHeader

0×00:   iUid1 ,可执行文件的第一个UID。这个UID可被看作是一个系统级别的标识符,例如Dlls是 0×1000 0079 ,可执行程序是 0×1000 007A 。如果你想更多的了解Symbian OS UID相关信息,可以 访问这里

0×04: iUid2 ,可执行文件的第二个UID。当两个对象拥有同一个UID1的时候,就需要用这个UID来区分它们,例如 0×1000 39CE 表示多态接口Dll( polymorphic interface DLLs ),  0×1000 008d 表示静态接口( static interface (shared library) )。

0×08: iUid3 ,可执行文件的第三个UID。   每个程序的UID3都不同。下面这段不翻译了,贴个图看图识字吧^_^。

It is unique for each application. Developers have to request this UID from Symbian Signed service. Symbian OS 9 applications usually have UIDs in the range of 0×200 0000 and 0×2FFF FFFF. Examples from Symbian OS SDKs, like S60 SDK or UIQ SDK, have the UIDs in the range of 0xA000 0000 and 0xAFFF FFFF. There are also some UID available for testing, which can be chosen from the range 0×0100 0000 to 0×0FFF FFFF.

0×0C: iUidChecksum ,校验前面提到的3个UID。Symbian SDK中提供了一个uidcrc.exe工具完成该功能;下面的例子展示了如何生成这三个UID: 0×1000 007A 0×1000 39CE  0xA000 017F 的校验码:

:\>uidcrc 0×1000007A 0×100039CE 0xA000017F

0×1000007a 0×100039ce 0xa000017f 0×1e7cca07

0×10: iSignature ,E32文件唯一签名( unique signature )。值统一为“EPOC”。图示如下:

0×14: iHeaderCrc ,整个头信息的完整校验,使用 CCITT CRC-32 算法。

0×18: iModuleVersion ,可执行版本号。该信息用于链接过程(linking process)。在S60 3 rd  iModuleVersion  值为10( 0×0000 000A )(Cxt注:大牛这里可能笔误了,iModuleVersion是T u int32,占4个字节,这个值应该是0×000A 0000)。

0×1C: iCompressionType ,一个UID,表示使用哪种压缩算法压缩了可执行文件。如果值为0就说明未压缩。就我目前所看到的,只使用了一种压缩算法:由RFC 1951定义的 Deflate/Huffman 算法。它的UID是 KUidCompressionDeflate 0×101F 7AFC )。需要注意的是,未来也许会使用其他压缩算法。

0×20: iToolsVersion ,生成这个可执行文件的 ELFTRAN 工具版本。

0×24: iTimeLo ,时间戳。 the lowest word of the timestamp when the file is created.

0×28: iTimeHi ,时间戳。 the lowest word of the timestamp when the file is created.

0×2C: iFlags ,可执行文件的一个标记,比如 KImageDll  KImageNoCallEntryPoint  等。这些标记定义在f32image.h中。f32image.h中有一些函数定义用来解释这些标记的意思。例如:如果flag值为 0×1200 002A ,我们可以把它看作: 0×1000 0000 + 0×0200 0000 + 0×0000 00020 + 0×0000 0008 + 0×0000 00002 ,结合f32image.h中的常量声明,我们可以发现:

·  0×10000000 = KImageImpFmt_PE ,可执行文件使用ELF-derived入口。

·  0×02000000 = KImageHdrFmt_V ,header的版本支持。

·  0×00000020 = KImageEpt_Eka2 ,EKA2可执行文件。

·  0×00000008 = KImageABI_EABI ,可执行文件为EABI image file。

·  0×00000002 = KimageNoCallEntryPoint ,no  to entry point。

    

0×30: iCodeSize is the size of code section, import address table, constant data and export dir.

0×34: iDataSize , size of initialised data.

0×38: iHeapSizeMin , the minimum size of the heap.

0×3C: iHeapSizeMax , the maximum size of the heap.

0×40: iStackSize , the size of the stack.

0×44: iBssSize , the size of the un-initialized data section.

0×48: iEntryPoint , offset into code of entry point.

0×4C: iCodeBase , where the code is linked for.

0×50: iDataBase , where the data is linked for.

0×54: iDllRefTableCount , the number of DLLs imported by this program.

0×58: iExportDirOffset , offset into the file of the export address table.

0×5C: iExportDirCount , the offset of the export address table.

0×60: iTextSize , size of just the text section, also doubles as the offset for the iat w.r.t. the code section.

0×64: iCodeOffset , file offset to code section, also doubles as header size.

0×68: iDataOffset , file offset to data section.

0×6C: iImportOffset , file offset to import section.

0×70: iCodeRelocOffset , relocations for code and const.

0×74: iDataRelocOffset , relocations for data.

0×78: iProcessPriority , executables priority.

0×7A: iCpuIdentifier , the identifier of CPU. Look at the following constant for all possible values:

enum TCpu

{

  ECpuUnknown=0, 

  ECpuX86=0×1000, 

  ECpuArmV4=0×2000, 

  ECpuArmV5=0×2001, 

  ECpuArmV6=0×2002, 

  ECpuMCore=0×4000

};                 

E32ImageHeaderComp

    开始之前,我们需要知道2个结构:定义在e32cmn.h的 ScapabilitySet SSSecurityInfo 。Symbian OS 9可以看到它们,它们保存了平台安全相关信息,比如能力(capability)、安全标识符(secure identifier)和厂商标识符(vendor identifier)。

struct SCapabilitySet

{

enum {ENCapW=2};

TUint32 iCaps[ENCapW];

};

struct SSecurityInfo

{

TUint32 iSecureId;

TUint32 iVendorId;

SCapabilitySet iCaps; // Capabilities re. platform security

};

好了,让我们回到 E32ImageHeaderV

0×80: iS.iSecureId ,可执行文件的安全ID(secure ID)。就是可执行文件的UID3。

0×84: iS.iVendorId ,可执行文件的厂商ID(vendor ID)。对于第三方应用程序,值为0。

0×88: iS.iCaps.iCaps ,运行可执行文件需要的能力。Symbian OS所有的能力定义可以在 e32capability.h 文件中找到。

enum TCapability

{

  ECapabilityTCB = 0,

  ECapabilityCommDD = 1,

  ECapabilityPowerMgmt = 2,

  ECapabilityMultimediaDD = 3,

  ECapabilityReadDeviceData = 4,

  ECapabilityWriteDeviceData = 5,

  ECapabilityDRM = 6,

  ECapabilityTrustedUI = 7,

  ECapabilityProtServ = 8,

  ECapabilityDiskAdmin = 9,

  ECapabilityNetworkControl = 10,

  ECapabilityAllFiles = 11,

  ECapabilitySwEvent = 12,

  ECapabilityNetworkServices = 13,

  ECapabilityLocalServices = 14,

  ECapabilityReadUserData = 15,

  ECapabilityWriteUserData = 16,

  ECapabilityLocation = 17,

  ECapabilitySurroundingsDD = 18,

  ECapabilityUserEnvironment = 19,

};

    能力以bit的形式表示,例如 ECapabilityTCB 意味着最低有效位(LSB),如果设置了最低有效位,可执行文件就具有TCB能力。

0×90: iExceptionDescriptor is offset in bytes from start of code section to Exception Descriptor, bit 0 set if valid

0×94: iSpare2 , reserved.

0×98: iExportDescSize , size of bitmap section.

0×9A: iExportDescType[1] , type of description of holes in export table.

0×9B: iExportDesc[1] , is description of holes in export table.

Example

    下面我们来看一个例子,来帮助你更好的理解上面说的那些字段。我们将使用标准的Symbian OS build command来编译3.0 SDK提供的Helloworld Basic例子。

:\Symbian\9.1\S60_3rd\S60Ex\helloworldbasic>bldmake bldfiles

:\Symbian\9.1\S60_3rd\S60Ex\helloworldbasic>abld build gcce urel

现在切换到 \epoc32 \r elease\gcce\urel 目录,使用 Elf2E32 工具读取E32 image头信息。做法如下:

:\Symbian\9.1\S60_3rd\S60Ex\helloworldbasic>elf2e32 –e32input=helloworldbasic.exe

然后你将会看到 helloworldbasic.exe 的头信息;输出的过程可能会很长,所以一个比较好的方法是将他转储到文件,便于我们进一步分析:

:\Symbian\9.1\S60_3rd\S60Ex\helloworldbasic>elf2e32 –e32input=helloworldbasic.exe > helloworldbasic.txt

打开 helloworldbasic.txt ,你将会看到头信息(我已经用绿色的注释标明了 E32ImageHeaderV 的字段):

  E32ImageFile ’helloworldbasic.exe’

  V2.00(505) Time Stamp: 00e0eb0a,d2525b80  // iTimeStampHi, iTimeStampLo

  EPOC Exe for ARMV5 CPU  // iCpuIdentifier = 0×20001 (ARMv5)

  Flags: 1200002a  // iFlags

  Priority Foreground

  Entry points are not called

  Image header is format 2

  Image is compressed using the DEFLATE algorithm  // iCompressionType

  Uncompressed size 0000b788

  Image FPU support : Soft VFP

  Secure ID: a000017f  // iSecureId

  Vendor ID: 00000000  // iVendorId

  Capabilities: 00000000 00000000  // iSs.iCaps.iCaps

  Exception Descriptor Offset: 00002561  // iExceptionDescriptor

  Exception Index Table Base: 00012dfc

  Exception Index Table Limit: 000130bc

  RO Segment Base: 00008001

  RO Segment Limit: 0000a77c

  Export Description: Size=000, Type=01  // iExportDescSize = 000 iExportDescType = 01

  Export description consistent

  Module Version: 10.0  // iModuleVersion

  Imports are ELF-style

  ARM EABI

  Built against EKA2

  Uids: 1000007a 100039ce a000017f (1e7cca07)

  // iUid1 = 1000007a, iUid2 = 100039ce, iUid3 = a000017f, iUidChecksum = 1e7cca07

  Header CRC: 023aca0d  // iHeaderCrc

  File Size: 0000b788  // iUncompressedSize

  Code Size: 0000b0bc  // iCodeSize

  Data Size: 00000000  // iDataSize

  Compression: 101f7afc  // iCompressionType

  Min Heap Size: 00001000  // iHeapSizeMin

  Max Heap Size: 00100000  // iHeapSieMax

  Stack Size: 00005000  // iStackSize

  Code link addr: 00008000  // iCodeBase

  Data link addr: 00400000  // iDataBase

  Code reloc offset: 0000b650  // iCodeRelocOffset

  Data reloc offset: 00000000  // iDataRelocOffset

  Dll ref table count: 10  // iDllRefTableCount

  Offset Size Relocs #Relocs

  Code 00009c 00b0bc 00b650 00007d +002504 (entry pnt)

  // iCodeOffset = 00009c iCodeSize = 00b0bc iCodeRelocOffset = 00b650

  Data 000000 000000

  // iDataOffset iDataSize

  Bss 000000  // iBssSize

  Import 00b158  // iImportOffset

    全都在这了!!希望你能够喜欢这篇文章!

Tags: , , ,

28 Nov 08 RO段、RW段和ZI段

一直以来对于体系中所描述的RO,RW和ZI数据存在似是而非的理解,这段时间对其仔细了解了一番,发现了一些规律,理解了一些以前书本上有的但是不理解的东西,我想应该有不少人也有和我同样的困惑,因此将我的一些关于RO,RW和ZI的理解写出来,希望能对大家有所帮助。
要了解RO,RW和ZI需要首先了解以下知识:
程序的组成
此处所说的“程序”是指在系统中正在执行的程序,而非保存在ROM中的bin映像(image)文件,这一点清注意区别。
一个程序包含3部分:RO,RW和ZI
RO是程序中的指令和常量
RW是程序中的已初始化变量
ZI是程序中的未初始化的变量
由以上3点说明可以理解为:
RO就是readonly,
RW就是read/write,
ZI就是zero
映像文件的组成
所谓映像文件就是指烧录到ROM中的bin文件,也成为image文件。以下用Image文件来称呼它。
Image文件包含了RO和RW数据。
之所以Image文件不包含ZI数据,是因为ZI数据都是0,没必要包含,只要程序运行之前将ZI数据所在的区域一律清零即可。包含进去反而浪费存储空间。
Q:为什么Image中必须包含RO和RW?
A:因为RO中的指令和常量以及RW中初始化过的变量是不能像ZI那样“无中生有”的。
程序的执行过程
从以上两点可以知道,烧录到ROM中的image文件与实际运行时的程序之间并不是完全一样的。因此就有必要了解程序是如何从ROM中的image到达实际运行状态的。
实际上,RO中的指令至少应该有这样的功能:
1. 将RW从ROM中搬到RAM中,因为RW是变量,变量不能存在ROM中。
2. 将ZI所在的RAM区域全部清零,因为ZI区域并不在Image中,所以需要程序根据编译器给出的ZI地址及大小来将相应得RAM区域清零。ZI中也是变量,同理:变量不能存在ROM中
在程序运行的最初阶段,RO中的指令完成了这两项工作后C程序才能正常访问变量。否则只能运行不含变量的代码。
说了上面的可能还是有些迷糊,RO,RW和ZI到底是什么,下面我将给出几个例子,最直观的来说明RO,RW,ZI在C中是什么意思。
1; RO
看下面两段程序,他们之间差了一条语句,这条语句就是声明一个字符常量。因此按照我们之前说的,他们之间应该只会在RO数据中相差一个字节(字符常量为1字节)。
Prog1:
#include <stdio.h>
void main(void)
{
;
}
Prog2:
#include <stdio.h>
const char a = 5;
void main(void)
{
;
}
Prog1编译出来后的信息如下:
================================================================================
Code RO Data RW Data ZI Data
948 60 0 96 0 Grand Totals
================================================================================
Total RO Size(Code + RO Data) 1008 ( 0.98kB)
Total RW Size(RW Data + ZI Data) 96 ( 0.09kB)
Total ROM Size(Code + RO Data + RW Data) 1008 ( 0.98kB)
================================================================================
Prog2编译出来后的信息如下:
================================================================================
Code RO Data RW Data ZI Data
948 61 0 96 0 Grand Totals
================================================================================
Total RO Size(Code + RO Data) 1009 ( 0.99kB)
Total RW Size(RW Data + ZI Data) 96 ( 0.09kB)
Total ROM Size(Code + RO Data + RW Data) 1009 ( 0.99kB)
================================================================================
以上两个程序编译出来后的信息可以看出:
Prog1和Prog2的RO包含了Code和RO Data两类数据。他们的唯一区别就是Prog2的RO Data比Prog1多了1个字节。这正和之前的推测一致。
如果增加的是一条指令而不是一个常量,则结果应该是Code数据大小有差别。
2; RW
同样再看两个程序,他们之间只相差一个“已初始化的变量”,按照之前所讲的,已初始化的变量应该是算在RW中的,所以两个程序之间应该是RW大小有区别。
Prog3:
#include <stdio.h>
void main(void)
{
;
}
Prog4:
#include <stdio.h>
char a = 5;
void main(void)
{
;
}
Prog3编译出来后的信息如下:
================================================================================
Code RO Data RW Data ZI Data
948 60 0 96 0 Grand Totals
================================================================================
Total RO Size(Code + RO Data) 1008 ( 0.98kB)
Total RW Size(RW Data + ZI Data) 96 ( 0.09kB)
Total ROM Size(Code + RO Data + RW Data) 1008 ( 0.98kB)
================================================================================
Prog4编译出来后的信息如下:
================================================================================
Code RO Data RW Data ZI Data
948 60 1 96 0 Grand Totals
================================================================================
Total RO Size(Code + RO Data) 1008 ( 0.98kB)
Total RW Size(RW Data + ZI Data) 97 ( 0.09kB)
Total ROM Size(Code + RO Data + RW Data) 1009 ( 0.99kB)
================================================================================
可以看出Prog3和Prog4之间确实只有RW Data之间相差了1个字节,这个字节正是被初始化过的一个字符型变量“a”所引起的。
3; ZI
再看两个程序,他们之间的差别是一个未初始化的变量“a”,从之前的了解中,应该可以推测,这两个程序之间应该只有ZI大小有差别。
Prog3:
#include <stdio.h>
void main(void)
{
;
}
Prog4:
#include <stdio.h>
char a;
void main(void)
{
;
}
Prog3编译出来后的信息如下:
================================================================================
Code RO Data RW Data ZI Data
948 60 0 96 0 Grand Totals
================================================================================
Total RO Size(Code + RO Data) 1008 ( 0.98kB)
Total RW Size(RW Data + ZI Data) 96 ( 0.09kB)
Total ROM Size(Code + RO Data + RW Data) 1008 ( 0.98kB)
================================================================================
Prog4编译出来后的信息如下:
================================================================================
Code RO Data RW Data ZI Data
948 60 0 97 0 Grand Totals
================================================================================
Total RO Size(Code + RO Data) 1008 ( 0.98kB)
Total RW Size(RW Data + ZI Data) 97 ( 0.09kB)
Total ROM Size(Code + RO Data + RW Data) 1008 ( 0.98kB)
================================================================================
编译的结果完全符合推测,只有ZI数据相差了1个字节。这个字节正是未初始化的一个字符型变量“a”所引起的。
注意:如果一个变量被初始化为0,则该变量的处理方法与未初始化华变量一样放在ZI区域。
即: C程序中,所有的未初始化变量都会被自动初始化为0。
总结:
1; C中的指令以及常量被编译后是RO类型数据。
2; C中的未被初始化或初始化为0的变量编译后是ZI类型数据。
3; C中的已被初始化成非0值的变量编译后市RW类型数据。
附:
程序的编译命令(假定C程序名为tst.c):
armcc -c -o tst.o tst.c
armlink -noremove - -nodebug -info totals -info sizes -map -list aa.map -o tst. tst.o
编译后的信息就在aa.map文件中。
ROM主要指:NAND Flash,Nor Flash
RAM主要指:PSRAM,SDRAM,SRAM,DDRAM

Tags: , , , ,

20 Oct 08 GTK+主循环(main loop)的工作原理

转至:http://www.builder.com.cn

我们知道GUI应用程序都是事件驱动的。这些事件大部分都来自于用户,比如键盘事件、鼠标事件或笔点事件。还有一些事件来自于系统内部,比如定时事件、socket事件和其它文件事件等等。在没有任何事件的情况下,应用程序处于睡眠状态。

因为这种事件驱动机制,GUI应用程序都毫无例外的需要一个主循环(main loop)。主循环(main loop)控制应用程序什么时候进入睡眠状态,什么时候被唤醒。主循环实现得好,应用程序才能工作正常又省电。

Win32 GUI应用程序的主循环是我们比较熟悉的,其大致如下:

// Main message loop:

while (GetMessage(&msg, NULL, 0, 0))

{

if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

}

在这个主循环中,它不断的从消息队列中提取消息,然后分发给消息的目标(通常是窗口),直到GetMessage返回FALSE(收到WM_QUIT消息,一般调用PostQuitMessage)为止,如果队列中没有消息,应用程序就进入睡眠状态。这种方法简单明了,缺陷也是明显的,它只能挂在消息队列上,而不能同时挂在多个事件源上(如管道和socket)。要挂在多个事件源上,需要使用其它方式,比如用WaitForMultipleObjects,那就比较麻烦了。

而在GTK+应用程序中,其主循环(main loop)更加简单,但是非常的不明了:

gtk_main ();

不少人用GTK+写了很长时间的程序,还是觉得这行代码很神秘,不知道里面到底干了什么。本文试图分析一下gtk_main的工作原理:

gtk_main主要是对glibmain loop的包装,基本上分为三步:

1. 调用初始化函数。

2. 进入glib main loop

3. 调用~初始化函数。

所以弄清楚glib main loop之后,gtk_main的实现也就尽收眼底了,本文重点分析glibmain loop的实现。main loop使用模式大致如下:

loop = g_main_loop_new (NULL, TRUE);

g_main_loop_run (loop);

g_main_loop_new创建一个main loop对象,一个main loop对象只能被一个线程使用,但一个线程可以有多个main loop对象。在GTK+应用中,一个线程使用多个main loop的主要用途是实现模态对话框,它在gtk_dialog_run函数里创建一个新的main loop,通过该main loop分发消息,直到对话框关闭为止。

g_main_loop_run则是进入主循环,它会一直阻塞在这里,直到让它退出为止。有事件时,它就处理事件,没事件时就睡眠。

g_main_loop_quit则是用于退出主循环,相当于Win32下的PostQuitMessage函数。

Glib main loop的最大特点就是支持多事件源,使用非常方便。来自用户的键盘和鼠标事件、来自系统的定时事件和socket事件等等,还支持一个称为idle的事件源,其主要用途是实现异步事件。Main loop的基本组成如下图所示:

GMainLoop的主要部件是GMainContextGMainContext可以在多个GMainLoop间共享,但要求这些GMainLoop都在同一个线程中运行,前面提到的模态对话框就属于这一类。GMainContext通常由多个GSource组成,GSource是事件源的抽象,任何事件源,只要实现GSource规定的接口,都可以挂到GMainContext中来。

GSource的接口函数有:

1. gboolean (*prepare) (GSource *source, gint *timeout_);进入睡眠之前,在g_main_context_prepare里,mainloop调用所有Sourceprepare函数,计算最小的timeout时间,该时间决定下一次睡眠的时间。

2. gboolean (*check) (GSource *source); poll被唤醒后,在g_main_context_check里,mainloop调用所有Sourcecheck函数,检查是否有Source已经准备好了。如果poll是由于错误或者超时等原因唤醒的,就不必进行dispatch了。

3. gboolean (*dispatch) (GSource*source, GSourceFunc callback,gpointer user_data); 当有Source准备好了,在g_main_context_dispatch里,mainloop调用所有Sourcedispatch函数,去分发消息。

4. void (*finalize) (GSource *source); Source被移出时,mainloop调用该函数去销毁Source

Main loop的工作流程简图如下:

下面我们看看几个内置Source的实现机制:

Idle 它主要用实现异步事件,功能类似于Win32下的PostMessage。但它还支持重复执行的特性,根据用户注册的回调函数的返回值而定。

1. g_idle_prepare把超时设置为0,也就是即时唤醒,不进入睡眠状态。

2. g_idle_check 始终返回TRUE,表示准备好了。

3. g_idle_dispatch 调用用户注册的回调函数。

Timeout 它主要用于实现定时器,支持一次定时和重复定时,根据用户注册的回调函数的返回值而定。

1. g_timeout_prepare 计算下一次的超时时间。

2. g_timeout_check 检查超时时间是否到了,如果到了就返回TRUE,否则返回FALSE

3. g_timeout_dispatch调用用户注册的回调函数。

线程可以向自己的mainloop中增加Source,也可以向其它线程的mainloop增加Source。向自己的mainloop中增加Source时,mainloop已经唤醒了,所以不会存在什么问题。而向其它线程的mainloop增加Source时,对方线程可能正挂在poll里睡眠,所以要想法唤醒它,否则Source可能来不及处理。在Linux下,这是通过wake_up_pipe管道实现的,mainlooppoll时,它除了等待所有的Source外,还会等待wake_up_pipe管道。要唤醒poll,调用g_main_context_wakeup_unlockedwake_up_pipe里写入字母A就行了。

Tags: , , , ,

16 Jan 08 [转]论调用约定

在C语言中,假设我们有这样的一个函数:

int function(int a,int b)

调用时只要用result = function(1,2)这样的方式就可以使用这个函数。但是,当高级语言被编译成计算机可以识别的机器码时,有一个问题就凸现出来:在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。为此,计算机提供了一种被称为栈的数据结构来支持参数传递。

栈是一种先进后出的数据结构,栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改。用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改。

函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。

在参数传递中,有两个很重要的问题必须得到明确说明:

  • 当参数个数多于一个时,按照什么顺序把参数压入堆栈
  • 函数调用后,由谁来把堆栈恢复原装

在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:

调用约定

很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是。在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。

调用约定声明的语法为(以前文的那个函数为例):

int __ function(int a,int b)

的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸

以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处翻译成汇编语言将变成:

push 2 第二个参数入栈 push 1 第一个参数入栈 function 调用参数,注意此时自动把cs:eip入栈

而对于函数自身,则可以翻译为:

push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复 mov ebp,esp 保存堆栈指针 mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b mov esp,ebp 恢复esp pop ebp ret 8

而在编译时,这个函数的名字被翻译成_function@8

注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。其中在函数开始处保留esp到ebp中,在函数结束恢复是编译器常用的方法。

从函数调用看,2和1依次被push进堆栈,而在函数中又通过相对于ebp(即刚进函数时的堆栈指针)的偏移量存取参数。函数结束后,ret 8表示清理8个字节的堆栈,函数自己恢复了堆栈。

cdecl调用约定

cdecl调用约定又称为C调用约定,是C语言缺省的调用约定,它的定义语法是:

int function (int a ,int b) //不加修饰就是C调用约定 int __cdecl function(int a,int b)//明确指出C调用约定

在写本文时,出乎我的意料,发现cdecl调用约定的参数压栈顺序是和是一样的,参数首先由有向左压入堆栈。所不同的是,函数本身不清理堆栈,调用者负责清理堆栈。由于这种变化,C调用约定允许函数的参数的个数是不固定的,这也是C语言的一大特色。对于前面的function函数,使用cdecl后的汇编码变成:

调用处 push 1 push 2 function add esp,8 注意:这里调用者在恢复堆栈 被调用函数_function处 push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复 mov ebp,esp 保存堆栈指针 mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b mov esp,ebp 恢复esp pop ebp ret 注意,这里没有修改堆栈

MSDN中说,该修饰自动在函数名前加前导的下划线,因此函数名在符号表中被记录为_function,但是我在编译时似乎没有看到这种变化。

由于参数按照从右向左顺序压栈,因此最开始的参数在最接近栈顶的位置,因此当采用不定个数参数时,第一个参数在栈中的位置肯定能知道,只要不定的参数个数能够根据第一个后者后续的明确的参数确定下来,就可以使用不定参数,例如对于CRT中的sprintf函数,定义为:

int sprintf(char* buffer,const char* format,…)

由于所有的不定参数都可以通过format确定,因此使用不定个数的参数是没有问题的。

fastcall

fastcall调用约定和类似,它意味着:

  • 函数的第一个和第二个DWORD参数(或者尺寸更小的)通过ecx和edx传递,其他参数通过从右向左的顺序压栈
  • 被调用函数清理堆栈
  • 函数名修改规则同

其声明语法为:int fastcall function(int a,int b)

thiscall

thiscall是唯一一个不能明确指明的函数修饰,因为thiscall不是关键字。它是C++类成员函数缺省的调用约定。由于成员函数调用还有一个this指针,因此必须特殊处理,thiscall意味着:

  • 参数从右向左入栈
  • 如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针在所有参数压栈后被压入堆栈。
  • 对参数个数不定的,调用者清理堆栈,否则函数自己清理堆栈

为了说明这个调用约定,定义如下类和使用代码:

 A { public:    int function1(int a,int b);    int function2(int a,...); }; int A::function1 (int a,int b) {    return a+b; } #include  int A::function2(int a,...) {    va_list ap;    va_start(ap,a);    int i;    int result = 0;    for(i = 0 ; i < a ; i ++)    {       result += va_arg(ap,int);    }    return result; } void callee() {    A a;    a.function1 (1,2);    a.function2(3,1,2,3); }

callee函数被翻译成汇编后就变成:

//函数function1调用 0401C1D push 2 00401C1F push 1 00401C21 lea ecx,[ebp-8] 00401C24 function1 注意,这里this没有被入栈 //函数function2调用 00401C29 push 3 00401C2B push 2 00401C2D push 1 00401C2F push 3 00401C31 lea eax,[ebp-8] 这里引入this指针 00401C34 push eax 00401C35 function2 00401C3A add esp,14h

可见,对于参数个数固定情况下,它类似于,不定时则类似cdecl

naked

这是一个很少见的调用约定,一般程序设计者建议不要使用。编译器不会给这种函数增加初始化和清理代码,更特殊的是,你不能用return返回返回值,只能用插入汇编返回结果。这一般用于实模式驱动程序设计,假设定义一个求和的加法程序,可以定义为:

__declspec(naked) int  add(int a,int b) {    __asm mov eax,a    __asm add eax,b    __asm ret  }

注意,这个函数没有显式的return返回值,返回通过修改eax寄存器实现,而且连退出函数的ret指令都必须显式插入。上面代码被翻译成汇编以后变成:

mov eax,[ebp+8] add eax,[ebp+12] ret 8

注意这个修饰是和__stdcall及cdecl结合使用的,前面是它和cdecl结合使用的代码,对于和结合的代码,则变成:

__declspec(naked) int __stdcall function(int a,int b) {     __asm mov eax,a     __asm add eax,b     __asm ret 8        //注意后面的8 }

至于这种函数被调用,则和普通的cdecl及调用函数一致。

函数调用约定导致的常见问题

如果定义的约定和使用的约定不一致,则将导致堆栈被破坏,导致严重问题,下面是两种常见的问题:

  1. 函数原型声明和函数体定义不一致
  2. DLL导入函数时声明了不同的函数约定

以后者为例,假设我们在dll种声明了一种函数为:

__declspec(dllexport) int func(int a,int b);//注意,这里没有,使用的是cdecl

使用时代码为:

      typedef int (*WINAPI DLLFUNC)func(int a,int b);       hLib = LoadLibrary(...);       DLLFUNC func = (DLLFUNC)GetProcAddress(...)//这里修改了调用约定       result = func(1,2);//导致错误

由于调用者没有理解WINAPI的含义错误的增加了这个修饰,上述代码必然导致堆栈被破坏,MFC在编译时插入的checkesp函数将告诉你,堆栈被破坏了。

Tags: , , , , , , ,

03 Dec 07 [转] C语言的setjmp: 异常处理与构建协作式多任务系统

转至 http://www.upsdn.net/html/2004-11/47.html

在C标准库中有一对非常有趣的函数()函数与longjmp()函数,用来实现代替实现一些非常重要的功能,如异常处理。C语言中,标准库函数和longjmp形成了结构化异常工具的基础。简单的说即实例化异常处理程序,而longjmp产生异常。

先介绍
int (jmp_buf envbuf)
宏函数()在缓冲区envbuf中保存系统堆栈里的内容,供longjmp()以后使用,()必须使用头文件.h。
调用()宏时,返回值为0,然而longjmp()把一个变原传递给(),该值(恒不为0)就是调用longjmp()后出现的()的值。
函数用于保存程序的运行时的堆栈环境,接下来的其它地方,你可以通过调用longjmp函数来恢复先前被保存的程序堆栈环境。当和longjmp组合一起使用时,它们能提供一种在程序中实现“非本地局部跳转”(”non-local ”)的机制。并且这种机制常常被用于来实现,把程序的控制流传递到错误处理模块之中;或者程序中不采用正常的返回(return)语句,或函数的正常调用等方法,而使程序能被恢复到先前的一个调用例程(也即函数)中。
函数的调用时,会保存程序当前的堆栈环境到env参数中;接下来调用longjmp时,会根据这个曾经保存的变量来恢复先前的环境, 并且当前的程序控制流,会因此而返回到先前调用时的程序执行点。此时,在接下来的控制流的例程中,所能访问的所有的变量(除寄存器类型的变量 以外),包含了longjmp函数调用时,所拥有的变量。
void longjmp(jmp_buf envbuf,int status);
     函数longjmp()使程序在最近一次调用()出重新执行。()和longjmp()提供了一种在函数间调转的手段,必须使用头部文件.h。
     函数longjmp()通过把堆栈复位成envbuf中描述的状态进行操作,envbuf的设置是由预先调用()生成的。这样使程序的执行在 ()调用后的下一个语句从新开始,使计算机认为从未离开调用()的函数。从效果上看,longjmp()函数似乎“绕”过了时间 和空间(内存)回到程序的原点,不必执行正常的函数返回过程。
     缓冲区envbuf具有<.h>中定义的buf_jmp类型,它必须调用longjmp()前通过调用()来设置好。
    值status变成()的返回值,由此确定长调转的来处。不允许的唯一值是0,0是程序直接调用函数()时由该函数返回的,不是间接通过执行函数longjmp()返回的。
     longjmp()函数最常用于在一个错误发生时,从一组深层嵌套的实用程序中返回。
longjmp函数用于恢复先前程序中调用的函数时所保存的堆栈环境。和longjmp组合一起使用时,它们能提供一种在程序中实现“非本地局部跳转”(”non-local ”)的机制。并且这种机制常常被用于来实现,把程序的控制流传递到错误处理模块,或者不采用正常的返回(return)语句,或函数的正常调用等方法,使程序能被恢复到先前的一个调用例程(也即函数)中。

   对函数的调用时,会保存程序当前的堆栈环境到env参数中;接下来调用longjmp时,会根据这个曾经保存的变量来恢复先前的环境,并且 因此当前的程序控制流,会返回到先前调用时的执行点。此时,value参数值会被函数所返回,程序继续得以执行。并且,在接下来 的控制流的例程中,它所能够访问到的所有的变量(除寄存器类型的变量以外),包含了longjmp函数调用时,所拥有的变量;而寄存器类型的变量将不可预 料。函数返回的值必须是非零值,如果longjmp传送的value参数值为0,那么实际上被返回的值是1。

  在调用的函数返回之前,调用longjmp,否则结果不可预料。

在使用longjmp时,请遵守以下规则或限制:
  · 不要假象寄存器类型的变量将总会保持不变。在调用longjmp之后,通过所返回的控制流中,例程中寄存器类型的变量将不会被恢复。
  · 不要使用longjmp函数,来实现把控制流,从一个中断处理例程中传出,除非被捕获的异常是一个浮点数异常。在后一种情况下,如果程序通过调用 _fpreset函数,来首先初始化浮点数包后,它是可以通过longjmp来实现从中断处理例程中返回。
如何实现异常处理
首先设置一个跳转点(() 函数可以实现这一功能),然后在其后的代码中任意地方调用 longjmp() 跳转回这个跳转点上,以此来实现当发生异常时,转到处理异常的程序上,在其后的介绍中将介绍如何实现。 () 为跳转返回保存现场并为异常提供处理程序,longjmp() 则进行跳转(抛出异常),() 与 longjmp() 可以在函数间进行跳转,这就像一个全局的 语句,可以跨函数跳转。

jmp_buf 异常结构

        使用 () 及 longjmp() 函数前,需要先认识一下 jmp_buf 异常结构。jmp_buf 将使用在 () 函数中,用于保存当前程序现场(保存当前需要用到的寄存器的值),jmp_buf 结构在 .h 文件内声明:

        typedef struct
        {
                unsigned j_sp;  // 堆栈指针寄存器
                unsigned j_ss;  // 堆栈段
                unsigned j_flag;  // 标志寄存器
                unsigned j_cs;  // 代码段
                unsigned j_ip;  // 指令指针寄存器
                unsigned j_bp; // 基址指针
                unsigned j_di;  // 目的指针
                unsigned j_es; // 附加段
                unsigned j_si;  // 源变址
                unsigned j_ds; // 数据段
        } jmp_buf;

        jmp_buf 结构存放了程序当前寄存器的值,以确保使用 longjmp() 后可以跳回到该执行点上继续执行。

() 与 longjmp() 函数都使用了 jmp_buf 结构作为形参,它们的调用关系是这样的:
        首先调用 () 函数来初始化 jmp_buf 结构变量 jmpb,将当前CPU中的大部分影响到程序执行的积存器存入 jmpb,为 longjmp() 函数提供跳转,() 函数是一个有趣的函数,它能返回两次,它应该是所有库函数中唯一一个能返回两次的函数,第一次是初始化时,返回零,第二次遇到 longjmp() 函数调用后,longjmp() 函数使 () 函数发生第二次返回,返回值由 longjmp() 的第二个参数给出(整型,这时不应该再返回零)。
        在使用 () 初始化 jmpb 后,可以其后的程序中任意地方使用 longjmp() 函数跳转会 () 函数的位置,longjmp() 的第一个参数便是 () 初始化的 jmpb,若想跳转回刚才设置的 () 处,则 longjmp() 函数的第一个参数是 () 所初始化的 jmpb 这个异常,这也说明一件事,即 jmpb 这个异常,一般需要定义为全局变量,否则,若是局部变量,当跨函数调用时就几乎无法使用(除非每次遇到函数调用都将 jmpb 以参数传递,然而明显地,是不值得这样做的);longjmp() 函数的第二个参数是传给 () 的第二次返回值,这在介绍 () 函数时已经介绍过。
        下面是 () 函数与 longjmp() 函数的一个示意图:

通过示意图可以看出 () 函数与 longjmp() 函数的关系,如何跳转。

呵呵!现在是否对程序的执行流程一目了然,其中最关键的就是setjjmp和longjmp函数的调用处理。我们分别来分析之。
  当程序运行到第②步时,调用函数,这个函数会保存程序当前运行的一些状态信息,主要是一些系统寄存器的值,如ss,cs,eip, eax,ebx,ecx,edx,eflags等寄存器,其中尤其重要的是eip的值,因为它相当于保存了一个程序运行的执行点。这些信息被保存到 mark变量中,这是一个C标准库中所定义的特殊结构体类型的变量。
  调用函数保存程序状态之后,该函数返回0值,于是接下来程序执行到第③步和第④步中。在第④步中语句执行时,如果变量n2为0值,于是便 引发了一个浮点数计算异常,,导致控制流转入fphandler函数中,也即进入到第⑤步。
  然后运行到第⑥步,调用longjmp函数,这个函数内部会从先前的所保存的程序状态,也即mark变量中,来恢复到以前的系统寄存器的 值。于是便进入到了第⑦步,注意,这非常有点意思,实际上,通过longjmp函数的调用后,程序控制流(尤其是eip的值)再次戏剧性地进入到了 函数的处理内部中,但是这一次返回的值是longjmp函数调用时,所传入的第2个参数,也即-1,因此程序接下来进入到了第⑧ 步的执行之中。
因为 jmp_buf 全局只能存放一个 jmpb 结构,使得只有最后一组宏可以响应异常;    这是无法嵌套异常的原因,要实现多重嵌套可以建立一个全局堆栈来维护一组 jmpb 结构
实现机理
()保存其调用返回点的ebx, esi, edi, ebp, esp, eip,对于sigsetjmp(), 还保存当前的信号屏蔽字, longjmp恢复这些寄存器及信号屏蔽字,同时传递一个返回值。
ongjmp只是使CPU的状态和时的状态一致,相同的CPU初始状态执行相同的代码并不意味着产生相同的结果,, longjmp并不关心内存变量的变化,而只关心CPU的状态,
内存变量的确定性应该根据上下文来灵活处理.
如果将函数之间的调用关系看成一种树型结构, 将可执行文件的入口函数看成它的最内层节点(根节点), 那么提供了标记节点的方法, longjmp提供了从外层节点直接返回到更内层节点的方法,
在同一层节点之间或者从内层节点到外层节点的longjmp是没有意义的, 因为目标节点在此时根本不存在.
摘要:讨论一个利用标准C语言库函烽实现查询式协作多任务系统,给出完整的内核和样例程序并对源代码进行说明。该系统具有简单易用的特点,只需要编写存取堆栈指针的宏就可方便地移植到新的平台上。文章详述了系统的优缺点,讨论一些性能扩展的方法。该内核适用
于中小规模的嵌入式软件。

关键词:协作式多任务 C语言

引言

本文介绍的是利用标准C语言库函数实现的具备此特点的协作式多任务系统。从本 质上讲,实时多任务操作系统应该具备按照优先级抢占调度的内核。然而,在实际应用中,抢中式的多任务某种程序上带来了用户程序设计时数据保护的困难,并 且,具备抢占功能的多任务内核设计时困难也比较多,这会增加操作系统自身的代码,也使它在小资源单片机系统中应用较少;而协作多任务系统的调度只在用户指 定的时机发生,这会大大简化内核和用户系统的设计,尤其本文实现的系统通过条件查询来放弃CPU,既符合传统单片机程序设计的思维,又带来了多任务、模块 化、可重入的编程便利。

是标准C语言库函数的组成部分,它可以实现程序执行中的远程转操作。具体来 说,它可以在一个函数中使用来初始化一个全局标号,然后只要该函数未曾返回,那么在其它任何地方都可以通过longjmp调用来跳转到 的下一条语句执行。实际上,函数将发生调用处的局部环境保存在一个jmp_buf的结构当中,只要主调函数中对应的内存未曾释放 (函数返回时局部内存就失效了),那么在调用longjmp的时候就可以根据已保存的jmp_buf参数恢复到的地方执行。我们的系统中就是 分析了标准库函数的特点,以简单的方式实现了协作式多任务。

1 演示程序

为了便于理解,首先给出多任务演示程序的源代码。这个程序演示了协作式多任务切换、任务的 动态生成、多任务共用代码等功能,一共使用了init_coos初始化根任务(也就是C语言main函数)、creat_task创建新任务和 WAITFOR查询条件这3个基本的系统调用。由于面向嵌入式系统,因而程序不会中止并且运行中也没有进行任何输出,需要借助适合的调试工具来理解多任务 系统的运行。

example.c文件清单:

#include<stdlib.h>

#include“co-.h”

void tskfunc1(int argc,void *argv);

void tskfunc2(int argc,void *argv);

void subfunc(void);

volatile int cnt,test;

int main(void){

int i;

init_coos(400);

creat_tsk(tskfunc1,12,NULL,400);

creat_tsk(tskfunc2,0,NULL,400);

i=0;

while(1){

WAITFOR(cnt= =8);

while(i++<cnt)test=i;

cnt++;

}

}

void tskfunc1(int argc,void *argv){

int i;

static int creat=0;

if(!creat){

creat_tsk(tskfunc1,9,NULL,400);

creat=1;

}

i=0;

while(1){

WAITFOR(cnt>argc);

test=0×55;

/*使用函数调用在子程序中测试WAITFOR*/

subfunc();

while(i++<cnt)test=i^0xAA;

}

}

void tskfunc2(int argc,void *argv){

while(1){

WAITFOR(++cnt>15);

cnt=0;

}

}

void subfunc(void){

int i;

WAITFOR(cnt<5);

for(i=0;i<++)test=0×10*i;

}

2 内核构成

内核包括一个供外部用户程序包含的头文件(co-.h)和具体实现的源文件(co-.c),它们提供了演示程序中用到的3个系统调用。

内核的实现代码假定了CPU堆栈是向下增长的,并且通过宏来直接操作堆栈指针。以下代码在 Microsoft VC6 for x86、Borland C++ Builder 5.5、SDS CrossCode7.0 for 68K和GCC3.2 for AVR四种平台中测试过,只需在co-.h头文件中定义相应的平台类型即可顺利编译。

(1)co-.h文件清单

#include<.h>

/*选择X86_VC6,X86_BC5,AVR_GCC或M68H_SDS.*/

#define X86_VC6

#define MAX_TSK 10

typedef struct {

void (*entry)(int argc,void *argv);

jmp_buf env;

int argc;

void *argv;

}TVB;

extern TCB tcb[MAX_TSK];

extern int task_num,tskid;

void init_coos(int mainstk);

int creat_tsk(void(*entry)(int argc,void *argv),int argc,void *argv,int stksize);

#define WAITFOR(condition)do{

(tcb[tskid].env);

if(!(condition)){

tskid++;

if(tskid>=task_num)tskid=0;

longijmp(tcb[tskid].env,1);

}

}while(0)

(2)co-.c文件清单

#include “co-.h”

#if defined(X86_VC6)||defined(X86_BC5)

#define SAVE_SP(p) _asm mov p,esp

#define RESTORE_SP(p) _asm mov esp,p

#elif defined(AVR_GCC)

#include<io.h>

#define SAVE_SP(p) p=(int*)SP

#define RESTORE_SP(p) SP=(int)p

#elif defined(M68K_SDS)

#define SAVE_SP(p) asm(”MOVE.L A7,{”#p”}”)

#define RESTORE_SP(p) asm(”MOVE.L {”#p”},A7″)

#endif

TCB tcb[MAX_TSK];

Int task_num=1;

Int tskid;

Static int stktop,oldsp;

Void init_coos(int mainstk){

SAVE_SP(stktop);

stktop=stktop+sizeof(void(*)(void))/sizeof(int)

-(mainstk+sizeof(int)-1)/sizeof(int);

}

int creat_tsk(void(*entry)(int argc,void *argv),

int argc,void *argv,int stksize){

if(task_num>=MAX_TSK)terurn-1;

SAVE_SP(oldsp);

RESTORE_SP(stktop);

If(!(tcb[task_num].env)){

RESTORE_SP(oldsp);

tcb[task_num].entry=entry;

tcb[task_num].argc=argc;

tcb[task_num].argv=argv;

task_num++;

stktop-=(stksize+sizeof(int)-1)/sizeof(int);

}

else tcb[tskid].entry(tcb[tskid].argc,tcb[tskid].argv);

return 0;

}

3 代码说明

任务代码通过执行设置本任务下次查询时的返回点,然后在等待条件放弃掉CPU 跳转到下一任务的返回点处执行。如此周而复始,让各任务都获得轮转运行的机会,也要求各任务都需要主动通过等待条件的方式放弃掉CPU。系统中除了中断服 务程序之外,所有任务都是平等的,都应该遵循同样的规则和其它任务一起协作运行。基本系统中没有设计杀死任务的调用,这要求各任务都应当设计成某种形式的 无限循环。

任务中等待的条件可以是任务复杂的表达式或都函数调用,也可以是中断服务程序设置的全局变 量(注意加volatile定义)。一般在任务执行时会让下次等待的条件不再满足,避免某个任务一直霸占CPU将系统饿死。在嵌入式软件中还经常会遇到任 务定时启动和超时等待在I/O操作上,在我们的系统中可以维护一个时间计数器,只需在适当的地方记录时刻,然后在任务查询条件中判断当前计数器和记录时刻 之间的差值就可以了。

内核实现的代码则相当简法。由于主要的保护和恢复任务现场的工作都由C语言标准库 实现了,我们就只需要操纵一下堆栈指针防止不同的任务使用了重叠的局部环境,这个工作在初始化和创建任务的时候通过预定义的两个宏来实现。在 init_coos函数中,记录了主任务(main函数)保留mainstk字节堆栈后的新栈顶位置(stktop),然后在每次creat_task时 都根据要求为每个任务保留stksize字节的堆栈并重新计算下一个stktop。在creat_task函数中利用了函数的返回值(直接返 回时为0,longjmp跳转返回时非0),使得一方面creat_task能正常回到调用者,又让下次轮转到新任务时能够找到创建时的入口。

co-.h中定义的WAITFOR使用了一个do{…while(0)实现多语句宏的 C语言小技巧,这样能保证在任何情况下WAITFOR都可以如单条语句一样在源程序中使用,需要担心多了或者少了大括弧破坏if/else匹配之类的问 题,并且,所有的编译器都会优化掉这个假循环。

为了尽量使程序简单并说明问题,以上代码中没有考虑中断相关的数据保护问题。实际运行的系 统中,如果首先写堆栈指针不能一步完成(如AVR这样的8位机),那么,在写操作正在进行的时候绝对不能允许中断;另外,在任务中查询的条件如果和中断有 关,那么也必须考虑数据的完整性。

4 性能分析

所有的协作式多任务系统中任务切换时间都和用户代码(是否长期占用CPU)、就绪时刻有 关,比标准系统略差的是,我们的简单系统中不支持任务优先级,并且完成一轮查询调度的时间还和任务数目有关。这也是为了达到简单和可移植性目标而不得已作 出的牺牲。在各任务间切换查询条件通过C语言标准库函数longjmp实现,一般来讲longjmp函数要从内存中恢复大部分CPU寄存器,执行它也需要 若干条指令的时间。

为了面向嵌入式系统应用,任务控制块(TCB)采用静态数组来实现,这样要求预先确定系统 的最大任务数(co-.h中的MAX_TSK)。如果需要,也可以通过环波链表来动态管理任务控制块(TCB),这时可以简单实现任务的动态创建和删 除,并且通过指针来访问TCB也要比通过下标(tskid)略快一点。

在某些情况下,如果在中断返回后需要执行某关键任务,可以考虑通过设置“高级中断”的方法 来实现。具体地讲,就是在中断返回前改变返回地址到某函数入口(“高级中断服务程序”),同时保留原返回地址到堆栈中,这样在“高级中断服务程序”完成后 执行return就又回到了正常的多任务查询流程。使用“高级中断”时要注意现场保护的衔接,并且这种技巧显然和CPU和体系结构有关。

5 结论

是标准C语言中用于远程跳转的库函数,利用它可方便实现一个简单易移植的协作式多任务系统。该系统功能完备、编程简单、易于学习,适合一些中小规模的嵌入式软件使用;并且,以此为基础,还可以用一些与平台相关的编程技巧提高其实时性和灵活性。

部分资料来源于希赛,单片机与嵌入式系统应用

Tags: , , , , , , , , , , , , ,

25 Nov 07 [分享] 某知名手机平台的XML Parser源代码

    今天心情不错~ 分享一下小弟06年在某手机公司写的 parser.
虽然当时脑子里还没有FSM的概念, 但代码逻辑还算清晰, 颇有成就感!
结构比较简单, 按DOM方式把指定文件解析成节点树, 另外提供几个简单的查找函数.
部分功能等完善, 过段时间再发一份功能比较完善的C++版本.

PLX_XMLParser.h

#if _MSC_VER > 1000
#pragma once
#endif

#ifndef __XMLPARSE_H
#define __XMLPARSE_H

#include

//////////////////////////////
// Configure

#define USE_INLINE_FUNCTION
#define USE_FILEBUFFER

//////////////////////////////
// Constants

typedef enum {
XMLERR_OK       = 0×0,
XMLERR_EFILE,       // failed to open file
XMLERR_ALRDOPEN,    // already opened
XMLERR_EDOC,
XMLERR_EPARSE,
} XMLERR;

typedef enum {
NODETYPE_UNKN    = 0×0,
NODETYPE_ELEM   = 0×1,
NODETYPE_TEXT   = 0×2,
NODETYPE_COMM   = 0×4,
NODETYPE_INST   = 0×8, // Not support
//NODETYPE_USEFUL = NODETYPE_ELEM|NODETYPE_TEXT,
//NODETYPE_ALL    = NODETYPE_ELEM|NODETYPE_TEXT|NODETYPE_COMM|NODETYPE_INST,
} NODETYPE;

typedef enum {
DSTAT_UNOPEN    = 0×0,
DSTAT_OPENED    = 0×1,
} DOCSTAT;

typedef enum {
ISTAT_STOP      = 0×0,
ISTAT_CONTINUE  = 0×1,
ISTAT_PASS      = 0×2,
} ITERSTAT;

enum {    MAXLEN_BSTR    = 256 };

//////////////////////////////
// Structures

struct tagBString
{
LONG    m_lLength;
union   {
CHAR    m_paStr[1];
LPCSTR  m_pszStr;
};
};

typedef struct tagBString       BSTRING;
typedef struct tagBString       *LPBSTRING;
typedef struct tagBString const *LPCBSTRING;

struct tagXMLAttrib;
typedef struct tagXMLAttrib            XMLATTRIB;
typedef struct tagXMLAttrib            *LPXMLATTRIB;
typedef struct tagXMLAttrib    const    *LPCXMLATTRIB;

struct tagXMLAttrib
{
LPBSTRING   m_pbstrName;
LPBSTRING   m_pbstrValue;
LPXMLATTRIB    m_pNext;
};

struct tagXMLNode;
typedef struct tagXMLNode       XMLNODE;
typedef struct tagXMLNode       *LPXMLNODE;
typedef struct tagXMLNode const *LPCXMLNODE;

struct tagXMLNode
{
NODETYPE    m_eNodeType;
LONG        m_lDepth;

LPBSTRING   m_pbstrTag;

LONG        m_lChildNum;
LONG        m_lChildNum_Elem;

LPXMLNODE    m_pRoot;
LPXMLNODE   m_pParent;
LPXMLNODE   m_pFirstChild;
LPXMLNODE   m_pLastChild;
LPXMLNODE   m_pPrevSibling;
LPXMLNODE   m_pNextSibling;

LONG        m_lAttribNum;
LPXMLATTRIB m_pFirstAttrib;
};

struct tagXMLDocument
{
DOCSTAT     m_eDocStat;
LPXMLNODE   m_lpRootNode;
};

typedef struct tagXMLDocument       XMLDOCUMENT;
typedef struct tagXMLDocument       *LPXMLDOCUMENT;
typedef struct tagXMLDocument const *LPCXMLDOCUMENT;

//////////////////////////////
// Types

typedef ITERSTAT    (CALLBACK *LPFNNODEPROC)( LPCXMLNODE pNode, LPVOID pvParam );

//////////////////////////////
// Macros

#if    defined(USE_INLINE_FUNCTION)

#define BSTR_C( pBStr )                     (&((pBStr)->m_paStr[0]))
#define BSTR_CAST( pvAnyType )              ((LPBSTRING)pvAnyType)
#define    BSTR_LEN( pBStr )                   ((pBStr)->m_lLength)
#define BSTR_ALLOC( pszStr )                AllocBString( pszStr, (NULL != (pszStr) ? ((LONG)strlen(pszStr)) : (0L)) )
#define BSTR_ALLOCEX( pszStr, nLen )        AllocBString( pszStr, (LONG)nLen )
#define BSTR_FREE( pBStr )                  free( pBStr )
#define BSTR_SAFEFREE( pBStr )              if ( NULL != pBStr ) { free( pBStr ); pBStr = NULL; }
#define BSTR_EQUAL( pBStrL, pBStrR ) \
( (BSTR_LEN(pBStrL) == BSTR_LEN(pBStrR) && 0 == strcmp(BSTR_C(pBStrL), BSTR_C(pBStrL))) ? \
TRUE : FALSE )
#define BSTR_EQUAL_STATIC( pBStr, szStatic ) \
( (BSTR_LEN(pBStr) == (LONG)(sizeof(szStatic) – 1) && 0 == strcmp(BSTR_C(pBStr), szStatic)) ? \
TRUE : FALSE )
#define BSTR_EQUAL_CSTR( pBStr, pszStr ) \
( (BSTR_LEN(pBStr) == (LONG)strlen(pszStr) && 0 == strcmp(BSTR_C(pBStr), pszStr)) ? \
TRUE : FALSE )

#define    XML_GetRootNode( pDoc )             ((NULL == (pDoc) || (pDoc)->m_eDocStat != DSTAT_OPENED) ? NULL : (pDoc)->m_lpRootNode)
#define    XML_GetNodeType( pNode )             (NULL != (pNode) ? (pNode)->m_eNodeType : NODETYPE_UNKN)
#define    XML_GetNodeDepth( pNode )            (NULL != (pNode) ? (pNode)->m_lDepth : (-1L))
#define    XML_GetNodeParent( pNode )             (NULL != (pNode) ? (pNode)->m_pParent : NULL)
#define    XML_GetNodeFirstChild( pNode )         (NULL != (pNode) ? (pNode)->m_pFirstChild : NULL)
#define    XML_GetNodeLastChild( pNode )         (NULL != (pNode) ? (pNode)->m_pLastChild : NULL)
#define    XML_GetNodeChildNum( pNode )         (NULL != (pNode) ? (pNode)->m_lChildNum : (0L))
#define    XML_GetNodeChildNum_Elem( pNode )     (NULL != (pNode) ? (pNode)->m_lChildNum_Elem : (0L))
#define    XML_GetNodePrevSibling( pNode )     (NULL != (pNode) ? (pNode)->m_pPrevSibling : NULL)
#define    XML_GetNodeNextSibling( pNode )     (NULL != (pNode) ? (pNode)->m_pNextSibling : NULL)
#define    XML_GetNodeTagName( pNode )         (NULL != (pNode) ? (pNode)->m_pbstrTag : NULL)
#define    XML_GetNodeAttribNum( pNode )         (NULL != (pNode) ? (pNode)->m_lAttribNum : (0L))
#define    XML_GetNodeFirstAttrib( pNode )     (NULL != (pNode) ? (pNode)->m_pFirstAttrib : NULL)
#define    XML_GetNodeNextAttrib( pAttr )         (NULL != (pAttr) ? (pAttr)->m_pNext : NULL)
#define XML_GetAttribValueBString( pAttr )  (NULL != (pAttr) ? (pAttr)->m_pbstrValue) : NULL)
#define XML_GetAttribValueCString( pAttr )  (NULL != (pAttr) ? BSTR_C((pAttr)->m_pbstrValue) : NULL)
#define XML_GetAttribValueLong( pAttr )     (NULL != (pAttr) ? (LONG)strtol(BSTR_C((pAttr)->m_pbstrValue), NULL, 0) : (0L))
#define XML_GetAttribValueInt( pAttr )      (NULL != (pAttr) ? (int)strtol(BSTR_C((pAttr)->m_pbstrValue), NULL, 0) : (0))

#endif

//////////////////////////////
// Function prototypes

#if defined(__cplusplus)
extern “C” {
#endif

size_t      strlen_when( LPCSTR lpszStr, CHAR ch );
size_t        strlen_notin( LPCSTR lpszStr, LPCSTR lpszSet );
LPCSTR        strchr_notin( LPCSTR lpszStr, LPCSTR lpszSet );
LPCSTR        strchr_skipws( LPCSTR lpszStr );

LPBSTRING   AllocBString( LPCSTR lpszStr, LONG lLen );

XMLERR      XML_OpenDocument( LPXMLDOCUMENT pDoc, LPCSTR lpszFileName, DWORD dwReserve );
XMLERR      XML_CloseDocument( LPXMLDOCUMENT pDoc );
LPXMLNODE    XML_GetNode( LPXMLDOCUMENT pDoc, LPXMLNODE pStartPoint, LPCSTR lpszTag, LONG lLen );
LPXMLATTRIB    XML_GetNodeAttrib( LPXMLNODE pNode, LPCSTR lpszAttr, LONG lLen );
LPXMLNODE    XML_GetNodeSibling( LPXMLNODE pNode, LPCSTR lpszTag, LONG lLen, BOOL IncludeThis );
LONG         XML_ForEachNode( LPXMLDOCUMENT pDoc, LPXMLNODE pStartPoint, LPFNNODEPROC pfnNodeProc, LPVOID pvParam );

#if defined(__cplusplus)
}   // extern “C” {
#endif

#endif // #ifndef __XMLPARSE_H

PLX_XMLParser.c

#include <assert.h>
#include <fcntl.h>
#include <io.h>
#if defined(DEBUG) || defined(_DEBUG)
#include <stdio.h>
#endif

#include “XMLParse.h”

//#pragma warning(disable:4305)

//////////////////////////////
// Configure

//#define  USE_MEMORY_HEAP

#ifdef  USE_FILEBUFFER  // Whether use file system with -layer buffer

#define INVALID_FILE_HANDLE         ((int)-1)
#define FILE_HANDLE                 int

#define MODE_RDONLY                 (O_RDONLY)
#define FILE_OPEN(pszFile,mode)     open( pszFile, mode )
#define FILE_READ(hFile,pbuf,size)  read(hFile, (LPVOID)pbuf, size)
#define FILE_SEEK(hFile,off,pos)    lseek( hFile, off, pos )
#define FILE_CLOSE(hFile)           close( hFile )

#else

#define INVALID_FILE_HANDLE         ((FILE *)NULL)
#define FILE_HANDLE                 FILE *

#define MODE_RDONLY                 (”r”)
#define FILE_OPEN(pszFile,mode)     fopen( pszFile, mode )
#define FILE_READ(hFile,pbuf,size)  fread((LPVOID)pbuf, size, 1, hFile)
#define FILE_SEEK(hFile,off,pos)    fseek( hFile, off, pos )
#define FILE_CLOSE(hFile)           fclose( hFile )

#endif

//////////////////////////////
// Constants

enum {    MAXLEN_READBUF    = 1024 };

typedef enum {
PSTAT_STOP      = 0×0,
PSTAT_INITIAL,
PSTAT_FINAL,
PSTAT_DECL_BEG,
PSTAT_DECL_END,
PSTAT_ELEM_BEG,
PSTAT_ELEM_END,
PSTAT_TEXT_BEG,
PSTAT_TEXT_END,
PSTAT_CDATA_BEG,
PSTAT_CDATA_END,
PSTAT_COMM_BEG,
PSTAT_COMM_END,
PSTAT_ERROR,
} PARSESTAT;

//////////////////////////////
// Macros

#define ZERO_MEMORY(p, size) \
( memset((LPVOID)(p), 0×0, (size_t)size) )

#define is_WhiteSpace(ch)   (((ch) == ‘ ‘  || (ch) == ‘\t’) ? TRUE : FALSE)
#define is_LineBreak(ch)    (((ch) == ‘\r’ || (ch) == ‘\n’) ? TRUE : FALSE)
#define is_LeftBracket(ch)    ((ch) == ‘<’ ? TRUE : FALSE)
#define is_RightBracket(ch)    ((ch) == ‘>’ ? TRUE : FALSE)

#define is_BufferEmpty(ps)  ((ps)->m_lReadCursor >= (ps)->m_lReadSize ? TRUE : FALSE)
#define get_BufferChar(ps)  ((CHAR)((ps)->m_aReadBuf[(ps)->m_lReadCursor]))

#define is_FirstChild(pn)    ((pn)->m_pPrevSibling == NULL ? TRUE : FALSE)
#define is_LastChild(pn)    ((pn)->m_pNextSibling == NULL ? TRUE : FALSE)

//////////////////////////////
// Structures

struct tagXMLParseStat
{
PARSESTAT   m_eParseStat;
LONG        m_lDepth;
LPXMLNODE    m_pRootNode;
LPXMLNODE    m_pLastNode;

FILE_HANDLE    m_hOpenFile;
LONG        m_lFileCursor;
LONG        m_lFileLength;

LONG        m_lLineNo;
LONG        m_lReadCursor;
LONG        m_lReadSize;
BYTE        m_aReadBuf[MAXLEN_READBUF];
};
typedef struct tagXMLParseStat            XMLPARSESTAT;
typedef struct tagXMLParseStat            *LPXMLPARSESTAT;
typedef struct tagXMLParseStat    const    *LPCXMLPARSESTAT;

struct tagXMLIterator
{
LONG            m_lCount;
LPFNNODEPROC    m_pfnProc;
LPVOID          m_pvParam;
LPXMLNODE       m_pStation;
};
typedef struct tagXMLIterator       XMLITERFATOR;
typedef struct tagXMLIterator       *LPXMLITERFATOR;
typedef struct tagXMLIterator const *LPCXMLITERFATOR;

/*struct tagMemoryPage
{
LONG    lGranu ;
LONG    lSize;
LPVOID  pvPage;
BYTE    bUseFlags[1];
};
typedef struct tagMemoryPage        MEMORYPAGE;
typedef struct tagMemoryPage        *LPMEMORYPAGE;
typedef struct tagMemoryPage const  *LPCMEMORYPAGE;*/

//////////////////////////////
// Function prototypes

#if defined(__cplusplus)
extern “C” {
#endif

static void        XML_FreeNodeTree( LPXMLNODE pNode );
static void        XML_FreeAttribList( LPXMLATTRIB pAttrib );
static void     XML_IterateTree( LPXMLNODE pTree, LPXMLITERFATOR pIterator );
static BOOL     XMLCmpNode_EqualTag( LPCXMLNODE pNode, LPVOID pvParam );

static BOOL        Parser_RoutineStart( LPXMLPARSESTAT pParseStat, LPXMLDOCUMENT pResult );
static int        Parser_GetTagString( LPXMLPARSESTAT pParseStat, LPSTR lpszBuf );
static BOOL        Parser_ReadStream( LPXMLPARSESTAT pParseStat );
static void        Parser_OnInitial( LPXMLPARSESTAT pParseStat, LPSTR lpszTag );
static void        Parser_OnElemBegin( LPXMLPARSESTAT pParseStat, LPSTR lpszTag );
static void        Parser_OnElemEnd( LPXMLPARSESTAT pParseStat, LPSTR lpszTag );
static void        Parser_OnTextBegin( LPXMLPARSESTAT pParseStat, LPSTR lpszTag );
static void        Parser_OnTextEnd( LPXMLPARSESTAT pParseStat, LPSTR lpszTag );
static void        Parser_OnCDATABegin( LPXMLPARSESTAT pParseStat, LPSTR lpszTag );
static void        Parser_OnCDATAEnd( LPXMLPARSESTAT pParseStat, LPSTR lpszTag );
static void        Parser_OnCommBegin( LPXMLPARSESTAT pParseStat, LPSTR lpszTag );
static void        Parser_OnCommEnd( LPXMLPARSESTAT pParseStat, LPSTR lpszTag );
static void        Parser_OnDeclBegin( LPXMLPARSESTAT pParseStat, LPSTR lpszTag );
static void        Parser_OnDeclEnd( LPXMLPARSESTAT pParseStat, LPSTR lpszTag );
static BOOL        Parser_OnFinal( LPXMLPARSESTAT pParseStat );
static BOOL        Parser_OnError( LPXMLPARSESTAT pParseStat );

//#if defined(USE_MEMORY_HEAP)
//static LPVOID   MemoryHeap_Create( LONG lInitialGranu, LONG lInitialSize );
//static BOOL     MemoryHeap_Destroy( LPVOID );
//static LPVOID   MemoryHeap_Alloc( void );
//static void     MemoryHeap_Free( LPVOID pvBlock );
//#endif

#if defined(__cplusplus)
}   // extern “C” {
#endif

//////////////////////////////
// Function implementations

#if defined(__cplusplus)
extern “C” {
#endif

/*********************************************************************\
* Function: strlen_when
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
size_t strlen_when( LPCSTR lpszStr, CHAR ch )
{
register size_t nLen;
assert( NULL != lpszStr );
for ( nLen = 0; *lpszStr != ch && *lpszStr != ‘\0′;    lpszStr++, nLen++ )
;
return nLen;
}

/*********************************************************************\
* Function: strlen_notin
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
size_t strlen_notin( LPCSTR lpszStr, LPCSTR lpszSet )
{
register size_t nLen;
assert( NULL != lpszStr );
for ( nLen = 0; *lpszStr != ‘\0′ && NULL == strchr(lpszSet, *lpszStr); lpszStr++, nLen++ )
;
return nLen;
}

/*********************************************************************\
* Function: strchr_notin
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
LPCSTR strchr_notin( LPCSTR lpszStr, LPCSTR lpszSet )
{
assert( NULL != lpszStr );
for ( ; *lpszStr != ‘\0′ && NULL == strchr(lpszSet, *lpszStr); lpszStr++ )
;
return lpszStr;
}

/*********************************************************************\
* Function: strchr_skipws
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
LPCSTR strchr_skipws( LPCSTR lpszStr )
{
assert( NULL != lpszStr );
for ( ; *lpszStr != ‘\0′ && is_WhiteSpace(*lpszStr); lpszStr++ )
;
return lpszStr;
}

/*********************************************************************\
* Function: AllocBString
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
LPBSTRING AllocBString( LPCSTR lpszStr, LONG lLen )
{
LPBSTRING pBStr = (LPBSTRING)malloc( sizeof(LONG) + lLen + 1 );
assert( NULL != pBStr );

pBStr->m_lLength = lLen;
strncpy( &pBStr->m_paStr[0], lpszStr, lLen );
pBStr->m_paStr[lLen] = ‘\0′;

return pBStr;
}

/*********************************************************************\
* Function: XML_OpenDocument
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
XMLERR XML_OpenDocument( LPXMLDOCUMENT pDoc, LPCSTR lpszFileName, DWORD dwReserve )
{
BOOL            bRet;
FILE_HANDLE        hFile;
XMLPARSESTAT    xmlParseStat;

assert( NULL != pDoc );
assert( NULL != lpszFileName );

if ( pDoc->m_eDocStat == DSTAT_OPENED )
return XMLERR_ALRDOPEN;

ZERO_MEMORY( &xmlParseStat, sizeof(XMLPARSESTAT) );

hFile = FILE_OPEN( lpszFileName, MODE_RDONLY );
if ( INVALID_FILE_HANDLE == hFile )
return XMLERR_EFILE;

xmlParseStat.m_hOpenFile    = hFile;
xmlParseStat.m_lFileCursor    = 0;
xmlParseStat.m_lFileLength    = FILE_SEEK( hFile, 0, SEEK_END );
FILE_SEEK( hFile, 0, SEEK_SET );

xmlParseStat.m_lDepth = 0;
xmlParseStat.m_lLineNo = 1;
bRet = Parser_RoutineStart( &xmlParseStat, pDoc );
if ( FALSE == bRet )
{
FILE_CLOSE( hFile );
return XMLERR_EPARSE;
}

pDoc->m_eDocStat    = DSTAT_OPENED;
pDoc->m_lpRootNode    = xmlParseStat.m_pRootNode;
FILE_CLOSE( hFile );
return XMLERR_OK;
}

/*********************************************************************\
* Function: XML_CloseDocument
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
XMLERR XML_CloseDocument( LPXMLDOCUMENT pDoc )
{
if ( NULL != pDoc && pDoc->m_eDocStat == DSTAT_OPENED )
{
XML_FreeNodeTree( pDoc->m_lpRootNode );
pDoc->m_lpRootNode = NULL;
pDoc->m_eDocStat = DSTAT_UNOPEN;
}
return XMLERR_OK;
}

/*********************************************************************\
* Function: Parser_GetTagString
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
int Parser_GetTagString( LPXMLPARSESTAT pParseStat, LPSTR lpszBuf )
{
int        nLen;
CHAR    ch;
CHAR    chTerm;
BOOL    bInclude;

// Should to ensure barket match Here!

nLen = 0;
for ( ;; )
{
if ( is_BufferEmpty(pParseStat) && FALSE == Parser_ReadStream(pParseStat) )
__RET;

ch = get_BufferChar(pParseStat);
if ( ch == ‘\n’ )
pParseStat->m_lLineNo++;

if ( !is_WhiteSpace(ch) && !is_LineBreak(ch) )
break;

pParseStat->m_lReadCursor++;
}

if ( is_LeftBracket(ch) )
{
chTerm = ‘>’;
bInclude = TRUE;
}
else
{
chTerm = ‘<’;
bInclude = FALSE;
}

for ( ;; )
{
if ( is_BufferEmpty(pParseStat) && FALSE == Parser_ReadStream(pParseStat) )
break;

ch = get_BufferChar(pParseStat);
if ( ch == ‘\n’ )
pParseStat->m_lLineNo++;

if ( ch == chTerm )
{
if ( FALSE != bInclude )
{
lpszBuf[nLen++] = chTerm;
pParseStat->m_lReadCursor++;
}
break;
}

lpszBuf[nLen++] = ch;
pParseStat->m_lReadCursor++;
}

__RET:
lpszBuf[nLen] = ‘\0′;
return nLen;
}

/*********************************************************************\
* Function: Parser_ReadStream
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
BOOL Parser_ReadStream( LPXMLPARSESTAT pParseStat )
{
size_t    nSize;

pParseStat->m_lReadCursor    = 0;
pParseStat->m_lReadSize    = 0;
nSize = FILE_READ( pParseStat->m_hOpenFile, (LPVOID)&pParseStat->m_aReadBuf[0], MAXLEN_READBUF );
if ( nSize <= 0 )
return FALSE;

pParseStat->m_lReadSize        = (LONG)nSize;
pParseStat->m_lFileCursor  += (LONG)nSize;
return TRUE;
}

/*********************************************************************\
* Function: Parser_RoutineStart
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
BOOL Parser_RoutineStart( LPXMLPARSESTAT pParseStat, LPXMLDOCUMENT pResult )
{
BOOL    bRet;
CHAR    szTag[256];

assert( NULL != pParseStat );

pParseStat->m_eParseStat = PSTAT_INITIAL;
while ( pParseStat->m_eParseStat != PSTAT_STOP )
{
switch ( pParseStat->m_eParseStat )
{
case PSTAT_INITIAL:     Parser_OnInitial( pParseStat, &szTag[0] );        break;

case PSTAT_DECL_BEG:    Parser_OnDeclBegin( pParseStat, &szTag[0] );    break;
case PSTAT_DECL_END:    Parser_OnDeclEnd( pParseStat, &szTag[0] );        break;

case PSTAT_ELEM_BEG:    Parser_OnElemBegin( pParseStat, &szTag[0] );    break;
case PSTAT_ELEM_END:    Parser_OnElemEnd( pParseStat, &szTag[0] );      break;

case PSTAT_TEXT_BEG:    Parser_OnTextBegin( pParseStat, &szTag[0] );    break;
case PSTAT_TEXT_END:    Parser_OnTextEnd( pParseStat, &szTag[0] );        break;

case PSTAT_CDATA_BEG:    Parser_OnCDATABegin( pParseStat, &szTag[0] );    break;
case PSTAT_CDATA_END:    Parser_OnCDATAEnd( pParseStat, &szTag[0] );        break;

case PSTAT_COMM_BEG:    Parser_OnCommBegin( pParseStat, &szTag[0] );    break;
case PSTAT_COMM_END:    Parser_OnCommEnd( pParseStat, &szTag[0] );        break;

case PSTAT_FINAL:        bRet = Parser_OnFinal( pParseStat );            break;
case PSTAT_ERROR:        bRet = Parser_OnError( pParseStat );            break;

default:
assert( !”Unknown parsing status!” );
break;
}
}
return bRet;
}

/*********************************************************************\
* Function: Parser_OnInitial
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
void Parser_OnInitial( LPXMLPARSESTAT pParseStat, LPSTR lpszTag )
{
int     nLen;
assert( NULL != lpszTag );

lpszTag[0] = ‘\0′;

nLen = Parser_GetTagString( pParseStat, lpszTag );
if ( nLen <= 0 )
{
pParseStat->m_eParseStat = PSTAT_STOP;
return;
}

if ( lpszTag[0] == ‘<’  )
{
if ( lpszTag[1] == ‘?’)
{
pParseStat->m_eParseStat = PSTAT_DECL_BEG;
}
else if ( !strncmp(&lpszTag[1], “!–”, 3) )
{
pParseStat->m_eParseStat = PSTAT_COMM_BEG;
}
else if ( lpszTag[1] == ‘/’ )
{
pParseStat->m_eParseStat = PSTAT_ELEM_END;
}
else if ( isalpha(lpszTag[1]) )
{
pParseStat->m_eParseStat = PSTAT_ELEM_BEG;
}
else if ( !strncmp(&lpszTag[1], “![CDATA[", 8) )
{
pParseStat->m_eParseStat = PSTAT_CDATA_BEG;
}
else
{
pParseStat->m_eParseStat = PSTAT_ERROR;
}
}
else if ( NULL != pParseStat->m_pLastNode )
{
pParseStat->m_eParseStat = PSTAT_TEXT_BEG;
}
else
{
pParseStat->m_eParseStat = PSTAT_ERROR;
}
}

/*********************************************************************\
* Function: Parser_OnFinal
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
BOOL Parser_OnFinal( LPXMLPARSESTAT pParseStat )
{
pParseStat->m_eParseStat = PSTAT_STOP;
return TRUE;
}

/*********************************************************************\
* Function: Parser_OnError
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
BOOL Parser_OnError( LPXMLPARSESTAT pParseStat )
{
XML_FreeNodeTree( pParseStat->m_pRootNode );
pParseStat->m_pRootNode     = NULL;
pParseStat->m_eParseStat = PSTAT_STOP;
TRACE( "[ Parser]: Syntax error @ %s #%ld.\n”, “”, pParseStat->m_lLineNo);
return FALSE;
}

/*********************************************************************\
* Function: Parser_OnDeclBegin
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
void Parser_OnDeclBegin( LPXMLPARSESTAT pParseStat, LPSTR lpszTag )
{
int nLenTag;

if ( NULL != pParseStat->m_pRootNode )
{
pParseStat->m_eParseStat = PSTAT_ERROR;
return;
}

lpszTag = (LPSTR)strchr_skipws( (LPCSTR)(lpszTag + 2) );
if ( lpszTag == ‘\0′ )
{
pParseStat->m_eParseStat = PSTAT_ERROR;
return;
}

nLenTag = (int)strlen_notin(lpszTag, ” \t\r\n>”);
if ( nLenTag == 3 && !strncmp(lpszTag, “”, 3) )
{
// Here, dispose version and coding infomation in document header
}
else if ( nLenTag == 14 && !strncmp(lpszTag, “-stylesheet”, 3) )
{
// Unsupport
}
else
{
// Unknown declaretion
}

pParseStat->m_eParseStat = PSTAT_INITIAL;
}

/*********************************************************************\
* Function: Parser_OnDeclEnd
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
void Parser_OnDeclEnd( LPXMLPARSESTAT pParseStat, LPSTR lpszTag )
{
// Do noting
}

/*********************************************************************\
* Function: Parser_OnElemBegin
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
void Parser_OnElemBegin( LPXMLPARSESTAT pParseStat, LPSTR lpszTag )
{
int         nLenTag;
int         nLenValue;
LPSTR       lpszValue;
LPXMLNODE   pNode;
LPXMLATTRIB    pAttrib;
LPXMLATTRIB pPrevAttr;

assert( is_LeftBracket(lpszTag[0]) );

// Multi-root node is not supproted
if ( 0 == pParseStat->m_lDepth && NULL != pParseStat->m_pRootNode )
__ERROR;

pNode = (LPXMLNODE)malloc(sizeof(XMLNODE));
if ( NULL == pNode )
__ERROR;

ZERO_MEMORY( pNode, sizeof(XMLNODE) );
pNode->m_eNodeType    = NODETYPE_ELEM;
pNode->m_lDepth        = pParseStat->m_lDepth;
if ( NULL != pParseStat->m_pLastNode )
{
assert( pNode->m_lDepth >= pParseStat->m_pLastNode->m_lDepth );
if ( pNode->m_lDepth > pParseStat->m_pLastNode->m_lDepth ) // new child
{
pNode->m_pParent = pParseStat->m_pLastNode;
pParseStat->m_pLastNode->m_pFirstChild = pNode;
}
else if ( pNode->m_lDepth == pParseStat->m_pLastNode->m_lDepth ) // new sibling
{
pNode->m_pParent = pParseStat->m_pLastNode->m_pParent;
pParseStat->m_pLastNode->m_pNextSibling = pNode;
pNode->m_pPrevSibling = pParseStat->m_pLastNode;
}

if ( NULL != pNode->m_pParent )
{
pNode->m_pParent->m_lChildNum++;
pNode->m_pParent->m_lChildNum_Elem++;
}
}

if ( NULL == pParseStat->m_pRootNode )
{
pParseStat->m_pRootNode = pNode;
}
pNode->m_pRoot = pParseStat->m_pRootNode;

pParseStat->m_pLastNode    = pNode;

lpszTag++;
nLenTag = (int)strlen_notin(lpszTag, ” \t\r\n>”);
if ( nLenTag <= 0 )
__ERROR;

pNode->m_pbstrTag = BSTR_ALLOCEX(lpszTag, nLenTag);
if ( NULL == pNode->m_pbstrTag )
__ERROR;

lpszTag = (LPSTR)(lpszTag + nLenTag);

for ( pPrevAttr = NULL;; )
{
lpszTag = (LPSTR)strchr_skipws( lpszTag );

if ( is_RightBracket(*lpszTag) )
{
pParseStat->m_lDepth++; // Move down one layer
pParseStat->m_eParseStat = PSTAT_INITIAL;
break;
}
else if ( !strncmp(lpszTag, “/>”, 2) )
{
//pParseStat->m_lDepth–; //
pParseStat->m_eParseStat = PSTAT_INITIAL;
break;
}

nLenTag = (int)strlen_when( lpszTag, ‘=’ );
if ( nLenTag <= 0 )
__ERROR;

lpszValue = strchr( (LPCSTR)(lpszTag + nLenTag), ‘\”‘ );
if ( NULL == lpszValue )
__ERROR;

lpszValue++;
nLenValue = (int)strlen_when( lpszValue, ‘\”‘ );

//if ( nLenValue <= 0 )
//    __ERROR;

pAttrib = (LPXMLATTRIB)malloc(sizeof(XMLATTRIB));
if ( NULL == pAttrib )
__ERROR;
ZERO_MEMORY( pAttrib, sizeof(XMLATTRIB) );

pAttrib->m_pbstrName    = BSTR_ALLOCEX( lpszTag, nLenTag );
pAttrib->m_pbstrValue    = BSTR_ALLOCEX( lpszValue, nLenValue );
if ( NULL == pAttrib->m_pbstrName || NULL == pAttrib->m_pbstrValue )
__ERROR;

if ( NULL == pNode->m_pFirstAttrib )
pNode->m_pFirstAttrib = pAttrib;

if ( NULL != pPrevAttr )
pPrevAttr->m_pNext = pAttrib;
pPrevAttr = pAttrib;

pNode->m_lAttribNum++;
lpszTag = lpszValue + nLenValue + 1;
}

return;

__ERROR:
XML_FreeNodeTree( pNode );
pParseStat->m_eParseStat = PSTAT_ERROR;
}

/*********************************************************************\
* Function: Parser_OnElemEnd
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
void Parser_OnElemEnd( LPXMLPARSESTAT pParseStat, LPSTR lpszTag )
{
pParseStat->m_lDepth–;

if ( NULL != pParseStat->m_pLastNode )
{
if ( NULL != pParseStat->m_pLastNode->m_pParent )
pParseStat->m_pLastNode->m_pParent->m_pLastChild = pParseStat->m_pLastNode;

if ( pParseStat->m_pLastNode->m_lDepth > pParseStat->m_lDepth )
pParseStat->m_pLastNode = pParseStat->m_pLastNode->m_pParent;
}

if ( 0 == pParseStat->m_lDepth )
pParseStat->m_eParseStat = PSTAT_FINAL;
else
pParseStat->m_eParseStat = PSTAT_INITIAL;
}

/*********************************************************************\
* Function: Parser_OnTextBegin
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
void Parser_OnTextBegin( LPXMLPARSESTAT pParseStat, LPSTR lpszTag )
{
LPXMLNODE pNode;

pNode = (LPXMLNODE)malloc(sizeof(XMLNODE));
if ( NULL == pParseStat->m_pLastNode )
__ERROR;

ZERO_MEMORY( pNode, sizeof(XMLNODE) );
pNode->m_eNodeType    = NODETYPE_TEXT;
pNode->m_lDepth        = pParseStat->m_lDepth;

if ( NULL != pParseStat->m_pLastNode )
{
assert( pNode->m_lDepth >= pParseStat->m_pLastNode->m_lDepth );
if ( pNode->m_lDepth > pParseStat->m_pLastNode->m_lDepth )
{
pNode->m_pParent = pParseStat->m_pLastNode;
pParseStat->m_pLastNode->m_pFirstChild = pNode;
}
else if ( pNode->m_lDepth == pParseStat->m_pLastNode->m_lDepth )
{
pNode->m_pParent = pParseStat->m_pLastNode->m_pParent;
pParseStat->m_pLastNode->m_pNextSibling = pNode;
pNode->m_pPrevSibling = pParseStat->m_pLastNode;
}

if ( NULL != pNode->m_pParent )
pNode->m_pParent->m_lChildNum++;

pNode->m_pRoot = pParseStat->m_pLastNode->m_pRoot;
}

pParseStat->m_pLastNode    = pNode;

pNode->m_pbstrTag = BSTR_ALLOC(lpszTag);
if ( NULL == pNode->m_pbstrTag )
__ERROR;

pParseStat->m_lDepth++; // Move down one layer
pParseStat->m_eParseStat = PSTAT_TEXT_END;
return;

__ERROR:
XML_FreeNodeTree( pNode );
pParseStat->m_eParseStat = PSTAT_ERROR;
}

/*********************************************************************\
* Function: Parser_OnTextEnd
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
void Parser_OnTextEnd( LPXMLPARSESTAT pParseStat, LPSTR lpszTag )
{
// Move up one layer
pParseStat->m_lDepth–;

if ( NULL != pParseStat->m_pLastNode &&
pParseStat->m_pLastNode->m_lDepth > pParseStat->m_lDepth )
pParseStat->m_pLastNode = pParseStat->m_pLastNode->m_pParent;

pParseStat->m_eParseStat = PSTAT_INITIAL;
}

/*********************************************************************\
* Function: Parser_OnCommBegin
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
void Parser_OnCommBegin( LPXMLPARSESTAT pParseStat, LPSTR lpszTag )
{
pParseStat->m_eParseStat = PSTAT_INITIAL;
}

/*********************************************************************\
* Function: Parser_OnCommEnd
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
void Parser_OnCommEnd( LPXMLPARSESTAT pParseStat, LPSTR lpszTag )
{
}

/*********************************************************************\
* Function: Parser_OnCDATABegin
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
void Parser_OnCDATABegin( LPXMLPARSESTAT pParseStat, LPSTR lpszTag )
{
pParseStat->m_eParseStat = PSTAT_INITIAL;
// Should convert to text here
}

/*********************************************************************\
* Function: Parser_OnCDATAEnd
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
void Parser_OnCDATAEnd( LPXMLPARSESTAT pParseStat, LPSTR lpszTag )
{
}

/*********************************************************************\
* Function: XML_IterateTree
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
void XML_IterateTree( LPXMLNODE pTree, LPXMLITERFATOR pIterator )
{
register ITERSTAT eStat;
if ( NULL != pTree )
{
pIterator->m_pStation = pTree;
eStat = pIterator->m_pfnProc( pTree, pIterator->m_pvParam );
if ( eStat == ISTAT_STOP )
return;
if ( eStat != ISTAT_PASS )
pIterator->m_lCount++;

XML_IterateTree( pTree->m_pFirstChild, pIterator );
XML_IterateTree( pTree->m_pNextSibling, pIterator );
}
}

/*********************************************************************\
* Function: XML_ForEachNode
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
LONG XML_ForEachNode( LPXMLDOCUMENT pDoc, LPXMLNODE pStartPoint, LPFNNODEPROC pfnNodeProc, LPVOID pvParam )
{
XMLITERFATOR    iter;

assert( NULL != pDoc );
assert( NULL != pfnNodeProc );

if ( pDoc->m_eDocStat == DSTAT_UNOPEN || (NULL != pStartPoint && pStartPoint->m_pRoot != pDoc->m_lpRootNode) )
return 0;

iter.m_lCount   = 0;
iter.m_pStation = NULL;
iter.m_pvParam  = pvParam;
iter.m_pfnProc  = pfnNodeProc;

pStartPoint = (NULL == pStartPoint ? pDoc->m_lpRootNode : pStartPoint);
XML_IterateTree( pStartPoint, &iter );
return iter.m_lCount;
}

/*********************************************************************\
* Function: XMLCmpNode_EqualTag
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
ITERSTAT XMLCmpNode_EqualTag( LPCXMLNODE pNode, LPVOID pvParam )
{
if ( FALSE != BSTR_EQUAL(pNode->m_pbstrTag, BSTR_CAST(pvParam)) )
return ISTAT_STOP;
else
return ISTAT_CONTINUE;
}

/*********************************************************************\
* Function: XML_GetNode
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
LPXMLNODE XML_GetNode( LPXMLDOCUMENT pDoc, LPXMLNODE pStartPoint, LPCSTR lpszTag, LONG lLen )
{
BSTRING         bStr;
XMLITERFATOR    iter;

assert( NULL != lpszTag );
assert( NULL != pDoc );

if ( pDoc->m_eDocStat == DSTAT_UNOPEN || pStartPoint->m_pRoot != pDoc->m_lpRootNode )
return NULL;

bStr.m_lLength  = lLen;
bStr.m_pszStr   = lpszTag;

iter.m_lCount   = 0;
iter.m_pfnProc  = &XMLCmpNode_EqualTag;
iter.m_pStation = NULL;
iter.m_pvParam  = (LPVOID)&bStr;

pStartPoint = (NULL == pStartPoint ? pDoc->m_lpRootNode : pStartPoint);
XML_IterateTree( pStartPoint, &iter );
return (iter.m_pStation);
}

/*********************************************************************\
* Function: XML_GetNodeAttrib
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
LPXMLATTRIB    XML_GetNodeAttrib( LPXMLNODE pNode, LPCSTR lpszAttr, LONG lLen )
{
BSTRING     bStr;
LPXMLATTRIB pAttrib;

assert( NULL != lpszAttr );
assert( NULL != pNode );
bStr.m_lLength  = lLen;
bStr.m_pszStr   = lpszAttr;

for ( pAttrib = XML_GetNodeFirstAttrib(pNode); NULL != pAttrib; pAttrib = XML_GetNodeNextAttrib(pAttrib) )
{
if ( FALSE != BSTR_EQUAL(pAttrib->m_pbstrName, &bStr) )
break;
}

return pAttrib;
}

/*********************************************************************\
* Function: XML_GetNodeSibling
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
LPXMLNODE XML_GetNodeSibling( LPXMLNODE pNode, LPCSTR lpszTag, LONG lLen, BOOL bIncludeThis )
{
BSTRING     bStr;

assert( NULL != lpszTag );
assert( NULL != pNode );
bStr.m_lLength  = lLen;
bStr.m_pszStr   = lpszTag;

if ( FALSE != bIncludeThis && FALSE != BSTR_EQUAL(pNode->m_pbstrTag, &bStr) )
return pNode;

for ( pNode = XML_GetNodeNextSibling(pNode); NULL != pNode; pNode = XML_GetNodeNextSibling(pNode) )
{
if ( FALSE != BSTR_EQUAL(pNode->m_pbstrTag, &bStr) )
break;
}

return pNode;
}

/*********************************************************************\
* Function: XML_FreeNodeTree
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
void XML_FreeNodeTree( LPXMLNODE pNode )
{
if ( NULL != pNode )
{
BSTR_FREE( pNode->m_pbstrTag );

XML_FreeAttribList( pNode->m_pFirstAttrib );

if ( is_FirstChild(pNode) && NULL != pNode->m_pParent )
pNode->m_pParent->m_pFirstChild = pNode->m_pNextSibling;

if ( NULL != pNode->m_pNextSibling )
pNode->m_pNextSibling->m_pPrevSibling = pNode->m_pPrevSibling;
if ( NULL != pNode->m_pPrevSibling )
pNode->m_pPrevSibling->m_pNextSibling = pNode->m_pNextSibling;

XML_FreeNodeTree( pNode->m_pFirstChild );
XML_FreeNodeTree( pNode->m_pNextSibling );

free( pNode );
}
}

/*********************************************************************\
* Function: XML_FreeAttribList
* Purpose:
* Params:
* Return
* Remarks
**********************************************************************/
void XML_FreeAttribList( LPXMLATTRIB pAttrib )
{
LPXMLATTRIB pNext;

while ( NULL != pAttrib )
{
pNext = pAttrib->m_pNext;
BSTR_FREE( pAttrib->m_pbstrName );
BSTR_FREE( pAttrib->m_pbstrValue );
free( pAttrib );
pAttrib = pNext;
}
}

#if defined(__cplusplus)
}   // extern “C” {
#endif

Tags: , , , , , ,

24 Mar 07 IE编程 – Document

定制浏览器
作者:冯明德

浏览器控件是个典型的Active控件,提供了大量的接口及自动化对象,可以灵活的加以控制,需要的时候,可以通过这些接口控制浏览器的行为,或提供相应的出接口定制浏览器。

一、概述
浏览器对象CLSID:
CLSID_WebBrowser

提供的主要接口
IWebBrowser2 浏览器的接口

当文档建立后,可以得到相应的文档接口,文档中各标记元素的接口。
在DHTML中,大量的对象和事件就是又这些接口提供和管理的。

IHTMLDocument2
IHTMLWindow2
IHTMLEventObj
IHTMLElement
….

浏览器还将调用宿主提供的接口,以发出事件或给用户提供定制机会。
出接口
DIID_DWebBrowserEvents2
DIID_HTMLDocumentEvents
DIID_HTMLWindowEvents

(ICustomDoc)
IDocHostUIHandler

二、事件的相应
除了使用MFC缺省的事件响应机制外,也可以自建事件接受器,来响应事件
也就是,在封装对象中提供DIID_DWebBrowserEvents2 接口,然后将此接口作为接受器连接到浏览器对象。

一种做法是
在派生类中,使用MFC建立接口方案提供一个DIID_DWebBrowserEvents2接口对象嵌套成员。

 CFMDBrowser : public CWebBrowser{	...	//事件接收器接口	//DWebBrowserEvents	//这是一个IDispatch分发接口	BEGIN_INTERFACE_PART(BrowserEventSink,DWebBrowserEvents)		STDMETHOD(GetTypeInfoCount)(UINT *pctinfo);			STDMETHOD(GetTypeInfo)(UINT iTInfo,LCID lcid,ITypeInfo **ppTInfo);		STDMETHOD(GetIDsOfNames)(REFIID riid,				LPOLESTR *rgszNames,UINT cNames,				LCID lcid,DISPID *rgDispId);		STDMETHOD(Invoke)(DISPID dispIdMember,REFIID riid,LCID lcid,				WORD wFlags,DISPPARAMS *pDispParams,				VARIANT *pVarResult,EXCEPINFO *pExcepInfo,				UINT  *puArgErr);		END_INTERFACE_PART(BrowserEventSink)	DWORD m_dwEventSinkCookie;	...}

这是一个接收器接口,无需添入到对象的接口表中。
(无需:BEGIN_INTERFACE_MAP、END_INTERFACE_MAP)

这是一个以分发接口(IDispatch)作为出接口的典型例子。在接口函数的实现中。Invoke负责又分发ID调用不同的虚拟函数。(事件函数作为虚拟函数,供派生类重载)

STDMETHODIMP CFMDBrowser::XBrowserEventSink::Invoke(DISPID dispIdMember,REFIID riid,LCID lcid,				  WORD wFlags,DISPPARAMS *pDispParams,				  VARIANT *pVarResult,EXCEPINFO *pExcepInfo,				  UINT  *puArgErr){	METHOD_PROLOGUE(CFMDBrowser,BrowserEventSink)	//将事件分发到各虚拟函数	//分发ID的定义见 exdispid.h	switch(dispIdMember)	{	case DISPID_BEFORENAVIGATE:		...		HRESULT hr=pThis->OnBeforeNavigate(..) //事件对应的虚拟函数		...		break;        case DISPID_NAVIGATECOMPLETE:        		...	case ...	case ...}

建立与浏览器的连接
得到IConnectionPointContainer接口,查找与DIID_DWebBrowserEvents对应的接收器,建立连接,记录连接的标号(m_dwEventSinkCookie);

BOOL CFMDBrowser::Connect(){	IUnknown *p_Unk=GetControlUnknown();	if(p_Unk==NULL)		return FALSE;	BOOL bOK=FALSE;	//查找连接点对象	IConnectionPointContainer *i_cpc=0;	HRESULT hr=p_Unk->QueryInterface(IID_IConnectionPointContainer,		(void **)(&i_cpc));	if (SUCCEEDED(hr))	{		IConnectionPoint *i_cp=0;		hr=i_cpc->FindConnectionPoint(DIID_DWebBrowserEvents,&i_cp);		if (SUCCEEDED(hr))		{			hr=i_cp->Advise(&m_xBrowserEventSink,&m_dwEventSinkCookie);			i_cp->Release();			bOK=TRUE;		}		i_cpc->Release();	}		return bOK;}

结束时,断开与浏览器的连接

BOOL CFMDBrowser::DisConnect(){	IUnknown *p_Unk=GetControlUnknown();	if(p_Unk==NULL)		return FALSE;		BOOL bOK=FALSE;		//查找连接点对象	IConnectionPointContainer *i_cpc=0;	HRESULT hr=p_Unk->QueryInterface(IID_IConnectionPointContainer,		(void **)(&i_cpc));	if (SUCCEEDED(hr))	{		IConnectionPoint *i_cp=0;		hr=i_cpc->FindConnectionPoint(DIID_DWebBrowserEvents,&i_cp);		if (SUCCEEDED(hr))		{			hr=i_cp->Unadvise(m_dwEventSinkCookie);			i_cp->Release();			bOK=TRUE;		}		i_cpc->Release();	}		return bOK;}

三、定制浏览器UI
浏览器提供了IDocHostUIHandler出接口,向用户查询界面特性
可以提供这个接口,与浏览器连接上,在其实现中,定制界面

1.建立接口

 CFMDBrowser : public CWebBrowser{	...	//IDocHostUIHandler接口,控制浏览器界面	BEGIN_INTERFACE_PART(UIHandlerSink,IDocHostUIHandler)		STDMETHOD(ShowContextMenu)(DWORD,POINT*,IUnknown*,IDispatch*);		STDMETHOD(GetHostInfo)(DOCHOSTUIINFO*);		STDMETHOD(ShowUI)(DWORD,			IOleInPlaceActiveObject*,			IOleCommandTarget*,			IOleInPlaceFrame*,			IOleInPlaceUIWindow*);		STDMETHOD(HideUI)();		STDMETHOD(UpdateUI)();		STDMETHOD(EnableModeless)(INT);		STDMETHOD(OnDocWindowActivate)(INT);		STDMETHOD(OnFrameWindowActivate)(INT);		STDMETHOD(ResizeBorder)(LPCRECT,IOleInPlaceUIWindow*,INT);		STDMETHOD(TranslateAccelerator)(LPMSG,const GUID*,DWORD);		STDMETHOD(GetOptionKeyPath)(LPOLESTR*,DWORD);		STDMETHOD(GetDropTarget)(IDropTarget*,IDropTarget**);		STDMETHOD(GetExternal)(IDispatch**);		STDMETHOD(TranslateUrl)(DWORD,OLECHAR*,OLECHAR**);		STDMETHOD(FilterDataObject)(IDataObject*,IDataObject**);	END_INTERFACE_PART(UIHandlerSink)	...}

无需添加接口映射

2.连接到浏览器
需要在NavigateComplete时间发生后,得到
ICustomDoc接口,由此接口的
SetUIHandler成员设置UI接口。

//设置界面接口IDispatch *i_dispatch=0;if (SUCCEEDED(i_dispatch=pThis->GetDocument())){	IHTMLDocument2 *i_htmldoc2=0;	if (SUCCEEDED(i_dispatch->QueryInterface(IID_IHTMLDocument2,			(void **)(&i_htmldoc2))))	{			// force connection of IDocHostUIHandler			ICustomDoc *i_customdoc=0;			if (SUCCEEDED(i_htmldoc2->QueryInterface(						IID_ICustomDoc,						(void **)(&i_customdoc))))			{				i_customdoc->SetUIHandler(					&(pThis->m_xUIHandlerSink));				i_customdoc->Release();			}	}	i_dispatch->Release();}

3.在接口的实现中,控制用户界面
例如更改右键菜单
在STDMETHOD(ShowContextMenu)(DWORD,POINT*,IUnknown*,IDispatch*);
的实现中:

HRESULT CFMDBrowser::ShowContextMenu(DWORD,POINT*,IUnknown*,IDispatch*){	..建立自己的菜单        return S_OK;         }

Tags: , , ,

24 Mar 07 IE编程 – ToolBar

关键字:Band,Desk Band,Explorer Band,Tool Band,浏览器栏,工具栏,桌面工具栏

一、引言
  最近,由于工作的要求,我需要在 IE 上做一些开发工作。于是在 MSDN 上翻阅了一些资料,根据 MSDN 上的说明我用 ATL 胜利完成了“资本家老板”分配的任务。
(并且在白天睡觉的过程中梦到了老板给我加工资啦……)
现在,我把 MSDN 上的原文资料,经过翻译整理并把一个 ATL 的实现奉贤给 VCKBASE 上的朋友们。

二、概念
  在翻译的过程中,有两个词汇非常不好理解。第一个词是 Band 对象,词典中翻译为“镶边、裙子边、带子、乐队……”我的英文水平有限,实在不知道应该翻译为什么词汇更合适。于是我毅然决然地决定:在如下的论述中,依然使用 band 这个词!(什么?没听明白?我的意思就是说,我不翻译这个词了)但到底 Band 对象应该如何理解那?请看图一:


图一

  图一中画红圈的地方,分别称作“垂直的浏览器栏”、“水平的浏览器栏”、“工具栏”和“桌面工具栏”。这些“栏”,都可以在 IE 的“查看”菜单中或鼠标右键的上下文快捷方式菜单中显示或隐藏起来。这些界面窗口的实现,其实就是实现一种 COM 接口对象,而这个对象叫 band。这个概念实在是只能意会而无法言传的,我总不能在文章中把它翻译为“总是靠在 IE 主窗口边上的对象”吧?^_^
  另外,还有一个词叫 site。这个很好翻译,叫“站点”!。呵呵,我敢打包票,如果你要能理解这个翻译在计算机类文章中的含义,那就只能恭喜你了,你的智慧太高了。(都是学计算机软件的人,做人的差距咋就这么大呢?)在本篇文章中,site 可以这样理解:IE 的主框架四周,就好比是“汽车站”,那些 band 对象,就好比是“汽车”。band 汽车总是可以停靠在“汽车站”上。所以,site 就是“站点”,它也是 COM 接口的对象(IObjectWithSite、IInputObjectSite)。

三、原理

3.1 基本 band 对象
  Band 对象,从 Shell 4.71(IE 5.0) 开始提供支持。Band 是一个 COM 对象,必须放在一个容器中去使用,当然使用它们就好象使用普通窗口是一样的。IE 就是一个容器,桌面 Shell 也是一个容器,它们提供不同的函数功能,但基本的实现是相似的。
  Band 对象分三种类型,浏览器栏 band(Explorer bands)、工具栏 band(Tool Bands)和桌面工具栏(Desk bands),而浏览器栏 band 又有两种表现形式:垂直和水平的。那么 IE 和 Shell 如何区分并加载这些 bands 对象呢?方法是:你要对不同的 band 对象,在注册表中注册不同的组件类型(CATID)。

Band 样式

组件类型

CATID

垂直的浏览器栏 CATID_InfoBand 00021493-0000-0000-C000-000000000046
水平的浏览器栏 CATID_CommBand 00021494-0000-0000-C000-000000000046
桌面的工具栏 CATID_DeskBand 00021492-0000-0000-C000-000000000046

  IE 工具栏不使用组件类型注册,而是使用在注册进行 CLSID 的登记方式。详细情况见 3.3。
  在例子程序中,实现了全部四个类型的 band 对象,垂直浏览器栏(CVerticalBar)显示了一个 HTML 文件,并且实现了对 IE 主窗口浏览网页的导航等功能;水平的浏览器栏(CHorizontalBar)是一个编辑窗,它同步显示当前网页的 BODY 源文件内容;IE 工具栏(CToolBar)最简单,只是添加了一个空的工具栏;桌面工具栏(CDeskBar)实现了一个单行编辑窗口,你可以在上面输入命令行或文件名称,回车后它会执行 Shell 的打开动作。

3.2 必须实现的 COM 接口
  Band 对象是 IE 或 Shell 的进程内服务器,所以它被包装在 DLL 中。而作为 COM 对象,它必须要实现 IUnknown 和 IClassFactory 接口。(大家可以不同操心,因为我们用 ATL 写程序,这两个接口是不用我们自己写代码的。)另外,Band 对象还必须实现 IDeskBand、IObjectWithSite 和 IPersistStream 三个接口:
  IPersistStream 是持续性接口的一种。当 IE 加载 band 对象的时候,它通过这个接口的 Load 方法传递属性值给对象,让其进行初始化;而当卸载前,IE 则调用这个接口的 Save 方法保存对象的属性。用 ATL 实现这个接口很简单:

 ATL_NO_VTABLE Cxxx : 	......	public IPersistStreamInitImpl, // 添加继承	......{public:	BOOL m_bRequiresSave; // IPersistStreamInitImpl 所必须的变量......BEGIN_COM_MAP(CVerticalBar)	......	COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)	COM_INTERFACE_ENTRY2(IPersistStream, IPersistStreamInit)	COM_INTERFACE_ENTRY(IPersistStreamInit)	......END_COM_MAP()BEGIN_PROP_MAP(Cxxx)...... // 添加需要持续性的属性END_PROP_MAP()		

  上面的代码,其实实现的是 IPersistStreamInit 接口,不过没有关系,因为 IPersistStreamInit 派生自 IPersistStream,实例化了派生类,自然就实例化了基类。在例子程序中,我只在桌面工具栏对象中添加了持续性属性,用来保存和初始化“命令行”。另外 COM_INTERFACE_ENTRY2(A,B)表示的含义是:如果想查询A接口的指针,则提供B接口指针来代替。为什么可以这样那?因为B接口派生自A接口,那么B接口的前几个函数必然就是A接口的函数了,自然B接口的地址其实和A接口的地址是一样的了。
  IObjectWithSite 是 IE 用来对插件进行管理和通讯用的一个接口。必须要实现这个接口的2个函数:SetSite() 和 GetSite()。当 IE 加载 band 对象和释放 band 对象的时候,都要调用 SetSite()函数,那么在这个函数里正好是写初始化和释放操作代码的地方:

STDMETHODIMP Cxxx::SetSite(IUnknown *pUnkSite){	if( NULL == pUnkSite )	// 释放 band 的时候	{		// 如果加载的时候,保存了一些接口		// 那么现在:释放它	}	else	// 加载 band 的时候	{		m_hwndParent = NULL;	// 装载 band 的父窗口(就是带有标题的那个框架窗口)		// 这个窗口的句柄,是调用 IUnknown::QueryInterface() 得到 IOleWindow		// 然后调用 IOleWindow::GetWindow() 而获得的。		CComQIPtr< IOleWindow, &IID_IOleWindow > spOleWindow(pUnkSite);		if( spOleWindow )	spOleWindow->GetWindow(&m_hwndParent);		if( !m_hwndParent )	return E_FAIL;				// 现在,正好是建立子窗口的时机。		// 注意,子窗口建立的时候,不要使用 WS_VISIBLE 属性		... ...		// 在例子程序中,用 CAxWindow 实现了一个能包容ActiveX的容器窗口(垂直浏览器栏)		// 在例子程序中,用 WIN API 函数 CreateWindow 实现了标准窗口(水平浏览器栏、工具栏)		// 在例子程序中,用 CWindowImpl 实现了一个包容窗口(桌面工具栏)		/*********************************************************/		   以下部分,根据 band 对象特有的功能,是可以选择实现的		**********************************************************/				// 如果子窗口实现了用户输入,那么必须实现 IInputObject 接口,		// 而该接口是被 IE 的 IInputObjectSite 调用的,因此在你的对象		// 中,应该保存 IInputObjectSite 的接口指针。		// 在类的头文件中,定义:		// CComQIPtr< IInputObjectSite, &IID_IInputObjectSite > m_spSite;		m_spSite = pUnkSite;	// 保存 IInputObjectSite 指针		if( !m_spSite )		return E_FAIL;		// 你需要控制 IE 的主框架吗?		// 那么在类的头文件中,定义:		// CComQIPtr< IWebBrowser2, &IID_IWebBrowser2 > m_spFrameWB;		// 然后,先取得 IServiceProvider,再取得 IWebBrowser2		CComQIPtr < IServiceProvider, &IID_IServiceProvider> spSP(pUnkSite);		if( !spSP )	return E_FAIL;		spSP->QueryService( SID_SWebBrowserApp, &m_spFrameWB );		if( !m_spFrameWB)	return E_FAIL;		// 如果你取得了 IE 主框架的 IWebBrowser2 指针		// 那么,当它发生了什么事情,你难道不想知道吗?		// 定义:CComPtr m_spCP;		CComQIPtr< IConnectionPointContainer,			&IID_IConnectionPointContainer> spCPC( m_spFrameWB );		if( spCPC )		{			spCPC->FindConnectionPoint( DIID_DWebBrowserEvents2, &m_spCP );			if( m_spCP )			{				m_spCP->Advise( reinterpret_cast< IDispatch * >( this ), &m_dwCookie );			}		}		// 咳~~~ 不说了,看源码去吧。这里能干的事情太多了... ...	}	return S_OK;}		

IDeskBand 是一个特殊的 band 对象接口,有一个方法函数:GetBarInfo();
IDockingWindow 是 IDeskBank 的基类,有3个方法函数:ShowDW()、CloseDW()、ResizeBorderDW();
IOleWindow 又是 IDockingWindow 的基类,有2个方法函数:GetWindow()、ContextSensitiveHelp();

  首先声明 IDeskBand ,然后要实现 IDeskBand 接口的共6个函数,这些函数比较简单,不同类型的 band 对象,其实现方法也都基本一致:

 ATL_NO_VTABLE Cxxx : 	......	public IDeskBand,	......{......BEGIN_COM_MAP(Cxxx)	......	COM_INTERFACE_ENTRY_IID(IID_IDeskBand, IDeskBand)	......END_COM_MAP()// IOleWindowSTDMETHODIMP Cxxx::GetWindow(HWND * phwnd){	// 取得 band 对象的窗口句柄	// m_hWnd 是建立窗口时候保存的	*phwnd = m_hWnd;		return S_OK;}STDMETHODIMP Cxxx::ContextSensitiveHelp(BOOL fEnterMode){	// 上下文帮助,参考 IContextMenu 接口	return E_NOTIMPL;}// IDockingWindowSTDMETHODIMP CVerticalBar::ShowDW(BOOL bShow){	// 显示或隐藏 band 窗口	if( m_hWnd )		::ShowWindow( m_hWnd, bShow ? SW_SHOW : SW_HIDE);	return S_OK;}STDMETHODIMP CVerticalBar::CloseDW(DWORD dwReserved){	// 销毁 band 窗口	if( ::IsWindow( m_hWnd ) )		::DestroyWindow( m_hWnd );	m_hWnd = NULL;    return S_OK;}STDMETHODIMP CVerticalBar::ResizeBorderDW(LPCRECT prcBorder, IUnknown* punkToolbarSite, BOOL fReserved){	// 当框架窗口的边框大小改变时	return E_NOTIMPL;}// IDeskBandSTDMETHODIMP CVerticalBar::GetBandInfo(DWORD dwBandID, DWORD dwViewMode,  DESKBANDINFO* pdbi){	         // 取得 band 的基本信息,你需要填写 pdbi 参数作为返回	if( NULL == pdbi )		return E_INVALIDARG;	// 如果将来需要调用 IOleCommandTarget::Exec() 则需要保存这2个参数	m_dwBandID = dwBandID;	m_dwViewMode = dwViewMode;	if(pdbi->dwMask & DBIM_MINSIZE)	{	// 最小尺寸		pdbi->ptMinSize.x = 10;		pdbi->ptMinSize.y = 10;	}	if(pdbi->dwMask & DBIM_MAXSIZE)	{	// 最大尺寸 (-1 表示 4G)		pdbi->ptMaxSize.x = -1;		pdbi->ptMaxSize.y = -1;	}	if(pdbi->dwMask & DBIM_INTEGRAL)	{		pdbi->ptIntegral.x = 1;		pdbi->ptIntegral.y = 1;	}	if(pdbi->dwMask & DBIM_ACTUAL)	{		pdbi->ptActual.x = 0;		pdbi->ptActual.y = 0;	}	if(pdbi->dwMask & DBIM_TITLE)	{	// 窗口标题		wcscpy(pdbi->wszTitle,L"窗口标题");	}	if(pdbi->dwMask & DBIM_MODEFLAGS)	{		pdbi->dwModeFlags = DBIMF_VARIABLEHEIGHT;	}	if(pdbi->dwMask & DBIM_BKCOLOR)	{	// 如果使用默认的背景色,则移除该标志		pdbi->dwMask &= ~DBIM_BKCOLOR;	}	return S_OK;}		

3.3 选择实现的 COM 接口
  有两个接口不是必须实现的,但也许很有用:IInputObject 和 IContextMenu。如果 band 对象需要接收用户的输入,那么必须实现 IInputObject 接口。IE 实现了 IInputObjectSite 接口,当容器中有多个输入窗口时,它调用 IInputObject 接口方法去负责管理用户的输入焦点。
在浏览器栏中需要实现3个函数:UIActivateIO()、HasFocusIO()、TranslateAcceleratorIO()。
当浏览器栏激活或失去活性的时候,IE 调用 UIActivateIO 函数,当激活的时候,浏览器栏一般调用 SetFocus 去设置它自己窗口的焦点。当 IE 需要判断哪个窗口有焦点的时候,它调用 HasFocusIO 。当浏览器栏的窗口或其子窗口有输入焦点时,则应返回 S_OK,否则返回 S_FALSE。TranslateAcceleratorIO 允许对象处理加速键,例子程序中没有实现,所以直接返回 S_FALSE。

STDMETHODIMP CExplorerBar::UIActivateIO(BOOL fActivate, LPMSG pMsg){    if(fActivate)        SetFocus(m_hWnd);    return S_OK;}STDMETHODIMP CExplorerBar::HasFocusIO(void){    if(m_bFocus)        return S_OK;    return S_FALSE;}STDMETHODIMP CExplorerBar::TranslateAcceleratorIO(LPMSG pMsg){    return S_FALSE;}      

  Band 对象能够通过包容器的 IOleCommandTarget::Exec() 调用执行命令。而 IOleCommandTarget 接口指针,则可以通过调用包容器的 IInputOjbectSite::QueryInterface(IID_IOleCommandTarget,…) 函数得到。CGID_DeskBand 是命令组,当一个 band 对象的 GetBandInfo 被调用的时候,包容器通过 dwBandID 参数指定一个 ID 给 band 对象,对象要保存住这个ID,以便调用 IOleCommandTarget::Exec()的时候使用。ID 的命令有:

  • DBID_BANDINFOCHANGED
    Band 的信息变化。设置参数 pvaIn 为 band ID, 该 ID 就是最近一次调用 GetBandInfo 所得到的值,容器会调用 band 对象的 GetBandInfo 函数来更新请求信息。
  • DBID_MAXIMIZEBAND
    最大化 band。设置参数 pvaIn 为 band ID,该 ID 就是最近一次调用 ?GetBandInfo ?所得到的值。
  • DBID_SHOWONLY
    打开或关闭容器中其它的 bands。 设置参数 pvaIn 为VT_UNKNOWN 类型,它可以是如下的值:
     
    描述
    pUnk band 对象的 IUnknown 指针,其它的桌面 bands 将被隐藏
    0 隐藏所有的桌面 bands
    1 显示所有的桌面 bands

  • DBID_PUSHCHEVRON
    在菜单项左边显示“v”的选择标志。容器发送一个 RB_PUSHCHEVRON 消息,当 band 对象接收到通知消息 RBN_CHEVRONPUSHED 提示它显示一个"v"的标志。设置 IOleCommandTarget::Exec 函数中 nCmdExecOpt 参数为 band ID,该 ID 是最近一次调用 GetBandInfo ?所得到的值,设置 IOleCommandTarget::Exec 函数中 pvaIn 参数为 VT_I4 类型,这是应用程序定义的一个值,它通过通知消息 RBN_CHEVRONPUSHED 中lAppValue 回传给 band 对象。

3.4 Band 对象注册
  Band 对象必须注册为一个 OLE 进程内的服务器,并且支持 apartment 线程公寓。注册表中默认键的值是表示菜单的文字。对于浏览器栏,它加到 IE 菜单的“查看\浏览器栏”中;对于工具栏 band ,它加到 IE 菜单的“查看\工具栏”中;对于桌面 band, 它加到系统任务栏的快捷菜单中。在菜单资源中,可以使用“&”指明加速键。

通常,一个基本的 band 对象的注册表项目是:

HKEY_CLASSES_ROOT
CLSID
{你的 band 对象的 CLSID}

  (Default) = 菜单的文字
  InProcServer32
   (Default) = DLL 的全路径文件名
   ThreadingModel= Apartment

工具栏 bands 还必须把它们的 CLSID 注册到 IE 的注册表中。

HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Toolbar 下给出 CLSID 作为键名,而其键值是被忽略的。

HKEY_LOCAL_MACHINE
Software
Microsoft
Internet Explorer
Toolbar

  {你的 band 对象的 CLSID}

  还有几个可选的注册表项目(例子程序并不是这样实现的)。比如,你想让浏览器栏显示 HTML 的话,必须要如下设置注册表:

HKEY_CLASSES_ROOT
CLSID
{你的 Band 对象的 CLSID}
Instance
CLSID
  
(Default) = {4D5C8C2A-D075-11D0-B416-00C04FB90376}

同时,如果要指定一个本地的 HTML 文件,那么要如下设置:

HKEY_CLASSES_ROOT
CLSID
{你的 Band 对象的 CLSID}
Instance
InitPropertyBag
  
Url

  另外,还可以指定浏览器栏的宽和高,当然,它是依赖于这个栏是纵向还是横向的。其实这个项目无所谓,因为当用户调整了浏览器栏的大小后,会自动保存在注册表中的。

HKEY_CURRENT_USER
Software
Microsoft
Internet Explorer
Explorer Bars
{你的 Band 对象的 CLSID}
  
BarSize

  BarSize 键的类型必须是 REG_BINARY 类型,它有8个字节。左起前4个字节,是用16进制表示的像素宽度或高度,后4个字节保留,你应该设置为0。下面是一个可以在浏览器栏上显示 HTML 文件的全部注册表项目的例子,默认宽度为291(0×123)个像素点:

HKEY_CLASSES_ROOT
CLSID
{你的 Band 对象的 CLSID}

 (Default) = 菜单文字
 InProcServer32
  (Default) = DLL 的全路径文件名
  ThreadingModel= Apartment
Instance
CLSID

  (Default) = {4D5C8C2A-D075-11D0-B416-00C04FB90376}
InitPropertyBag
  Url= 你的 HTML 文件名

HKEY_CURRENT_USER
Software
Microsoft
Internet Explorer
Explorer Bars
{你的 Band 对象的 CLSID}

  BarSize= 23 01 00 00 00 00 00 00

  对于注册表的设置,用 ATL 实现其实是异常简单的。打开工程的 xxx.rgs 文件,并手工编辑一下就可以了。 下面这个文件源码,是例子程序中 IE 工具栏的注册表样式,HKLM 是需要手工添加的,因为它不使用组件类型方式注册。而对于其它类型的 band 对象只要在类声明中添加:

BEGIN_CATEGORY_MAP(Cxxx)			// 向注册表中注册 COM 类型	IMPLEMENTED_CATEGORY(CATID_InfoBand)	// 垂直样式的浏览器栏END_CATEGORY_MAP()		

IE 工具栏类型 band 对象的“.rgs”文件

HKCR	// 这个项目是 ATL 帮你生成的,你只要手工修改“菜单上的文字”就可以了{	Bands.ToolBar.1 = s ''ToolBar ''	{		CLSID = s ''{ 你的 CLSID }''	}	Bands.ToolBar = s ''ToolBar ''	{		CLSID = s ''{ 你的 CLSID }''		CurVer = s ''Bands.ToolBar.1''	}	NoRemove CLSID	{		ForceRemove { 你的 CLSID } = s ''用在菜单上的文字(&T)''		{			ProgID = s ''Bands.ToolBar.1''			VersionIndependentProgID = s ''Bands.ToolBar''			ForceRemove ''Programmable''			InprocServer32 = s ''%MODULE%''			{				val ThreadingModel = s ''Apartment''			}			''TypeLib'' = s ''{xxxx-xxxx-xxxxxxxxxxxxxxx}''		}	}}HKLM	// 这个项目是手工添加的IE工具栏所特有的{	Software	{		Microsoft		{			''Internet Explorer''			{				NoRemove Toolbar				{					ForceRemove val { 你的 CLSID } = s ''随便给个说明性文字串''				}			}		}	}}		

四、 ATL 实现
  下载代码后(VC 6.0 工程),请参照前面的说明仔细阅读,代码中也有一些关键点的注释。如果想运行,则可以用 regsvr32.exe 进行注册,然后打开 IE 浏览器或资源浏览器就可以看到效果了。如果想自己实践一下,可以按照如下的步骤构造工程:

4.1 建立一个 ATL DLL 工程
4.2 添加 New ATL Object…,选择 Internet Explorer Object,选这个类型的目的是让向导给我们添加 IObjectWithSite 的支持。如果你使用的是 .net 环境,则不要忘记选择支持这个接口。

4.3 输入对象名称,比如我想建立一个垂直的浏览器栏,不妨叫它 VerBar

4.4 线程模型必须选择 Apartment,接口类型的选择无所谓,看你想不想支持 IDispatch 接口功能了。在例子程序中的垂直浏览器栏中,由于想更简单的操纵 IE 和从 IE 中接受事件(连接点),选择 Dual 是必要的。聚合选项,你只要别选择 Only 就可以了。

4.5 展现你无穷的智慧,开始输入程序吧。如果是 Debug 方式编译,可能会出现一个连接错误,报告找不到_AtlAxCreateControl,那么你要在菜单 Project\Settings…\Link 中增加对 Atl.lib 的连接。或者使用 #pragma comment ( lib, "atl" )加入连接库。
4.6 如果想调试代码,在菜单 Project\Settings…\Debug 中输入 IE 的路径名称,比如:“C:\Program Files\Internet Explorer\IEXPLORE.EXE”,然后就可以跟踪断点调试了。 编译和调试桌面工具栏的 band 对象,是非常麻烦的,因为计算机启动时自动运行 Shell,而 Shell 就会加载活动的桌面对象。

五、结束语
好了,到这里,就到这里了。祝大家学习快乐^_^

Tags: , , , , , , , ,

13 Jan 07 如何屏蔽控制台应用程序的窗口?

Step1. Create a console application.

Step2. Add following segment before main() entry function:
            #pragma comment( linker, "/subsystem:\"\" /entry:\"mainCRTStartup\"" )

Step3. Try to build it.

Tags: