2007年5月5日

     摘要: VC中为什么会出现LNK2005"符号已定义"的链接错误,本文详细介绍了VC链接的文件的过程,以及解决这个错误的方法  阅读全文
posted @ 2007-05-05 16:02 dyh 阅读(150) | 评论 (0)编辑 收藏

2007年5月2日

Homepage: http://www.grinninglizard.com/tinyxml/
download:http://sourceforge.net/projects/tinyxml

用mingw32-make前修改一下makefile文件,改为如下

# DEBUG can be set to YES to include debugging info, or NO otherwise(不是DEBUG)
DEBUG          := NO

# PROFILE can be set to YES to include profiling info, or NO otherwise
PROFILE        := NO

# TINYXML_USE_STL can be used to turn on STL support. NO, then STL
# will not be used. YES will include the STL files.(使用STL,选择的话,则可以使用std::string)
TINYXML_USE_STL := YES

一、      TinyXml的特点

TinyXml是一个基于DOM模型的、非验证的轻量级C++解释器。

1.      SAX和DOM

目前XML的解析主要有两大模型:SAX和DOM。

其中SAX是基于事件的,其基本工作流程是分析XML文档,当发现了一个新的元素时,产生一个对应事件,并调用相应的用户处理函数。这种方式占用内存少,速度快,但用户程序相应得会比较复杂。

而DOM(文档对象模型),则是在分析时,一次性的将整个XML文档进行分析,并在内存中形成对应的树结构,同时,向用户提供一系列的接口来访问和编辑该树结构。这种方式占用内存大,速度往往慢于SAX,但可以给用户提供一个面向对象的访问接口,对用户更为友好。

 

2.      验证和非验证

对于一个特定的XML文档而言,其正确性分为两个层次。首先是其格式应该符合XML的基本格式要求,比如第一行要有声明,标签的嵌套层次必须前后一致等等,符合这些要求的文件,就是一个合格的XML文件,称作well-formatted。但除此之外,一个XML文档因其内容的不同还必须在语义上符合相应的标准,这些标准由相应的DTD文件或者Schema文件来定义,符合了这些定义要求的XML文件,称作valid。

因此,解析器也分为两种,一种是验证的,即会跟据XML文件中的声明,用相应的DTD文件对XML文件进行校验,检查它是否满足DTD文件的要求。另一种是忽略DTD文件,只要基本格式正确,就可以进行解析。

就我所知,验证的解析器通常都是比较重量级的。TinyXml不支持验证,但是体积很小,用在解析格式较为简单的XML文件,比如配置文件时,特别的合适。

 

二、 TinyXml的构建和使用
1.      获取

TinyXml首页在http://www.grinninglizard.com/tinyxml/index.html,从这里可以找到最新版本的源代码,目前的版本是 2.4.3 (截至2006.5.17).

2.构建

TinyXml在构建时可以选择是否支持STL,选择的话,则可以使用std::string,所以通常应在Windows上,TinyXml的源码包里提供了VC6的工程文件,直接用它就可以生成两个静该打开这个选项。态库(带STL和不带STL),非常容易。唯一需要注意的是,默认生成的库是单线程的,如果用在多线程的项目中,需要改动一下配置,生成相应的多线程库。

在Unix平台上,TinyXml的源码包里只提供了一个Makefile,对于典型的Linux系统,或装了gcc和gmake的其他Unix,这个Makefile足够用了,我在RH9和RHEL4上测试,简单的make就成功了。需要注意的有以下几点:默认的编译是不支持STL的,可以通过编辑Makefile的TINYXML_USE_STL := NO那一行,把NO改成YES就可以支持STL了;还有默认只生成了一个测试程序,没有生成任何库,如果要生成静态库的话,可以用ar命令,将生成的几个目标文件打包就行了,如果要生成动态库,则需要加上-fpic参数重新编译。

3.      使用

构建了相应的库之后,在使用了它们的工程中,只要在连接时把他们连上就行了。需要注意的是,如果需要STL支持,在编译用到了TinyXml的文件时,需要定义一个宏TIXML_USE_STL,对gcc,可以使用参数-DTIXML_USE_STL,对cl.exe(VC),可以使用参数/DTIXML_USE_STL,如果嫌麻烦,可以直接定义在 tinyxml.h文件里。

 

三、 TinyXml的编程模型

1.类之间的关系

TinyXml实现的时DOM访问模型,因此提供了一系列的类对应XML文件中的各个节点。主要类间的关系如下图所示:

 

 

TiXmlBase:其它类的基类,是个抽象类

TiXmlNode:表示一个节点,包含节点的一般方法,如访问自节点、兄弟节点、编辑自身、编辑子节点

TiXmlDocument:表示整个XML文档,不对应其中某个特定的节点。

TiXmlElement:表示元素节点,可以包含子节点和TiXmlAttribute

TiXmlComment:表示注释

TiXmlDeclaration:表示声明

TiXmlText:表示文本节点

TiXmlUnknown:表示未知节点,通常是出错了

TiXmlAttribute:表示一个元素的属性

下面是一个简单的例子:

<?xml version="1.0" encoding="utf-8" ?>

 

 

<!-This is only a sample-->

 

 

<book>

 

 

       <name>TinyXml How To</name>

 

 

       <price unit=”RMB”>20</price>

 

 

       <description>Some words…</description>

 

 

