可视化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 阅读(1858) | 评论 (0)编辑 收藏
 
[转载]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 阅读(353) | 评论 (0)编辑 收藏
 
     摘要: [转载]VC中的一些常用方法(20条) 文章来源: www.csdn.net 作者blog: http://blog.csdn.net/oury/ VC中的一些常用方法 //一、打开CD-ROMmciSendStrin...  阅读全文
posted @ 2007-01-15 19:14 dyh 阅读(386) | 评论 (0)编辑 收藏
 
写这个文章完全是因为想要搞清楚 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 阅读(1137) | 评论 (1)编辑 收藏
 
安装Visual Studio.net 2003时,系统提示"安装程序检测到另一个程序要求计算机重新启动。必须重新启动计算机后才能安装 Visual Studio .NET 系统必备。系统重新启动后,您需要重新启动安装程序。单击“确定”重新启动。单击“取消”退出安装程序,以后再安装。"系统重新启动后,问题依旧。
解决方法:
打开注册表,找到下面主键并删除
HKEY_LOCAL_MACHINE\SYSTEM\Control001\Contrl\Session Manager\PendingFileRenameOperations,删除后的效果是立杆见影的,马上就可以安装了。
posted @ 2006-12-13 21:16 dyh 阅读(2401) | 评论 (5)编辑 收藏
 

一、在程序中包含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 阅读(591) | 评论 (0)编辑 收藏
 
大家在使用C++写操作文件的程序时,一定使用过eof()这个函数,用它来判别文件结束,但有不少也用来判别文件是否为空的.但是,这样操作的结果不是我们所想的.看下面程序:

#include <string>

using namespace std;

int main()
{
    
char c = 'c';
    ifstream FILE(
"test.txt");
    
if (FILE.eof())
        cout 
<< "文件是空的."<<endl;exit(1); 

    
while (!FILE.eof())
    
{
        FILE.
get(c);
        cout 
<< c;
    }
    
    system(
"pause");
    
return 0;
}


当test.txt为空文件时,它输出的是:c
奇怪!应该输出是:文件是空的. while里面的操作也应该不用到的.但是结果偏偏和我们所想的相反.
好,那操作二进制文件又是怎样的呢?修改下为:
   

 ifstream FILE("test.txt",ios::in|ios::binary);
    
if (FILE.eof())
        cout 
<< "文件是空的."<    while (!FILE.eof())
    
{
        FILE.read(
&c,1);
        cout 
<< c;
    }
//代码其他部分相同

结果输出还是c..噢!!怎么会这样的.分明是骗人的东西嘛!!到底是什么原因呢?
     经过一段研究后,原来eof()返回true的条件是"读到文件结束符",而不是文件内容的最后一个字符。
要清楚"文件结束符"(0xff).就是说我们文件最后的字符不是文件结束符,而最后的字符的下一位才是.所以操作再读多一次.就为什么上面if (FILE.eof())总是false的. 在一些编译器中(dev c++),它读到最后一个字符后文件位置的指针会定在那儿,所以就会重复最后一个字符.     
    在一个外国的CPP论坛见到一位同志的代码刚好有这解决方法.现在把上面的代码改为下面的:

#include <iostream.h>
#include 
<stdlib.h>
#include 
<fstream.h>

int main()
{
    
char c = 'c';
    ifstream FILE(
"test.txt");
    
if (FILE.peek() == EOF)//修改 
    {
        cout 
<< "文件是空的."<< endl; 
        exit(
1);
    }
       
    
while (FILE.peek() != EOF)//修改
    {
        FILE.
get(c);
        cout 
<< c;
    }
    
    system(
"pause");
    
return 0;
}

主要的是把eof()改为peek() == EOF来判别,其中peek()是取文件当前指针,EOF是文件尾标符,它的值为-1.所以采用这种方法就解决上面eof()的问题了..这种方法也可以用在读写二进制文件中.