</ book >

 

 

整个文档,对应TiXmlDocument

book,name,price, description,都对应TiXmlElement

第一行对应一个TiXmlDeclaration

第二行对应一个TiXmlComment

“TinyXml How To”对应一个TiXmlText

unit则是price的一个TiXmlAttribute

这些类与XML文件中的相应元素都有很好的对应关系,因此相信参照TinyXml的文档,可以很容易的掌握各个方法的使用。

 

2.  需要注意的问题

各类之间的转换

 

 

由于各个节点类都从TiXmlNode继承,在使用时常常需要将TiXmlNode*类型的指针转换为其派生类的指针,在进行这种转换时,应该首先使用由TiXmlNode类提供的一系列转换函数,如ToElement(void),而不是c++的dynamic_cast

 

检查返回值

 

 

由于TinyXml是一个非校验的解析器,因此当解析一个文件时,很可能文件并不包含我们预期的某个节点,在这种情况下,TinyXml将返回空指针。因此,必须要对返回值进行检查,否则将很容易出现内存访问的错误。

 

如何重头建立一个XML文件

 

 

先建立一个TiXmlDocument对象,然后,载入某个模板,或者直接插入一个节点作为根节点,接着就可以像打开一个已有的XML文件那样对它进行操作了。

 

四、总结

TinyXml最大的特点就是它很小,可以很方便的静态连接到程序里。对于像配置文件、简单的数据文件这类文件的解析,它很适合。但是由于它是非验证的,因此需要在程序里做许多检查工做,加重了程序编写的负担。因此对于复杂的XML文件,我觉得最好还是用验证的解析器来处理。

posted @ 2007-05-02 10:46 dyh 阅读(399) | 评论 (0)编辑 收藏

2007年4月28日

     摘要: Blitz++与MTL两大数值计算程序库(C++)的简介 Blitz++与MTL都是基于C++ template高效数值计算程序库,不过他们专注于不同的方向。 Blitz++提供了一个N维(1—10)的Array类,这个Array类以reference counting技术实现,支持任意的存储序(row-major的C-style数组,column-major的Fortran-style数组)...  阅读全文
posted @ 2007-04-28 11:32 dyh 阅读(308) | 评论 (2)编辑 收藏

2007年4月27日

     摘要: 经典的C++库
STLport-------SGI STL库的跨平台可移植版本,在以前有些编译器离符合
标准比较远的情况下 那时还是有用的,当然目前vc71已经比较接近标准了,
故目前不怎么用它了。
Boost---------准标准库, 功能强大 涉及能想的到的大部分非特别领域的算法,
有一个大的C++社区支持
WxWindows-----功能强大的跨平台GUI库 ,它的功能和结构都类似 MFC,故原则上
可以通过WxWindows把现有MFC程序移植到非Win平台下
Blitz---------高效率的数值计算函数库 ,你可以订制补充你需要的算法
Log4cpp-------日志处理 ,功能类似java中的log4j
ACE-----------自适应通讯环境, 重量级的通讯环境库。
Crypto++ -----加/解密算法库, 非常专业的C++ 密码学函式库
CppUnit --- 一个  阅读全文
posted @ 2007-04-27 20:48 dyh 阅读(2144) | 评论 (0)编辑 收藏

2007年3月6日

可视化graph的工具: GraphViz

这套工具可以把有向图(digraph)和无向图(graph)在平面内展现出来,方便观察。GraphViz使用DOT(一种图形描述语言)描述图,然后有解释工具dot生成图像文件。dot支持多种图像文件,包括非矢量的gif、矢量的ps、svg等约20多种格式。DOT语言也非常简单易学。举个例子:

digraph G {
 size = "4,4"
 main [shape=box]; /* this is a comment */
 main -> parse [weight=8];
 parse -> execute;
 main -> init [style=dotted];
 main -> cleanup;
 execute -> { make_string; printf}
 init -> make_string;
 edge [color=red]; // so is this
 main -> printf [style=bold, label="100 times"];
 node [shape=box, style=filled, color=".7.3 1.0"];
 execute -> compare;
}

存为test.dot,然后执行
  > dot test.dot -Tpng -o test.png
就生成了graph的图像文件。很方便哦。
 
实际上GraphViz还可以画出很多漂亮的“图”。比如ER图,hash table示意图。更多请参考:http://www.graphviz.org/Gallery.php。以后可以考虑用GraphViz画一些示意图,既方便有专业,还很容易转成ps(eps)。继续研究研究。
 
posted @ 2007-03-06 21:35 dyh 阅读(470) | 评论 (0)编辑 收藏

2007年1月15日

[转载]Visual C++ MFC 中常用宏的含义

文章来源: http://www.vccode.com/
文章作者:不详

AND_CATCHAND_CATCH
AND_CATCH(exception_class,exception _object_point_name)
说明:

定义一个代码块,它用于获取废除当前TRY块中的附加异常类型。使用CATCH宏以获得一个异常类型,然后使用AND_CATCH宏获得随后的异常处理代码可以访问异常对象(若合适的话)已得到关于异常的特别原因的更多消息。在AND_CATCH块中调用THROW_LAST宏以便把处理过程移到下个外部异常框架。AND_CATCH可标记CATCH或AND_CATCH块的末尾。

注释:
AND_CATCH块被定义成为一个C++作用域(由花括号来描述)。若用户在此作用域定义变量,那么记住他们只在此作用域中可以访问。他也用于exception_object_pointer_name变量。
 
ASSERT
ASSERT(booleanExpression)
说明:
计算变量的值。如果结构的值为0,那么此宏便打印一个诊断消息并且成讯运行失败。如果条件为非0,那么什么也不做。 诊断消息的形式为: assertion failed in file in line 其中name是元文件名,num是源文件中运行失败的中断号。 在Release版中,ASSERT不计算表达式的值也就不中断程序。如果必须计算此表达式的值且不管环境如何那么用VERIFY代替ASSERT。
注释:
ASSERT只能在Debug版中用

ASSERT_VAILD
ASSERT_VAILD(pObject)
说明:
用于检测关于对象的内部状态的有效性。ASSERT_VALID调用此对象的AssertValid成员函数(把它们作为自己的变量来传递)。在Release版中ASSERT_VALID什么也不做。在DEBUG版中,他检查指针,以不同于NULL的方式进行检查,并调用对象自己的AssertValid成员函数。如果这些检测中有任何一个失败的话,那么他会以与ASSERT相同的方法显示一个警告的消息。
注释:
此函数只在DEBUG版中有效。

BEGIN_MESSAGE_MAP
BEGIN_MESSAGE_MAP(the class,baseclass)
说明:
使用BEGIN_MESSAGE_MAP开始用户消息映射的定义。在定义用户类函数的工具(.cpp)文件中,以BEGIN_MESSAGE_MAP宏开始消息映射,然后为每个消息处理函数增加宏项,接着以END_MESSAGE_MAP宏完成消息映射。

CATCH
CATCH(exception_class,exception_object_pointer_name)
说明:
使用此用定义一个代码块,此代码用来获取当前TRY块中都一个异常类型。异常处理代码可以访问异常对象,如何合适的话,就会得到关于异常的特殊原因的更多消息。调用THROW_LAST宏以把处理过程一下一个外部异常框架,如果exception-class是类CExceptioon,那么会获取所有异常类型。用户可以使用CObject::IsKindOf成员函数以确定那个特别异常被排除。一种获取异常的最好方式是使用顺序的AND_CATCH语句,每个带一个不同的异常类型。此异常类型的指针由宏定义,用户不必定义。
注释:
此CATCH块被定义作一个C++范围(由花括号描述)。如用户在此范围定义变量,那么它们只在吃范围内可以访问。他还可以用于异常对象的指针名。

DEBUG_NEW
#define new DEBUG_NEW
说明:
帮助查找内存错误。用户在程序中使用DEBUG_NEW,用户通常使用new运算符来从堆上分配。在Debug模式下(但定义了一个DEBUG符号),DEBUG_NEW为它分配的每个对象记录文件名和行号。然后,在用户使用CMemoryState::DumpAllObjectSince成员函数时,每个以DEBUG_NEW分配的对象分配的地方显示出文件名和行号。 为了使用DEBUG_NEW,应在用户的资源文件中插入以下指令: #define new DEBUG_NEW 一旦用户插入本指令,预处理程序将在使用new的地方插入DEBUG_NEW,而MFC作其余的工作。但用户编译自己的程序的一个发行版时,DEBUG_NEW便进行简单的new操作,而且不产生文件名和行号消息。

DECLARE_DYNAMIC
DECLARE_DYNAMIC(class_name)
说明:
但从CObject派生一个类时,此宏增加关于一个对象类的访问运行时间功能。把DECLARE_DYNAMIC宏加入类的头文件中,然后在全部需要访问词类对象的.CPP文件中都包含此模块。如果像所描述那样使用DELCARE_DYNAMIC和IMPLEMENT_DYNAMIC宏,那么用户便可使用RUNTIME_CLASS宏和CObject::IsKindOf函数以在运行时间决定对象类。如果DECLARE_DYNAMIC包含在类定义中,那么IMPLEMETN_DYNAMIC必须包含在类工具中。

DECLARE_DYNCREATE
DECLARE_DYNCREATE(class_name)
说明:
使用DECLARE_DYNCRETE宏以便允许CObject派生类的对象在运行时刻自动建立。主机使用此功能自动建立新对象,例如,但它在串行化过程中从磁盘读一个对象时,文件及视图和框架窗应该支持动态建立,因为框架需要自动建立它。把DECLARE_DYNCREATE宏加入类的.H文件中,然后在全部需要访问此类对象的.CPP文件中包含这一模式。如果DECLARE_DYNCREATE包含在类定义中,那么IMPLEMENT_DYNCREATE必须包含在类工具中。

DECLARE_MESSAGE_MAP
DECLARE_MESSAGE_MAP()
说明:
用户程序中的每个CCmdTarget派生类必须提供消息映射以处理消息。在类定义的末尾使用DECLARE_MESSAGE_MAP宏。接着,在定义类成员函数的.CPP文件中,使用BEGIN_MESSAGE_MAP宏,每个用户消息处理函数的宏项下面的列表以及END_MESSAGE_MAP宏。
注释:
如果在DECLARE_MESSAGE_MAP之后定义任何一个成员,那么必须为他们指定一个新存取类型(公共的,私有的,保护的)。