posted @ 2006-06-21 21:10 dyh 阅读(4829) | 评论 (6)编辑 收藏
 

        由于项目的需要,需要用C++去连Oracle数据库,Oracle版本为10g,在经过n次错误后终于成功连接。
        最开始用ADO连,装上客户端以后,在Oracle Net Manager中设置服务命名,测试连接成功,然后
设置ODBC数据源,测试也成功,ADO连接串写上"DSN=xxx"就能连上了,但是在释放连接时总是出错,
经过多次尝试后放弃,在网上看到Oracle专门为C++提供了连接的接口OCCI,于是尝试采用这种方法。
        一、安装Oracle客户端
                安装方式选择为管理员。安装完以后设置服务命名。在%ORACLIENTHOME%\NETWORK\ADMIN目录下tnsnames.ora文件记录了服务命名的设置。我的设置如下:
               

ORCL_192.168.0.3 =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.3)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SID = orcl)
      (SERVER = DEDICATED)
    )
  )

ORCL =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.3)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = ORCL_192.168.0.3)
    )
  )

其中192.168.0.3为远程Oracle服务器IP地址
设置完以后在Net Manager中测试连接,成功就OK了。
        二、设置VC++.net环境
        我用的编译器是VS2003,首先在工具---选项中Project添加包含文件%OracleClientHome%\Oci\include
添加库文件%OracleClientHome%\Oci\lib\msvc\vc71和%OracleClientHome%\Oci\lib\msvc如果是VC6.0的话就
添加%OracleClientHome%\Oci\lib\msvc\vc6。
然后设置项目属性,在编译器---链接中添加lib文件,oraocci10d.lib,之后把oraocci10d.dll文件复制到system32目录下,这个文件在VC71目录下。
第三步在C++----代码生成中设置运行时库为多线程DLL或是多线程调试DLL,注意这一步设置很重要,
没有设置的话会造成getString函数出错。
        三、代码
       

#include  " stdafx.h "
#include 
< occi.h >
using   namespace  std;
using   namespace
 oracle::occi ;
int
 main() 
{
    Environment
*  env = Environment::createEnvironment( " ZHS16GBK " " UTF8 "
);
    
// Environment *env=Environment::createEnvironment(Environment::DEFAULT);


    
string  mc;
    
{
        Connection 
* conn  =  env -> createConnection( " system " " system " " orcl_192.168.0.3 "
);
        
        
try

        
{
            Statement 
* stmt  =  conn -> createStatement( " select * from test1 "
);

            ResultSet 
* rs  =  stmt ->
executeQuery();
            
while (rs -> next()  ==   true
)
            
{
                mc
= rs -> getString( 1
);
                cout 
<<  mc  <<
 endl;
            }

            stmt
-> closeResultSet(rs);
            conn
->
terminateStatement(stmt);
        }

        
catch  (SQLException e)
        
{
            cout
<<
e.what();
        }

        env
-> terminateConnection(conn);
    }

    Environment::terminateEnvironment(env);
    system(
" pause " );
    
return   0
;
}

orcl_192.168.0.3就是前面设置的连接串。
测试成功。以上就是所有的设置过程,唉,还是jdbc好连阿。。。

注意,项目属性中运行时库一定要设置为多线程DLL或是多线程调试DLL,否则getString函数就会出错,我在这个问题上也卡了很长时间,单步调试发现是在string对象析购时出错,在网上看到一篇文章得到答案。
        原因是由于程序中使用的内存管理多来源于crt提供的例程,而非直接使用操作系统的接口,这些例程都需要维护一些module全局数据(例如维护池、维护空闲块、或者标记已申请的块等等,不同的实现中有不同的作用),当他们被静态连编时,实际上这些“全局数据”就不“全局”了,不同的module各自为政,每份module都有自己的“全局数据”,自身的内存信息不为他人所知,module A的合法内存快自然不可能通得过module B的合法性验证