DECLARE_SERIAL
DECLARE_SERIAL(class_name)
说明:
DECLARE_SERIAL为一个可以串行化的CObject派生类产生必要的C++标题代码。串行化是把某个对象的内容从一个文件读出和写入一文件。在.H文件中使用DECLARE_SERIAL宏,接着在需要访问此类对象的全部.CPP文件中包含此文件。如果DECLARE_SERIAL包含在类定义中,那么IMPLEMENT_SERIAL必须包含在类工具中。DECLARE_SERIAL宏包含全部DECLARE_DYNAMIC,IMPLEMENT_DYCREATE的功能。

END_CATCH
END_CATCH
说明:
标识最后的CATCH或AND_CATCH块的末尾。

END_MESSAGE_MAP
END_MESSAGE_MAP
说明:
使用END_MESSAGE_MAP宏结束用户的消息映射定义
IMPLEMENT_DYNAMIC
IMPLEMENT_DYNAMIC(class_name,base_class_name)
说明:
通过运行时在串行结构中为动态CObject派生类访问类名和位置来产生必要的C++代码。在.CPP文件中使用IMPLEMENT_DYNAMIC宏,接着一次链接结果对象代码

IMPLEMENT_DYNCREATE
IMPLEMENT_DYNCREATE(class_name,base_class_name)
说明:
通过DECLARE_DYNCREATE宏来使用IMPLEMENT_DYNCREATE宏,以允许CObject派生类对象在运行时自动建立。主机使用此功能自动建立对象,例如,但它在串行化过程中从磁盘读去一个对象时,他在类工具里加入IMPLEMENT_DYNCREATE宏。若用户使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏,那么接着使用RUNTIME_CLASS宏和CObject::IsKindOf成员函数以在运行时确定对象类。若declare_dyncreate包含在定义中,那么IMPLEMENT_DYNCREATE必须包含在类工具中。

IMPLEMENT_SERIAL
IMPLEMENT_SERIAL(class_name,base_class_name,wSchema)
说明:
通过运行时在串行结构中动态CObject派生类访问类名和位置来建立必要的C++代码。在.CPP文件中使用IMPLEMENT_SERIAL宏,然后一次链接结果对象代码。

ON_COMMAND
ON_COMMAND(id,memberFxn)
说明:
此宏通过ClassWizard或手工插入一个消息映射。它表明那个函数将从一个命令用户接口(例如一个菜单项或toolbar按钮)处理一个命令消息。当一个命令对象通过指定的ID接受到一个Windows WM_COMMAND消息时,ON_COMMAND将调用成员函数memberFxn处理此消息。在用户的消息映射中,对于每个菜单或加速器命令(必须被映射到一个消息处理函数)应该确实有一个ON_COMMAND宏语句。

ON_CONTROL
ON_CONTROL(wNotifyCode,id,memberFxn)
说明:
表明哪个函数将处理一个常规控制表示消息。控制标识消息是那些从一个控制夫发送到母窗口的消息。

ON_MESSAGE
ON_MESSAGE(message,memberFxn)
说明:
指明哪个函数将处理一用户定义消息。用户定义消息通常定义在WM_USER到0x7FF范围内。用户定义消息是那些不是标准Windows WM_MESSAGE消息的任何消息。在用户的消息映射中,每个必须被映射到一个消息处理函数。用户定义消息应该有一个ON_MESSAGE宏语句。

ON_REGISTERED_MESSAGE
ON_REGISTERED_MESSAGE(nmessageVarible,memberFxn)
说明:
Windows的RegisterWindowsMesage函数用于定义一个新窗口消息,此消息保证在整个系统中是唯一的。此宏表明哪个函数处理已注册消息。变量nMessageViable应以NEAR修饰符来定义。

ON_UPDATE_COMMAND_UI
ON_UPDATE_COMMAND_UI(id,memberFxn)
说明:
此宏通常通过ClassWizard被插入一个消息映射,以指明哪个函数将处理一个用户接口个更改命令消息。在用户的消息映射中,每个用户接口更改命令(比讯被映射到一个消息处理函数)应该有一个ON_UPDATE_COMMAND_UI宏语句。

ON_VBXEVENT
ON_VBXEVENT(wNotifyCode,memberFxn)
说明:
此宏通常通过ClassWizard被插入一个消息映射,以指明哪个函数将处理一个来自VBX控制的消息。在用户的消息映射中每个被映射到一消息处理函数的VBX控制消息应该有一个宏语句。

RUNTIME_CLASS
RUNTIME_CLASS(class_name)
说明:
使用此宏从c++类民众获取运行时类结构。RUNTIME_CLASS为由class_name指定的类返回一个指针到CRuntimeClass结构。只有以DECLARE_DYNAMIC,DECLARE_DYNCREATE或DECLARE_SERIAL定义的CObject派生类才返回到一个CRuntimeClass结构的指针。

THROW
THROW(exception_object_pointer)
说明:
派出指定的异常。THROW中断程序的运行,把控制传递给用户程序中的相关的CATCH块。如果用户没有提供CATCH块,那么控制被传递到一个MFC模块,他打印出一个错误并终止运行。

THROW_LAST
THROW_LAST()
说明:
此宏允许用户派出一个局部建立的异常。如果用户试图排除一个刚发现的异常,那么一般此异常将溢出并被删除。使用THROW_LAST,此异常被直接传送到下一个CATCH处理程序。

TRACE
TRACE(exp)
说明:
把一个格式化字符串送到转储设备,例如,文件或调试监视器,而提供与printf相似的功能。同MS_DOS下C程序的printf一样,TRACE宏是一个在程序运行时跟踪变量值的方便形式。在DEBUG环境中,TRACE宏输出到afxDump。在Release版中他不做任何工作。
注释:
此宏只在MFC的DEBUG版中有效。

TRACE0
TRACE0(exp)
说明:
与TRACE相似,但他把跟踪字符串放在代码段中,而不是DGROUP,因此使用少的DGROUP空间。TRACE0是一组跟踪宏的一个变体,这些宏可用于调试输出。这一组包括TRACE0,TRACE1,TRACE2和TRACE3,这些宏不同在于所取参数的数目不同。TRACE0只取一个格式化字符串并可用于简单文本消息。TRACE1取一格式化字符串加上一个变量——一个将转储的变量。同样,TRACE2,TRACE3分别取2个或3个参数(在格式化字符串之后)。如果用户以便以了应用程序的发行版,那么它只把数据转储到afxDump。
注释:
此宏只在MFC的DEBUG中有效。


TRACE1
TRACE1(exp,param1)
说明:
参见TRACE0

TRACE2
TRACE2(exp,param1,param2)
说明:
参见TRACE0

TRACE3
TRACE3(exp,param1,param2,param3)
说明:

TRY
TRY
说明:
使用此宏建立一TRY块。一个TRY识别一个可排除异常的代码块。这些异常在随后的CATCH和AND_CATCH块处理。传递是允许的:异常可以传递一个外部TRY块,或者忽略它们或者使用THROW_LAST宏。

VERIFY
VERIFY(booleanExpression)
说明:
在MFC的DEBUG版中,VERIFY宏计算它的变量值。 如果结果为0,那么宏打印一个诊断消息并中止程序。如果条件不为0,那么什么工作也不作。 诊断有如下形式: assertion failed in file in line 其中name是源文件的名字,num是在源文件中失败的中止行号。在MFC的Release版中,VERIFY计算表达式值但不打印或中止程序。例如:如果表达式是个函数调用,那么调用成功。
posted @ 2007-01-15 19:20 dyh 阅读(155) | 评论 (0)编辑 收藏
 
     摘要: [转载]VC中的一些常用方法(20条) 文章来源: www.csdn.net 作者blog: http://blog.csdn.net/oury/ VC中的一些常用方法 //一、打开CD-ROMmciSendStrin...  阅读全文
posted @ 2007-01-15 19:14 dyh 阅读(175) | 评论 (0)编辑 收藏

2007年1月8日

写这个文章完全是因为想要搞清楚 vc 怎么布局每个 c++ 对象,以及怎样完成指针的转换的过程.
  先问一个问题,两个不同类型的指针相互转换以后,他们在数值上是一样的吗?比如:

    int nValue = 10;
    int *pInt = &nValue;
    void *pVoid = pInt;
    char *pChar = (char*)pInt;


  这些指针的值(不是说指针指向的内存的内容)是一样的吗? 如果你的回答是 yes,那如果是一个类的继承体系呢?在继承类向基类转换的过程中,指针的数值还是不变化的么?如果你的回答是"不一定会变化,要看类的体系是怎么设计的"的话,那恭喜你,不用看下去了.如果你还不确定究竟变还是不变,究竟哪些变,哪些不变,究竟为什么要变为什么不变的话,接着看下来.

  c++ 标准不规定 c++ 实现的时候的对象的具体的内存布局,除了在某些方面有小的限制以外,c++ 对象在内存里面的布局完全是由编译器自行决定,这里我也只是讨论 vc++ .net 2003 build 7.1.3091 的实现方式,我并没有在 vc5 vc6 vc.net 2002 以及其他的 2003 build 上面做过测试,结论也许不适合那些编译平台.这些属于编译器具体实现,ms 保留有在不通知你我的情况下作出更改的权利.废话这么多,马上开始.

  对于 c 的内建指针的转换,结果是不用多讨论的,我们只是讨论 c++ 的对象.从最简单的开始.

    class CBase
    {
    public:
      int m_nBaseValue;
    };


  这样的一个类在内存里放置是非常简单的,他占有4个 bytes 的空间,不用多说,我们从他派生一个类出来.

    class CDerive1 : public CBase
    {
    public:
      int m_nDerive1Value;
    };


  CDerive1 的对象在内存里面是怎么放的呢? 也很简单,占有8个 bytes 的空间,前4个 bytes 属于 CBase 类,后四个 bytes 属于自己.一个CDerive1 的指针转换成一个 CBase 的指针,结果是一样的.下面我们加上多重继承看看.

    class CFinal : public CDerive,public CBase // 这里的 CDerive 是一个和 CBase 差不多的基类
    {
    public:
      int m_nFinalValue;
    };


  CFinal 的对象在内存里面的布局稍微复杂一点,但是也很容易想象,他占有 12 个 bytes 的空间,前4个属于 CDerive,中间4个属于 CBase,后面4个才是自己的.那一个 CFinal 的指针转换成一个 CDerive 指针,数值会变么? 转换成一个 CBase 指针呢?又会变化么?答案是,前一个不变,后一个要变化,道理非常的明显,CFinal 对象的开头刚好是一个 CDerive 对象,而 CBase 对象却在 CFinal 对象的中间,自然是要变化的了,具体怎么变化呢? 加 4 就 ok(自然要检查是否是空指针).

    CBase *pBase = pFinal ? (CBase*)((char*)pFinal + sizeof(CDerive)) : 0;// 当你写下 pBase = pFinal 的时候,其实是这样的

  这种不带 virtual 的继承就这么简单,只是加上一个 offset 而已.下面我们看看如果加上 virtual function 的时候是什么样子的呢?