解决问题的方法有:
1、不要跨module传递c++对象,或者避免释放跨module申请的内存

2、将参与合作的module统统以multithreaded dll方式链入crt库,让他们的“全局”数据真正全局,注意,所有有交互的module都需要动态链入crt。

posted @ 2006-06-20 13:07 dyh 阅读(5986) | 评论 (12)编辑 收藏
 

1)运行时库就是 C run-time library,是 C 而非 C++ 语言世界的概念:取这个名字就是因为你的 C 程序运行时需要这些库中的函数.

2)C 语言是所谓的“小内核”语言,就其语言本身来说很小(不多的关键字,程序流程控制,数据类型等);所以,C 语言内核开发出来之后,Dennis Ritchie 和 Brian Kernighan 就用 C 本身重写了 90% 以上的 UNIX 系统函数,并且把其中最常用的部分独立出来,形成头文件和对应的 LIBRARY,C run-time library 就是这样形成的。

3)随后,随着 C 语言的流行,各个 C 编译器的生产商/个体/团体都遵循老的传统,在不同平台上都有相对应的 Standard Library,但大部分实现都是与各个平台有关的。由于各个 C 编译器对 C 的支持和理解有很多分歧和微妙的差别,所以就有了 ANSI C;ANSI C (主观意图上)详细的规定了 C 语言各个要素的具体含义和编译器实现要求,引进了新的函数声明方式,同时订立了 Standard Library 的标准形式。所以C运行时库由编译器生产商提供。至于由其他厂商/个人/团体提供的头文件和库函数,应当称为第三方 C 运行库(Third party C run-time libraries)。

4)C run-time library里面含有初始化代码,还有错误处理代码(例如divide by zero处理)。你写的程序可以没有math库,程序照样运行,只是不能处理复杂的数学运算,不过如果没有了C run-time库,main()就不会被调用,exit()也不能被响应。因为C run-time library包含了C程序运行的最基本和最常用的函数。


5)到了 C++ 世界里,有另外一个概念:Standard C++ Library,它包括了上面所说的 C run-time library 和 STL。包含 C run-time library 的原因很明显,C++ 是 C 的超集,没有理由再重新来一个 C++ run-time library. VC针对C++ 加入的Standard C++ Library主要包括:LIBCP.LIB, LIBCPMT.LIB和 MSVCPRT.LIB

6)Windows环境下,VC提供的 C run-time library又分为动态运行时库和静态运行时库。
动态运行时库主要是DLL库文件msvcrt.dll(or MSVCRTD.DLL for debug build),对应的Import library文件是MSVCRT.LIB(MSVCRTD.LIB for debug build)
静态运行时库(release版)对应的主要文件是:
LIBC.LIB (Single thread static library, retail version)
LIBCMT.LIB (Multithread static library, retail version)

msvcrt.dll提供几千个C函数,即使是像printf这么低级的函数都在msvcrt.dll里。其实你的程序运行时,很大一部分时间时在这些运行库里运行。在你的程序(release版)被编译时,VC会根据你的编译选项(单线程、多线程或DLL)自动将相应的运行时库文件(libc.lib,libcmt.lib或Import library msvcrt.lib)链接进来。

编译时到底哪个C run-time library联入你的程序取决于编译选项:
/MD, /ML, /MT, /LD   (Use Run-Time Library)
你可以VC中通过以下方法设置选择哪个C run-time library联入你的程序:
To find these options in the development environment, click Settings on the Project menu. Then click the C/C++ tab, and click Code Generation in the Category box. See the Use Run-Time Library drop-down box.

从程序可移植性考虑,如果两函数都可完成一种功能,选运行时库函数好,因为各个 C 编译器的生产商对标准C Run-time library提供了统一的支持.

posted @ 2006-06-20 12:11 dyh 阅读(551) | 评论 (0)编辑 收藏
仅列出标题
共2页: 1 2