还是从简单类开始.

    class CBase
    {
    public:
      virtual void VirtualBaseFunction(){}
      int m_nBaseValue;
    };


  这里刻意没有使用 virtual destructor,因为这个函数稍微有些不同.还是同样的问题,CBase 类在内存上占多大的空间?还是 4 bytes 么? 答案是 no, 在我的编译器上面是 8 bytes,多出来的 4 bytes 是 __vfptr(watch 窗口看见的名字),他是一个指针,指向了类的 vtable,那什么是 vtable 呢,他是用来干什么的呢? vtable 是用来支援 virtual function 机制的,他其实是一个函数指针数组(并不等同于c/c++语言里面的指针数组,因为他们的类型并不一定是一样的.)他的每一个元素都指向了一个你定义的 virtual function,这样通过一个中间层来到达动态连编的效果,这些指针是在程序运行的时候准备妥当的,而不是在编译的时候准备妥当的,这个就是动态联编的目的,具体是由谁来设置这些指针的呢?constructor/destructor/copy constructor/assignment operator他们完成的,不用奇怪,编译器会在你写的这些函数里面安插些必要的代码用来设置 vtable 的值,如果你没有写这些函数,编译器会在适当的时候帮你生成这些函数.明白一点, vtable 是用来支持 virtual function 机制的,而需要 virtual 机制的类基本上都会由一个 __vfptr 指向他自己的 vtable.在调用 virtual function的时候,编译器这样完成:

   pBase->VirtualBaseFunction(); => pBase->__vfptr[0]();// 0 是你的virtual function 在 vtable 中的 slot number,编译器决定

  现在应该很想象 CBase 的大小了吧,那这个 __vfptr 是放到什么位置的呢? 在 m_nBaseValue 之前还是之后呢? 在我的编译器上看来,是在之前,为什么要放到之前,是因为在通过 指向类成员函数的指针调用 virtual function 的时候能少些代码(指汇编代码),这个原因这里就不深入讨论了,有兴趣的同学可以看看 inside the c++ object model 一书.
  接下来,我们加上继承来看看.

    class CDerive1 : public CBase
    {
    public:
      virtual void VirtualDerive1Function();
    };


  这个时候你也许要说,内存布局跟没有 virtual 是一样的,只不过每个类多了一个 __vfptr 而已,呃...这个是不对的,在我的编译器上面 两个类共享同一个 __vfptr, vtable 里面放有两个指针,一个是两个类共享的,一个只属于 CDerive1 类,调用的时候如何呢?

   pDerive1->VirtualDerive1Function() => pDerive1->__vfptr[1]();
   pDerive1->VirtualBaseFunction() => pDerive1->__vfptr[0]();


  至于指针的相互转换,数值还是没有变化的(也正是追求这种效果,所以把 __vfptr 放到类的开头,因为调整 this 指针也是要占有运行时的时间的).

  现在加上多重继承瞧瞧,代码我不写上来了,就跟上面的 CFinal, CDerive, CBase 体系一样,只是每个类多一个VirtualxxxFunction出来,这个时候的指针调整还是没有什么变化,所以我们只是看看 vtable 的情况,你会说 CDerive 和 CFinal 共享一个 __vfptr,而 CBase 有一个自己的 __vfptr,而 CFinal 的 __vfptr 有 2 个slot,这个结论是正确的. 同时你也会说 通过 CFinal 类调用 CBase 的函数是要进行指针调整的,yes you'r right,不仅仅是 this 指针调整(呃,this 指针会成为 function 的一个参数),还要调整 vtable 的值:

   pFinal->VirtualBaseFunction() => (CBase*)((char*)pFinal + sizeof(CDerive))->__vfptr[0]();

   转换成 asm 的代码大约是这样的:

   mov eax,[pFinal] ; pFinal is a local object,pFinal will be epb - xx
   add eax,8 ; 8 = sizeof(CDerive)
   mov ecx,eax ; ecx is this pointer
   mov edx,[eax] ; edx = vtable address
   call [edx] ; call vtable[0]


  写到这里也就明白this指针是怎么调整的.带 virtual function 的继承也不复杂,this指针调整也是很简单的,下面看最复杂的部分 virtual inheritance.

  我的编译器支持虚拟继承的方式和虚函数的方式差不多,都是通过一个 table 完成,只是这个就看不到 vc 赋予的名字了,我们叫他 vbtable 吧,编译器同样在类里面加入一个指向 vbtable 的指针,我们叫他 __vbptr 吧,这个指针指向了 vbtable ,而 vbtable 里面的每一项对应了一个基类,vbtable 记录了每个基类的某一个偏移量,通过这个偏移量就能计算出具体类的指针的位置.看个简单的例子:

   class CBase
   {
   public:
     virtual ~CBase(){}
   };

   class CMid1 : public virtual CBase
   {
   public:
     virtual ~CMid1(){}
     int m_nMid1;
   };

   class CMid2 : public virtual CBase
   {
   public:
     virtual ~CMid2(){}
     int m_nMid2;
   };

   class CFinal : public CMid1,public CMid2
   {
   public:
     virtual ~CFinal(){}
     int m_nFinal;
   };

   CFinal final;
   CFinal *pFinal = &final;    // pFinal = 0x0012feb4;
   CBase *pBase = pFinal; // pBase = 0x0012fec8 = pFinal + 0x14;
   CMid1 *pMid1 = pFinal; // pMid1 = 0x0012feb4 = pFinal;
   CMid2 *pMid2 = pFinal; // pMid2 = 0x004210b4 = pFinal;


  结果让你吃惊吗? 最奇怪的地方居然是 CMid2 和 CMid1 的地址居然是一样的,这个是因为 vc 把 vbtable 放到了 CFinal 类的开头的原因,而CMid1 和 CMid2 也同样要使用这个 vbtable, 所以 这个三个的地址也就必须相同了.那 CBase 的地址是怎么出来的呢? 呃...刚刚我们说了 vbtable 放到了CFinal 的开头(vc 一定会放在开头吗?答案是不一定,这个稍后解释).在我的机器上面 final 对应内存的第一个 dword 是 0x00426030,查看这个地址,第一个dword 是 0 ,第二个就是 0x14,刚好和 pBase 的偏移相同,这个只是巧合,也许你换个类的继承体系就完全不同了,但是我只是想说明一点,基类的偏移计算是和 vbtable 的值相关联的.下面我们就来看看 vc 是怎么计算这些偏移的.
  vc 在分析我们的代码的时候,生成了一份类的继承体系信息,其中有一个叫 thisDisplacement 的_PMD结构:

    struct _PMD // total undocumented
    {
      int mdisp; // i think the meaning is Multiinheritance DISPlacement
      int pdisp; // Pointer to vbtable DISPlacement
      int vdisp; // Vbtable DISPlacement
    };


  结构的名字和成员变量的名字确确实实是 vc 的名字(在 watch 窗口输入 (_PMD*)0 就能看到这个结构的详细信息),每个字段的含义却是我自己猜测出来的.mdisp 大概用来表示多重继承(包括单一继承)的时候的偏移量,pdisp 表示 vbtable 的偏移量,而 vdisp 表示类在 vbtable 里面的下标.那么有了这个结构怎样才能完成指针的转换呢?假如我们有一个派生类指针 pFinal,要转换成一个特定的基础类,我们首先要知道和这个基类对应的 _PMD 结构的信息(这个信息的获取,我暂时没有找到一个非常方便的方法,现在我使用的方法下面会有描述),有了这个信息以后,转换就方便了.首先找到 vbtabel 的地址 *(pFinal + pdisp),然后找到基类的偏移 *(*(pFinal + pdisp) + vdisp) 这个偏移值是相对vbtable的,所以还要加上 vbtable的偏移,最后加上 mdisp的偏移,如下:

  char *pFinal = xxx; // need a init value
  char *pBase; // we must calc
  pBase = pFinal + mdisp + *(int *)(*(int *)(pFinal + pdisp) + vdisp) + pdisp;


  注意: 当 pdisp < 0 的时候就表示这个类没有 vbtable 直接使用 pFinal + mdisp 就得到结果了.
  所以这个结构是一个通用的结构,专门用作类型转换,不管是有无虚继承都能使用这个结构进行类型转换.

  通过这个结构,我们也能看到 vc 是怎样布局这个 object 的.

  看到这里,也许你要大呼一口气,妈妈呀,一个类型转换要这么的麻烦吗?我直接写 pBase = pFinal 不就可以了吗? 恭喜你还没有被我忽悠得晕头转向,哈哈.其实你写下那行语句的时候,编译器在帮你做这个转换,大约生成下面的代码

    mov eax,[pFinal] ;final address
    mov ecx,[eax] ; vbtable address *(int *)(pFinal + pdisp)
    mov edx,eax ; save to edx
    add edx,[ecx + 4] ; ecx + 4 is (*(int *)(pFinal + pdisp) + vdisp)
    mov [pBase],edx ; edx = pFinal + mdisp + *(int *)(*(int *)(pFinal + pdisp) + vdisp) + pdisp;
    ; here mdisp = 0, pdisp = 0, vdisp = 4


  也许你要说了,我要这些东西来干什么?要转换的时候直接转换就好了,编译器会帮做,的确,大多数的时候确实是这样,但是,在某些时候却并不如此,现在你要实现一个功能,输入一个指针,输入一个 _PMD 结构,你要实现一个AdjustPointer 的函数来生成另一个指针.这个时候你也只能这样完成了,因为我没有给你两个指针的名字,就算给了你字符串形式的名字也没有用,呃....你也许会说,办法是有的,的确是有,模板就能实现这种功能,呵..这个我们暂时不讨论具体的实现细节.也许你要问了,究竟什么时候会去实现这种听都没有听过的功能,其实这个函数是真正存在的,只不过不是由你来实现的,而是 ms 的人实现的,你只用写一个 带有 c++ 异常的程序,使用 ida 反汇编,然后查找函数,就能找到这个函数了,他用来在异常处理时创建 catch 所需要的 object.至于这个详细的信息,请期待.我会最快速度写出关于 vc 是怎样实现 c++ 异常的文章来.

  最后了,说说那个 _PMD 结构的获取方式.看的时候不要吃惊,方法比较的麻烦,比如我想知道和 CFinal 类相关的 _PMD 信息,先新建工作,写下 throw pFinal 这样的语句,编译,在这个语句的地方设置断点,运行,转到反汇编,进入 __CxxThrowException@8 函数,这个时候不出意外你能看到一个叫 pThrowInfo 的东西(如果看不到,请打开"显示符号名"选项),在 watch 窗口里面输入pThrowInfo,展开他,看到一个pCatchableTypeArray,记录下他的 nCacthableTypes的值,然后在 watch 里面输入
pThrowInfo->pCatchableTypeArray->arrayOfCatchableTypes[0] 到 pThrowInfo->pCatchableTypeArray->arrayOfCatchableTypes[n], n 就是你刚刚记录的值减1,再展开他们,你就能看到一个 thisDisplacement 的数据,继续展开就是 mdisp 等等了,很是麻烦吧.哈..你已经猜到了,这个是和异常有关系的.

  后记: 这段时间,我一直在读些反汇编之后的代码,也颇有些心得,所以才有想法写一些文章,探讨 vc 编译器鲜为人知(太过狂妄了)的秘密,这个方面的文章也有人写过,那些文章也给我不少的启发,我不认为自己是第一个发现这些秘密的人,但是至少我自己知道的,我是第一个把这些东西写出来的人.文章里面作墨多的部分都是自己发现的.就这个文章里面的内容来说,inside the c++ object model 是有比较详细的描写,但是他并不是转换针对 vc 这个编译器的实现,而 _PMD 这个结构我也没有在什么地方见有人描述过,只是在 windows develop network 的2002年12月的杂志上看有人提到过这个结构,可惜他却没有了解(至少他在他发表文章的时候是如是说的)这个结构的用处(正是因为这个原因,我才有写这个文章以及后续文章的冲动).所以,这个文章也算是我自己的原创吧.这个文件虽然和游戏制造没有太大的关系,但是小 T 自视清高,不愿意自己的文章被一帮不懂的人评价来评价去的,所以也没有发到那些著名的 xxx 网站,只发 goldpoint.转载请注明出处(小 T 对自己的第一个原创文章比较珍惜,比较重视,谢谢).
posted @ 2007-01-08 21:17 dyh 阅读(255) | 评论 (0)编辑 收藏

2006年12月13日

安装Visual Studio.net 2003时,系统提示"安装程序检测到另一个程序要求计算机重新启动。必须重新启动计算机后才能安装 Visual Studio .NET 系统必备。系统重新启动后,您需要重新启动安装程序。单击“确定”重新启动。单击“取消”退出安装程序,以后再安装。"系统重新启动后,问题依旧。
解决方法:
打开注册表,找到下面主键并删除
HKEY_LOCAL_MACHINE\SYSTEM\Control001\Contrl\Session Manager\PendingFileRenameOperations,删除后的效果是立杆见影的,马上就可以安装了。
posted @ 2006-12-13 21:16 dyh 阅读(940) | 评论 (3)编辑 收藏

2006年6月22日

一、在程序中包含commctrl.h头文件,链接到ComCtl32.lib,并调用InitCommon Controls.

#if  (_WIN32_IE >= 0x0300)
    INITCOMMONCONTROLSEX iccx;
    iccx.dwSize 
=   sizeof (iccx);
    iccx.dwICC 
=  ICC_COOL_CLASSES  |  ICC_BAR_CLASSES;
    BOOL bRet 
=  ::InitCommonControlsEx( & iccx);
    bRet;
    ATLASSERT(bRet);
#else
    ::InitCommonControls();
#endif


二、向项目中添加一个名为YourApp.exe.manifest的文件,其中的YourApp就写你的可执行文件的名称,这个文件具有XML格式:

   <? xml version="1.0" encoding="UTF-8" standalone="yes"  ?>  
< assembly  xmlns ="urn:schemas-microsoft-com:asm.v1"  manifestVersion ="1.0" >
  
< assemblyIdentity  version ="1.0.0.0"  processorArchitecture ="X86"  name ="CompanyName.ProductName.YourApp"  type ="win32"   />  
  
< description > Your application description here. </ description >  
< dependency >
< dependentAssembly >
  
< assemblyIdentity  type ="win32"  name ="Microsoft.Windows.Common-Controls"  version ="6.0.0.0"  processorArchitecture ="X86"  publicKeyToken ="6595b64144ccf1df"  language ="*"   />  
  
</ dependentAssembly >
  
</ dependency >
  
</ assembly >

三、在应用程序的资源文件(YourApp.rc)的顶部中添加这样的一行语句:

CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST  "YourApp .exe.manifest "

我在程序中没有做第一步也能成功,可能是因为没有用API创建的窗口或动态窗口,在程序中如果调用了CreateWindow函数去创建窗口,则需要执行第一步。

posted @ 2006-06-22 13:04 dyh 阅读(191) | 评论 (0)编辑 收藏
仅列出标题  下一页