榕树驿站

要是生命真的有如果,就不会有这么多的遗憾;要是等一分钟可以如愿,就不会有这么多的伤感...
posts(11) comments(13) trackbacks(0)
  • C++博客
  • 联系
  • RSS 2.0 Feed 聚合
  • 管理

常用链接

  • 我的随笔
  • 我的评论
  • 我参与的随笔

留言簿

  • 给我留言
  • 查看公开留言
  • 查看私人留言

随笔分类

  • 编程技术(5)
  • 基础学习(5)
  • 脚本技术
  • 心情小站(1)

随笔档案

  • 2007年6月 (2)
  • 2007年4月 (9)

文章分类

  • 军事看台
  • 舞文弄墨
  • 政治经济

技术好友

  • nianshao's blog

搜索

  •  

最新评论

  • 1. re: Select模型[未登录]
  • 扯淡的东西
    select是这样用的吗
  • --123
  • 2. re: vc自定义消息的发送与接收的方法实现
  • 非常详细,谢谢分享
  • --holmsLI
  • 3. re: Select模型
  • 写得很好了 谢谢 哈哈
  • --jemmyLiu
  • 4. re: vc自定义消息的发送与接收的方法实现[未登录]
  • @华可
    谢谢你,能否加个博客链接?
  • --榕树藤
  • 5. re: vc自定义消息的发送与接收的方法实现
  • 评论内容较长,点击标题查看
  • --华可

阅读排行榜

评论排行榜

HOWTO: Create Debug Symbols for a Visual C++ Application

在 Visual C++ .NET 开发环境中为应用程序创建符号文件:
1. 在“解决方案资源管理器”中,右击要修改的项目,然后单击属性。
2. 在“配置属性”窗口中,单击C/C++ 文件夹,然后单击常规。
3. 在调试信息格式下拉列表中,单击选择程序数据库 (/Zi)。
4. 如果您希望在调试转储文件或执行实时调试时获得有关局部变量的信息,请单击选择优化下拉列表中的禁用 (/Od)。
5. 在“配置属性”窗口的 Linker 文件夹中,确保“调试”子类别的生成调试信息属性设置为是 (/DEBUG)。备注:尽管并不要求您在同一子类别中同样启用生成映射文件,但该选项会创建一个 .map 文件,当您只有发生访问冲突的地址时,该文件对确定发生故障的位置可能很有用。有关更多信息,请参见“参考”部分。
在 Visual C++ 6.0 开发环境中为应用程序创建符号文件:
1. 在项目菜单上,选择设置。
2. 在C/C++ 选项卡上,将类别更改为常规。
3. 在调试信息下拉列表中,选择程序数据库。备注:如果您希望在调试转储文件或执行实时调试时获得有关局部变量的信息,还应该选择优化下拉列表中的禁用(调试)。

4. 在链接选项卡上,将类别更改为调试。
5. 在调试信息部分,确保选中调试信息 选项,然后选择两种格式。备注:尽管不要求您在链接选项卡上同样启用常规类别中的生成映射文件,但该选项会创建一个 .map 文件,当您只有发生访问冲突的地址时,该文件对确定发生故障的位置可能很有用。(有关更多信息,请参见“参考”。)

posted @ 2007-06-11 22:54 榕树藤 阅读(415) | 评论 (0) | 编辑 收藏

VC++6.0编译环境介绍

大家可能一直在用VC开发软件,但是对于这个编译器却未必很了解。原因是多方面的。大多数情况下,我们只停留在“使用”它,而不会想去“了解”它。因为它只是一个工具,我们宁可把更多的精力放在C++语言和软件设计上。我们习惯于这样一种“模式”:建立一个项目,然后写代码,然后编译,反反复复调试。但是,所谓:“公欲善其事,必先利其器”。如果我们精于VC开发环境,我们是不是能够做得更加游刃有余呢?


Visual C++可新建的 Projects项目

Visual C++可新建的 File文件

 

Visual C++的Build设置
1.Compile TEST.cpp选项 只编译当前文件而不调用链接器或其它工具。输出窗口将显示编译过程检查出的错误或警告信息,在错误信息处单击鼠标右键,可以得到错误代码的位置
2. Build TEST.exe 选项 对最后修改过的源文件进行编译和链接
3. Rebuild All选项 该选项允许用户编译所有的源文件,而不管它们何时曾经被修改过
4. Batch Build选项 该选项能单步重新建立多个工程文件,并允许用户指定要建立的项目类型.VC提供了两种目标应用程序类型 Win32 Release(发行版)、Win32 Debug(调试版)。


 

我们先来看一下VC的处理流程,大致分为两步:编译和连接。源文件通过编译生成了.obj文件;所有.obj文件和.lib文件通过连接生成.exe文件或.dll文件。下面,我们分别讨论这两个步骤的一些细节。

 

工程配置对话框 在这个对话框中,左上方的下拉列表框用于选择一种工程配置,包括有Win32 Debug、Win32 Release和All Configurations(指前两种配置一起),某些选项在不同的工程配置中有不同的缺省值。左边的树形视图给出了当前工程所有的文件及分类情况。如果我们把工程“Schedule”置为高亮显示(正如图9-1那样),对话框的右边就会出现总共十个选项卡,其中列出了与工程有关的各种选项,不少选项卡中有一个Reset按钮,按下它后可以把选项卡内的各项设置恢复到生成工程时的初始值。如果我们在树形视图中选择一个文件类或一个文件,那么对话框右边的选项卡会自动减少到一个或两个,其中列出的都是与选中的文件类或文件有关的选项。

 编译参数的设置。主要通过VC的菜单项Project->Settings->C/C++页来完成。我们可以看到这一页的最下面Project Options中的内容,一般如下:

/nologo /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_AFXDLL" /D "_MBCS" /Fp"Debug/WritingDlgTest.pch" /Yu"stdafx.h" /Fo"Debug/" /Fd"Debug/" /FD /GZ /c

各个参数代表的意义,可以参考Project Option语法解释。比如/nologo表示编译时不在输出窗口显示这些设置(我们可以把这个参数去掉来看看效果)等等。一般我们不会直接修改这些设置,而是通过这一页最上面的Category中的各项来完成。

1)General:一些总体设置。Warning level用来控制警告信息,None表示不显示任何警告,L1表示只显示严重的警告,L2表示显示比L1次严重的警告,L4则表示显示出所有的警告,包括那些安全忽略的警告;Warnings as errors将警告信息当作错误处理,这样在编译完毕后就无法启动连接器来进行连接;Optimizations是代码优化,可以在Category的Optimizations项中进行更细的设置;Generate browse info用以生成.sbr文件,记录类、变量等符号信息,可以在Category的Listing Files项中进行更多的设置。Debug info,生成调试信息:None,不产生任何调试信息(编译比较快);Line Numbers Only,仅生成全局的和外部符号的调试信息到.OBJ文件或.EXE文件,减小目标文件的尺寸;C 7.0- Compatible,记录调试器用到的所有符号信息到.OBJ文件和.EXE文件;Program Database,创建.PDB文件记录所有调试信息;Program Database for "Edit & Continue",创建.PDB文件记录所有调试信息,并且支持调试时编辑。

 

2)C++ Language:pointer_to_member representation用来设置类定义/引用的先后关系,一般为Best-Case Always表示在引用类之前该类肯定已经定义了;Enable Exception Handling,进行同步的异常处理;Enable Run-Time Type Information迫使编译器增加代码在运行时进行对象类型检查;Disable Construction Displacements,设置类构造/析构函数调用虚函数问题。

 

  

3)Code Generation:Processor表示代码指令优化,可以为80386、80486、Pentium、Pentium Pro,或者Blend表示混合以上各种优化。Use run-time library用以指定程序运行时使用的运行时库(单线程或多线程,Debug版本或Release版本),有一个原则就是,一个进程不要同时使用几个版本的运行时库。Single-Threaded,静态连接LIBC.LIB库;Debug Single-Threaded,静态连接LIBCD.LIB库;Multithreaded,静态连接LIBCMT.LIB库;Debug Multithreaded,静态连接LIBCMTD.LIB库;Multithreaded DLL,动态连接MSVCRT.DLL库;Debug Multithreaded DLL,动态连接MSVCRTD.DLL库。连接了单线程库就不支持多线程调用,连接了多线程库就要求创建多线程的应用程序。Calling convention可以用来设定调用约定,有三种:__cdecl、__fastcall和__stdcall。各种调用约定的主要区别在于,函数调用时,函数的参数是从左到右压入堆栈还是从右到左压入堆栈;在函数返回时,由函数的调用者来清理压入堆栈的参数还是由函数本身来清理;以及在编译时对函数名进行的命名修饰(可以通过Listing Files看到各种命名修饰方式)。Struct member alignment用以指定数据结构中的成员变量在内存中是按几字节对齐的,根据计算机数据总线的位数,不同的对齐方式存取数据的速度不一样。这个参数对数据包网络传输等应用尤为重要,不是存取速度问题,而是数据位的精确定义问题,一般在程序中使用#pragma pack来指定。

 

4)Customize:Disable Language Extensions,表示不使用微软为标准C做的语言扩展;Eliminate Duplicate Strings,主要用于字符串优化(将字符串放到缓充池里以节省空间),使用这个参数,使得

char *sBuffer = "This is a character buffer";

char *tBuffer = "This is a character buffer";

sBuffer和tBuffer指向的是同一块内存空间;Enable Function-Level Linking ,告诉编译器将各个函数按打包格式编译;Enables minimal rebuild,通过保存关联信息到.IDB文件,使编译器只对最新类定义改动过的源文件进行重编译,提高编译速度;Enable Incremental Compilation,同样通过.IDB文件保存的信息,只重编译最新改动过的函数;Suppress Startup Banner and Information Messages,用以控制参数是否在output窗口输出。

 

5) Listing Files:Generate browse info的功能上面已经提到过。这里可以进行更多的设置。Exclude Local Variables from Browse Info表示是否将局部变量的信息放到.SBR文件中。Listing file type可以设置生成的列表信息文件的内容:Assembly-Only Listing仅生成汇编代码文件(.ASM扩展名);Assembly With Machine Code生成机器代码和汇编代码文件(.COD扩展名);Assembly With Source Code生成源代码和汇编代码文件(.ASM扩展名);Assembly, Machine Code, and Source生成机器码、源代码和汇编代码文件(.COD扩展名)。Listing file name为生成的信息文件的路径,一般为Debug或Release目录下,生成的文件名自动取源文件的文件名。

 

 

6)Optimizations:代码优化设置。可以选择Maximize Speed生成最快速的代码,或Minimize Size生成最小尺寸的程序,或者Customize定制优化。定制的内容包括:

Assume No Aliasing,不使用别名(提高速度);

Assume Aliasing Across Function Calls,仅函数内部不使用别名;

Global Optimizations,全局优化,比如经常用到的变量使用寄存器保存,或者循环内的计算优化,如

i = -100;

while( i < 0 ){    i += x + y;}

会被优化为

i = -100;

t = x + y;

while( i < 0 ){i += t;}

Generate Intrinsic Functions,使用内部函数替换一些函数调用(提高速度);

Improve Float Consistency,浮点运算方面的优化;

Favor Small Code,程序(exe或dll)尺寸优化优先于代码速度优化;

Favor Fast Code,程序(exe或dll)代码速度优化优先于尺寸优化;

Frame-Pointer Omission,不使用帧指针,以提高函数调用速度;

Full Optimization,组合了几种参数,以生成最快的程序代码。

Inline function expansion,内联函数扩展的三种优化(使用内联可以节省函数调用的开销,加快程序速度):Disable不使用内联;Only __inline,仅函数定义前有inline或__inline标记使用内联;Any Suitable,除了inline或__inline标记的函数外,编译器“觉得”应该使用内联的函数,都使用内联。

 

7)Precompiled Headers:预编译头文件的设置。

 预编译头的概念: 所谓的预编译头就是把一个工程中的那一部分代码,预先编译好放在一个文件里(通常是以.pch为扩展名的),这个文件就称为预编译头文件这些预先编译好的代码可以是任何的C/C++代码--------甚至是inline的函数,但是必须是稳定的,在工程开发的过程中不会被经常改变。如果这些代码被修改,则需要重新编译生成预编译头文件。你可将将一些公共的、不大变动的头文件(比如FILEX.h等)集中放到stdafx.h中,这一部分代码就不必每次都重新编译(除非是Rebuild All)。注意生成预编译头文件是很耗时间的。同时你得注意预编译头文件通常很大,通常有6-7M大。使用预编译可以提高重复编译的速度。

  也许你会问:现在的编译器都有Time stamp的功能,编译器在编译整个工程的时候,它只会编译那些经过修改的文件,而不会去编译那些从上次编译过,到现在没有被修改过的文件。那么为什么还要预编译头文件呢?答案在这里,我们知道编译器是以文件为单位编译的,一个文件经过修改后,会重新编译整个文件,当然在这个文件里包含的所有头文件中的东西(.eg Macro, Preprocessor )都要重新处理一遍。VC的预编译头文件保存的正是这部分信息。以避免每次都要重新处理这些头文件。

1.这里是使用工程里的设置,/Yu”stdafx.h”。如果使用了/Yu,就是说使用了预编译,我们在每个.cpp文件的最开头,我强调一遍是最开头,包含 你指定产生pch文件的.h文件(默认是stdafx.h)不然就会有问题。如果你没有包含这个文件,就告诉你Unexpected file end. 如果你不是在最开头包含的

2.如果你把pch文件不小心丢了,最简单的办法就是选中第一个选项“Not using....",这样就根本不用预编译头也不去寻找pch文件,就不会出错了,但是这样做的后果是每次编译、连接都化更多的时间。也可以选第二个选项”Automatic use of",然后在“Through header”力填上stdafx.h,这样如果没有pch文件系统会自动生成一个pch,如果有的话就使用这个pch,这个选项是比较“智能”的。第三个选项是强行创建一个pch文件,第四个选项是直接使用pch文件。当然“Through headers”里都填stdafx.h了。

让编译器生成一个pch文件就可以了。也就是说把 stdafx.cpp(即指定/Yc的那个cpp文件)从新编译一遍就可以了。当然你可以傻傻的 Rebuild all。

 

8) Preprocessor:预编译处理。我们可以在这里预先定义一些宏名,指定部分或所有符号具有未定义状态。Additional include directories,可以指定额外的包含目录,一般是相对于本项目的目录,如..\Include。

 

Link连接参数的设置。主要通过VC的菜单项Project->Settings->Link页来完成。我们可以看到这一页的最下面Project Options中的内容,一般如下:

/nologo /subsystem:windows /incremental:yes /pdb:"Debug/WritingDlgTest.pdb" /debug /machine:I386 /out:"Debug/WritingDlgTest.exe" /pdbtype:sept

下面我们分别来看一下Category中的各项设置。

 

1. General:一些总体设置。可以设置生成的文件路径、文件名 如在“Output   file   name:”下面的编辑框中可输入:“C:\bin\TEST.exe”;连接的库文件;Generate debug info,生成Debug信息到.PDB文件(具体格式可以在Category->Debug中设置);Ignore All Default Libraries,放弃所有默认的库连接;Link Incrementally,通过生成. ILK文件实现递增式连接以提高后续连接速度,但一般这种方式下生成的文件(EXE或DLL)较大;Generate Mapfile,生成.MAP文件记录模块相关信息;Enable Profiling,这个参数通常与Generate Mapfile参数同时使用,而且如果产生Debug信息的话,不能用.PDB文件,而且必须用Microsoft Format。  

2.  Customize:这里可以进行使用程序数据库文件的设置。选中Use program database允许使用程序数据库,这样连接器会把调试信息放在程序数据库中,如果不选中该选项,那么也不能使用递增连接方式。Force File Output ,即使某个模块引用了一些未定义或者重复定义的符号,连接器仍然会强制(但不一定能正确运行)产生输出文件(EXE或DLL);Print Progress Messages,可以将连接过程中的进度信息输出到Output窗口。

  

3. Debug:设置是否生成调试信息,以及调试信息的格式。格式可以有Microsoft Format、COFF Format(Common Object File Format)和Both Formats(两种都有)三种选择;Separate Types,表示将Debug格式信息以独立的.PDB文件存放,还是直接放在各个源文件的.PDB文件中。选中的话,表示采用后者的方式,这种方式调试启动比较快。

4. Input:这里可以指定要连接的库文件,放弃连接的库文件。还可以增加额外的库文件目录,一般是相对于本项目的目录,如..\Lib。Force Symbol References,可以指定连接特定符号定义的库。 如 在“Object/library   Modules:”下面的编辑框中输入:“TestDll.lib”;在“Additional   library   path:”下面的编辑框中输入:“C:\bin”。可用Workspace另一个工程编译的静态库
 

5.Output:Base Address可以改变程序默认的基地址(EXE文件默认为0x400000,DLL默认为0x10000000),操作系统装载一个程序时总是试着先从这个基地址开始。Entry-Point Symbol可以指定程序的入口地址,一般为一个函数名(且必须采用__stdcall调用约定)。一般Win32的程序,EXE的入口为WinMain,DLL的入口为DllEntryPoint;最好让连接器自动设置程序的入口点。默认情况下,通过一个C的运行时库函数来实现:控制台程序采用mainCRTStartup (或wmainCRTStartup)去调用程序的main (或wmain)函数;Windows程序采用WinMainCRTStartup (或 wWinMainCRTStartup)调用程序的WinMain (或 wWinMain,必须采用__stdcall调用约定);DLL采用_DllMainCRTStartup调用DllMain函数(必须采用__stdcall调用约定)。Stack allocations,用以设置程序使用的堆栈大小(请使用十进制),默认为1兆字节。Version Information告诉连接器在EXE或DLL文件的开始部分放上版本号。一般情况下都不用改变。

 

值得注意的是,上面各个参数是大小写敏感的;在参数后加上“-”表示该参数无效;各个参数值选项有“*”的表示为该参数的默认值;可以使用页右上角的“Reset”按钮来恢复该页的所有默认设置。

 

9)Resources选项卡  Resources选项卡控制着VC6的资源编译器。如图9-5所示,我们可以指定编译后生成的资源文件的路径,资源的语言类型,以及额外的资源包含目录。

 MIDL选项卡 这个选项卡与COM(组件对象模型)编程有关,我们不讨论它。

 

10)Browse Info选项卡 在这个选项卡中,我们可以指定是否在建立工程的同时也生成浏览信息文件,有了这个文件后,我们就能够在文本编辑器中通过关联菜单的相应命令快速定位到某个符号的定义或引用的地方。

 

11)Pre-link step

这个选项卡用于添加在连接之前要执行的命令。

 

l2) Post-build step

这个选项卡用于添加在工程建立完毕之后要执行的命令 如 “copy debug\TestDll.lib C:\bin\TestDll.lib” 并在C盘下建一个bin目录 做的件事情就是把TestDLL.lib拷贝到C:\bin所在的文件夹中
 

 

其它一些参数设置。

  

13) Project->Settings->General,可以设置连接MFC库的方式(静态或动态)。如果是动态连接,在你的软件发布时不要忘了带上MFC的DLL。第二个选项用于指定在编译连接过程中生成的中间文件和输出文件的存放目录,对于调试版本来说,缺省的目录是工程下面的“Debug”子目录。最下面的第三个选项用于指定是否允许每种工程配置都有自己的文件依赖关系(主要指头文件),由于绝大多数工程的调试版本和发布版本都具有相同的文件依赖关系,所以通常不需要更改该选项。

 

14) Project->Settings->Debug,可以设置调试时运行的可执行文件,如果正在编写的程序是一个DLL,那么应在此处指定一个用来调试该DLL的EXE文件。另外三个选项可以指定用于调试的工作目录,开始调试时给程序传送的命令行参数,以及进行远程调试时可执行文件的路径。把类别切换到Additional DLLs后,我们可以指定在开始调试时是否为一些额外的DLL装载调试符号信息,只有装载了符号信息后才能跟踪进DLL。

 

15)Project->Settings->Custom Build,可以设置编译/连接成功后自动执行一些操作。比较有用的是,写COM时希望VC对编译通过的COM文件自动注册,可以如下设置:

Description: Register COM

Commands: regsvr32 /s /c $(TargetPath)

echo regsvr32 exe.time > $(TargetDir)\$(TargetName).trg

Outputs: $(TargetDir)\$(TargetName).trg

 

16)Tools->Options->Directories,设置系统的Include、Library路径。

 

一些小窍门

1) 有时候,你可能在编译的时候,计算机突然非法关机了(可能某人不小心碰了电源或你的内存不稳定等原因)。当你重启机器后打开刚才的项目,重新进行编译,发现VC会崩掉。你或许以为你的VC编译器坏了,其实不然(你试试编译其它项目,还是好的!),你只要将项目的.ncb、.opt、.aps、.clw文件以及Debug、Release目录下的所有文件都删掉,然后重新编译就行了。 

2) 如果你想与别人共享你的源代码项目,但是把整个项目做拷贝又太大。你完全可以删掉以下文件:.dsw、.ncb、.opt、.aps、.clw、. plg文件以及Debug、Release目录下的所有文件。

3) 当你的Workspace中包含多个Project的时候,你可能不能直观地、一眼看出来哪个是当前项目。可以如下设置:Tools->Options->Format,然后在Category中选择Workspace window,改变其默认的字体(比如设成Fixedsys)就行了。

4) 如何给已有的Project改名字?将该Project关掉。然后以文本格式打开.dsp文件,替换原来的Project名字即可。

5) VC6对类成员的智能提示功能很有用,但有时候会失灵。你可以先关掉项目,将.clw和.ncb删掉,然后重新打开项目,点击菜单项View->ClassWizard,在弹出的对话框中按一下“Add All”按钮;重新Rebuild All。应该可以解决问题。

 

附:

VC文件扩展名解读大全

.APS:存放二进制资源的中间文件,VC把当前资源文件转换成二进制格式,并存放在APS文件中,以加快资源装载速度。资源辅助文件。

.BMP:位图资源文件。

.BSC:浏览信息文件,由浏览信息维护工具(BSCMAKE)从原始浏览信息文件(.SBR)中生成,BSC文件可以用来在源代码编辑窗口中进行快速定位。用于浏览项目信息的,如果用source brower的话就必须有这个文件。可以在project options里去掉Generate Browse Info File,这样可以加快编译进度。

.C:用C语言编写的源代码文件。

.CLW:ClassWizard生成的用来存放类信息的文件。classwizard信息文件,ini文件的格式。

.CNT:用来定义帮助文件中“Contents”的结构。

.CPP或.CXX:用C++语言编写的源代码文件。

.CUR:光标资源文件。

.DEF:模块定义文件,供生成动态链接库时使用。

.DLG:定义对话框资源的独立文件。这种文件对于VC工程来说并非必需,因为VC一般把对话框资源放在.RC资源定义文件中。

.DSP:VC开发环境生成的工程文件,VC4及以前版本使用MAK文件来定义工程。项目文件,文本格式。

.DSW:VC开发环境生成的WorkSpace文件,用来把多个工程组织到一个WorkSpace中。工作区文件,与.dsp差不多。

.EXP:由LIB工具从DEF文件生成的输出文件,其中包含了函数和数据项目的输出信息,LINK工具将使用EXP文件来创建动态链接库。只有在编译DLL时才会生成,记录了DLL文件中的一些信息。

.H、.HPP或.HXX:用C/C++语言编写的头文件,通常用来定义数据类型,声明变量、函数、结构和类。

.HLP:Windows帮助文件。

.HM:在Help工程中,该文件定义了帮助文件与对话框、菜单或其它资源之间ID值的对应关系。

.HPJ:由Help Workshop生成的Help工程文件,用来控制Help文件的生成过程。

.HPG,生成帮助的文件的工程。

.ICO:图标资源文件。

.ILK:连接过程中生成的一种中间文件,只供LINK工具使用。

.INI:配置文件。

.LIB:库文件,LINK工具将使用它来连接各种输入库,以便最终生成EXE文件。

.LIC:用户许可证书文件,使用某些ActiveX控件时需要该文件。

.MAK:即MAKE文件,VC4及以前版本使用的工程文件,用来指定如何建立一个工程,VC6把MAK文件转换成DSP文件来处理。

.MAP:由LINK工具生成的一种文本文件,其中包含有被连接的程序的某些信息,例如程序中的组信息和公共符号信息等。执行文件的映像信息记录文件。

.MDP:旧版本的项目文件,相当于.dsp

.NCB:NCB是“No Compile Browser”的缩写,其中存放了供ClassView、WizardBar和Component Gallery使用的信息,由VC开发环境自动生成。无编译浏览文件。当自动完成功能出问题时可以删除此文件。编译工程后会自动生成。

.OBJ:由编译器或汇编工具生成的目标文件,是模块的二进制中间文件。

.ODL:用对象描述语言编写的源代码文件,VC用它来生成TLB文件。

.OLB:带有类型库资源的一种特殊的动态链接库,也叫对象库文件。

.OPT:VC开发环境自动生成的用来存放WorkSpace中各种选项的文件。工程关于开发环境的参数文件。如工具条位置信息等。

.PBI、.PBO和.PBT:由VC的性能分析工具PROFILE生成并使用的三种文件。

.PCH:预编译头文件,比较大,由编译器在建立工程时自动生成,其中存放有工程中已经编译的部分代码,在以后建立工程时不再重新编译这些代码,以便加快整个编译过程的速度。

.PDB:程序数据库文件,在建立工程时自动生成,其中存放程序的各种信息,用来加快调试过程的速度。记录了程序有关的一些数据和调试信息。

.PLG:编译信息文件,编译时的error和warning信息文件。

.RC:资源定义文件。

.RC2:资源定义文件,供一些特殊情况下使用。

.REG:注册表信息文件。

.RES:二进制资源文件,资源编译器编译资源定义文件后即生成RES文件。

.RTF:Rich Text Format(丰富文本格式)文档,可由Word或写字板来创建,常被用来生成Help文件。

.SBR:VC编译器为每个OBJ文件生成的原始浏览信息文件,浏览信息维护工具(BSCMAKE)将利用SBR文件来生成BSC文件。

.TLB:OLE库文件,其中存放了OLE自动化对象的数据类型、模块和接口定义,自动化服务器通过TLB文件就能了解自动化对象的使用方法。

.WAV:声音资源文件。

posted @ 2007-06-08 10:00 榕树藤 阅读(7542) | 评论 (0) | 编辑 收藏

Select模型

讲一下套接字模式和套接字I/O模型的区别。先说明一下,只针对Winsock,如果你要骨头里挑鸡蛋把UNIX下的套接字概念来往这里套,那就不关我的事。
套接字模式:阻塞套接字和非阻塞套接字。或者叫同步套接字和异步套接字。
套接字模型:描述如何对套接字的I/O行为进行管理。
Winsock提供的I/O模型一共有五种:
select,WSAAsyncSelect,WSAEventSelect,Overlapped,Completion。今天先讲解select。

1:select模型(选择模型)
先看一下下面的这句代码:
int iResult = recv(s, buffer,1024);
这是用来接收数据的,在默认的阻塞模式下的套接字里,recv会阻塞在那里,直到套接字连接上有数据可读,把数据读到buffer里后recv函数才会返回,不然就会一直阻塞在那里。在单线程的程序里出现这种情况会导致主线程(单线程程序里只有一个默认的主线程)被阻塞,这样整个程序被锁死在这里,如果永远没数据发送过来,那么程序就会被永远锁死。这个问题可以用多线程解决,但是在有多个套接字连接的情况下,这不是一个好的选择,扩展性很差。Select模型就是为了解决这个问题而出现的。
再看代码:

int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s, buffer,1024);

这一次recv的调用不管套接字连接上有没有数据可以接收都会马上返回。原因就在于我们用ioctlsocket把套接字设置为非阻塞模式了。不过你跟踪一下就会发现,在没有数据的情况下,recv确实是马上返回了,但是也返回了一个错误:WSAEWOULDBLOCK,意思就是请求的操作没有成功完成。看到这里很多人可能会说,那么就重复调用recv并检查返回值,直到成功为止,但是这样做效率很成问题,开销太大。
感谢天才的微软工程师吧,他们给我们提供了好的解决办法。
先看看select函数
int select(
int nfds,
fd_set FAR *readfds,
fd_set FAR *writefds,
fd_set FAR *exceptfds,
const struct timeval FAR *timeout
);
第一个参数不要管,会被系统忽略的。第二个参数是用来检查套接字可读性,也就说检查套接字上是否有数据可读,同样,第三个参数用来检查数据是否可以发出。最后一个是检查是否有带外数据可读取。
参数详细的意思请去看MSDN,这里限于篇幅不详细解释了。
最后一个参数是用来设置select等待多久的,是个结构:


struct timeval {
long tv_sec; // seconds
long tv_usec; // and microseconds
};
如果将这个结构设置为(0,0),那么select函数会马上返回。
说了这么久,select的作用到底是什么?
他的作用就是:防止在在阻塞模式的套接字里被锁死,避免在非阻塞套接字里重复检查WSAEWOULDBLOCK错误。
他的工作流程如下:
1:用FD_ZERO宏来初始化我们感兴趣的fd_set,也就是select函数的第二三四个参数。
2:用FD_SET宏来将套接字句柄分配给相应的fd_set。
3:调用select函数。
4:用FD_ISSET对套接字句柄进行检查,如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里,那么说明当前数据已经来了,马上可以读取成功而不会被阻塞。

下面给出一个简单的select模型的服务端套接字。

#include “iostream.h”
#include “winsock2.h”
#include “windows.h”


#define InternetAddr "127.0.0.1"
#define iPort 5055

#pragma comment(lib, "ws2_32.lib")


void main()
{
    WSADATA wsa;
    WSAStartup(MAKEWORD(2,2), &wsa);
    
    SOCKET fdServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(InternetAddr);
    server.sin_port = htons(iPort);
    
    int ret = bind(fdServer, (sockaddr*)&server, sizeof(server));
    ret = listen(fdServer, 4);


    SOCKET AcceptSocket;
    fd_set fdread;
    timeval tv;
    int nSize;

    while(1)
    {
        
        FD_ZERO(&fdread);//初始化fd_set
        FD_SET(fdServer, &fdread);//分配套接字句柄到相应的fd_set
        
        
        tv.tv_sec = 2;//这里我们打算让select等待两秒后返回,避免被锁死,也避免马上返回
        tv.tv_usec = 0;
        
        select(0, &fdread, NULL, NULL, &tv);
        
        nSize = sizeof(server);
        if (FD_ISSET(fdServer, &fdread))//如果套接字句柄还在fd_set里,说明客户端已经有connect的请求发过来了,马上可以accept成功
        {
            AcceptSocket = accept(fdServer,( sockaddr*) &server, &nSize);
            break;
        }
        
        else//还没有客户端的connect请求,我们可以去做别的事,避免像没有用select方式的阻塞套接字程序被锁死的情况,如果没用select,当程序运行到accept的时候客户端恰好没有connect请求,那么程序就会被锁死,做不了任何事情
        {
            //do something
            ::MessageBox(NULL, "waiting...", "recv", MB_ICONINFORMATION);//别的事做完后,继续去检查是否有客户端连接请求
        }
    }

    char buffer[128];
    ZeroMemory(buffer, 128);

    ret = recv(AcceptSocket,buffer,128,0);//这里同样可以用select,用法和上面一样

    ::MessageBox(NULL, buffer, "recv", MB_ICONINFORMATION);

    closesocket(AcceptSocket);
    WSACleanup();
    
    return;

}

基本上就这样,个人感觉select模型用处不是很大,我只用过一次,去年写端口扫描器的时候用select来检查超时。
感觉讲得不是很清楚,虽然东西我很明白,但是要讲解出来讲解得很清楚真不容易。
不知道大家能不能看懂,如果看得过程中有什么问题就问吧。

posted @ 2007-04-25 09:09 榕树藤 阅读(1749) | 评论 (2) | 编辑 收藏

WinSock学习笔记

与socket有关的一些函数介绍

1、读取当前错误值:每次发生错误时,如果要对具体问题进行处理,那么就应该调用这个函数取得错误代码。
      int  WSAGetLastError(void );
#define h_errno   WSAGetLastError()
错误值请自己阅读Winsock2.h。

2、将主机的unsigned long值转换为网络字节顺序(32位):为什么要这样做呢?因为不同的计算机使用不同的字节顺序存储数据。因此任何从Winsock函数对IP地址和端口号的引用和传给Winsock函数的IP地址和端口号均时按照网络顺序组织的。
      u_long  htonl(u_long hostlong);
举例:htonl(0)=0
htonl(80)= 1342177280
3、将unsigned long数从网络字节顺序转换位主机字节顺序,是上面函数的逆函数。
      u_long  ntohl(u_long netlong);
举例:ntohl(0)=0
ntohl(1342177280)= 80
4、将主机的unsigned short值转换为网络字节顺序(16位):原因同2:
      u_short  htons(u_short hostshort);
举例:htonl(0)=0
htonl(80)= 20480
5、将unsigned short数从网络字节顺序转换位主机字节顺序,是上面函数的逆函数。
      u_short  ntohs(u_short netshort);
举例:ntohs(0)=0
ntohsl(20480)= 80
6、将用点分割的IP地址转换位一个in_addr结构的地址,这个结构的定义见笔记(一),实际上就是一个unsigned long值。计算机内部处理IP地址可是不认识如192.1.8.84之类的数据。
      unsigned long  inet_addr( const char FAR * cp );
举例:inet_addr("192.1.8.84")=1409810880
inet_addr("127.0.0.1")= 16777343
如果发生错误,函数返回INADDR_NONE值。

7、将网络地址转换位用点分割的IP地址,是上面函数的逆函数。
      char FAR *  inet_ntoa( struct in_addr in );
举例:char * ipaddr=NULL;
char addr[20];
in_addr inaddr;
inaddr. s_addr=16777343;
ipaddr= inet_ntoa(inaddr);
strcpy(addr,ipaddr); 
这样addr的值就变为127.0.0.1。
注意意不要修改返回值或者进行释放动作。如果函数失败就会返回NULL值。

8、获取套接字的本地地址结构:
      int  getsockname(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
s为套接字
name为函数调用后获得的地址值
namelen为缓冲区的大小。
9、获取与套接字相连的端地址结构:
      int  getpeername(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
s为套接字
name为函数调用后获得的端地址值
namelen为缓冲区的大小。
10、获取计算机名:
      int  gethostname( char FAR * name, int namelen );
name是存放计算机名的缓冲区
namelen是缓冲区的大小
用法:
char szName[255];
memset(szName,0,255);
if(gethostname(szName,255)==SOCKET_ERROR)
{
//错误处理
}
返回值为:szNmae="xiaojin"
11、根据计算机名获取主机地址:
      struct hostent FAR *  gethostbyname( const char FAR * name );
name为计算机名。
用法:
hostent * host;
char* ip;
host= gethostbyname("xiaojin");
if(host->h_addr_list[0])
{
struct in_addr addr;
memmove(&addr, host->h_addr_list[0],4);
//获得标准IP地址
ip=inet_ ntoa (addr);
}
返回值为:hostent->h_name="xiaojin"
hostent->h_addrtype=2    //AF_INET
hostent->length=4
ip="127.0.0.1"
Winsock 的I/O操作:

1、 两种I/O模式
  • 阻塞模式:执行I/O操作完成前会一直进行等待,不会将控制权交给程序。套接字 默认为阻塞模式。可以通过多线程技术进行处理。
  • 非阻塞模式:执行I/O操作时,Winsock函数会返回并交出控制权。这种模式使用 起来比较复杂,因为函数在没有运行完成就进行返回,会不断地返回 WSAEWOULDBLOCK错误。但功能强大。
为了解决这个问题,提出了进行I/O操作的一些I/O模型,下面介绍最常见的三种:

2、select模型:

  通过调用select函数可以确定一个或多个套接字的状态,判断套接字上是否有数据,或
者能否向一个套接字写入数据。
      int  select( int nfds, fd_set FAR * readfds, fd_set FAR * writefds, 
fd_set FAR *exceptfds, const struct timeval FAR * timeout );
◆先来看看涉及到的结构的定义:
a、 d_set结构:
#define FD_SETSIZE 64?
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;      
fd_count为已设定socket的数量
fd_array为socket列表,FD_SETSIZE为最大socket数量,建议不小于64。这是微软建
议的。

B、timeval结构:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
tv_sec为时间的秒值。
tv_usec为时间的毫秒值。
这个结构主要是设置select()函数的等待值,如果将该结构设置为(0,0),则select()函数
会立即返回。

◆再来看看select函数各参数的作用:
  1. nfds:没有任何用处,主要用来进行系统兼容用,一般设置为0。
  2. readfds:等待可读性检查的套接字组。
  3. writefds;等待可写性检查的套接字组。
  4. exceptfds:等待错误检查的套接字组。
  5. timeout:超时时间。
  6. 函数失败的返回值:调用失败返回SOCKET_ERROR,超时返回0。
readfds、writefds、exceptfds三个变量至少有一个不为空,同时这个不为空的套接字组
种至少有一个socket,道理很简单,否则要select干什么呢。 举例:测试一个套接字是否可读:
fd_set fdread;
//FD_ZERO定义
// #define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0)
FD_ZERO(&fdread);
FD_SET(s,&fdread); //加入套接字,详细定义请看winsock2.h
if(select(0,%fdread,NULL,NULL,NULL)>0
{
//成功
if(FD_ISSET(s,&fread) //是否存在fread中,详细定义请看winsock2.h
{
//是可读的
}
}

◆I/O操作函数:主要用于获取与套接字相关的操作参数。

 int  ioctlsocket(SOCKET s, long cmd, u_long FAR * argp );     
s为I/O操作的套接字。
cmd为对套接字的操作命令。
argp为命令所带参数的指针。

常见的命令:
//确定套接字自动读入的数据量
#define FIONREAD _IOR(''''f'''', 127, u_long) /* get # bytes to read */
//允许或禁止套接字的非阻塞模式,允许为非0,禁止为0
#define FIONBIO _IOW(''''f'''', 126, u_long) /* set/clear non-blocking i/o */
//确定是否所有带外数据都已被读入
#define SIOCATMARK _IOR(''''s'''', 7, u_long) /* at oob mark? */
3、WSAAsynSelect模型:
WSAAsynSelect模型也是一个常用的异步I/O模型。应用程序可以在一个套接字上接收以
WINDOWS消息为基础的网络事件通知。该模型的实现方法是通过调用WSAAsynSelect函
数 自动将套接字设置为非阻塞模式,并向WINDOWS注册一个或多个网络时间,并提供一
个通知时使用的窗口句柄。当注册的事件发生时,对应的窗口将收到一个基于消息的通知。
      int  WSAAsyncSelect( SOCKET s, HWND hWnd, u_int wMsg, long lEvent);       
s为需要事件通知的套接字
hWnd为接收消息的窗口句柄
wMsg为要接收的消息
lEvent为掩码,指定应用程序感兴趣的网络事件组合,主要如下:
#define FD_READ_BIT 0
#define FD_READ (1 << FD_READ_BIT)
#define FD_WRITE_BIT 1
#define FD_WRITE (1 << FD_WRITE_BIT)
#define FD_OOB_BIT 2
#define FD_OOB (1 << FD_OOB_BIT)
#define FD_ACCEPT_BIT 3
#define FD_ACCEPT (1 << FD_ACCEPT_BIT)
#define FD_CONNECT_BIT 4
#define FD_CONNECT (1 << FD_CONNECT_BIT)
#define FD_CLOSE_BIT 5
#define FD_CLOSE (1 << FD_CLOSE_BIT)
用法:要接收读写通知:
int nResult= WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE);
if(nResult==SOCKET_ERROR)
{
//错误处理
}
取消通知:
      int nResult= WSAAsyncSelect(s,hWnd,0,0);
当应用程序窗口hWnd收到消息时,wMsg.wParam参数标识了套接字,lParam的低字标明
了网络事件,高字则包含错误代码。

4、WSAEventSelect模型
WSAEventSelect模型类似WSAAsynSelect模型,但最主要的区别是网络事件发生时会被发
送到一个事件对象句柄,而不是发送到一个窗口。

使用步骤如下:
a、 创建事件对象来接收网络事件:
#define WSAEVENT HANDLE
#define LPWSAEVENT LPHANDLE
WSAEVENT WSACreateEvent( void );
该函数的返回值为一个事件对象句柄,它具有两种工作状态:已传信(signaled)和未传信
(nonsignaled)以及两种工作模式:人工重设(manual reset)和自动重设(auto reset)。默认未
未传信的工作状态和人工重设模式。

b、将事件对象与套接字关联,同时注册事件,使事件对象的工作状态从未传信转变未
已传信。
      int  WSAEventSelect( SOCKET s,WSAEVENT hEventObject,long lNetworkEvents );  
s为套接字
hEventObject为刚才创建的事件对象句柄
lNetworkEvents为掩码,定义如上面所述

c、I/O处理后,设置事件对象为未传信
BOOL WSAResetEvent( WSAEVENT hEvent );

Hevent为事件对象

成功返回TRUE,失败返回FALSE。

d、等待网络事件来触发事件句柄的工作状态:

DWORD WSAWaitForMultipleEvents( DWORD cEvents,
const WSAEVENT FAR * lphEvents, BOOL fWaitAll,
DWORD dwTimeout, BOOL fAlertable );

lpEvent为事件句柄数组的指针
cEvent为为事件句柄的数目,其最大值为WSA_MAXIMUM_WAIT_EVENTS 
fWaitAll指定等待类型:TRUE:当lphEvent数组重所有事件对象同时有信号时返回;
FALSE:任一事件有信号就返回。
dwTimeout为等待超时(毫秒)
fAlertable为指定函数返回时是否执行完成例程

对事件数组中的事件进行引用时,应该用WSAWaitForMultipleEvents的返回值,减去
预声明值WSA_WAIT_EVENT_0,得到具体的引用值。例如:

nIndex=WSAWaitForMultipleEvents(…);
MyEvent=EventArray[Index- WSA_WAIT_EVENT_0];

e、判断网络事件类型:

int WSAEnumNetworkEvents( SOCKET s,
WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );

s为套接字
hEventObject为需要重设的事件对象
lpNetworkEvents为记录网络事件和错误代码,其结构定义如下:

typedef struct _WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;

f、关闭事件对象句柄:

BOOL WSACloseEvent(WSAEVENT hEvent);

调用成功返回TRUE,否则返回FALSE。

posted @ 2007-04-25 09:08 榕树藤 阅读(586) | 评论 (1) | 编辑 收藏

中国黑客组织与人物名单完全档案(转载)

著名黑客组织

先介绍零三年之前的组织。

安全焦点 http://www.xfocus.net/

1999年8月26日由xundi创立,创始人还有quack和casper。后来stardust,isno,glacier,alert7,benjurry,blackhole,eyas,flashsky,funnywei,refdom,tombkeeper,watercloud,wollf等人也加入了近来。站点主页风格一向是很简单。而该组织目前已经成为国内最权威的信息安全站点,也是最接近世界的一个国内组织。目前国内一些技术性比较强的文章都由作者亲自提交到该网站,而国内一些知名的技术属一属二的高手都会去这里的论坛。讨论技术的氛围还可以。而且一些网络安全公司也关注这里的论坛。现在流行的著名扫描工具x-scan的作者就是该组织的成员。从2002年开始,每年都举办一次信息安全峰会,吸引了国内外众多知名网络安全专家关注参加。会议涉及众多领域,备受关注。创造了良好的学术交流氛围。在此祝愿这个组织越走越好。

中国红客联盟(已解散)http://www.cnhonker.com/

借这个机会要说明一下,只有这个红客联盟才配的上是真正的红客联盟,并不是因为他申请了什么专利,而是在大家的眼中,只有他才只真正的红客联盟。这个组织是由lion在2000年12月组建的。曾再2001年带领众多会员参与中美黑客大战,而名震“江湖”。不过这个时代早已逝去,激情的往事也跟着逝去,留给人们的只有回忆。在2005年的最后一天,lion在主页上宣布正式解散。或许很多人难以理解吧。不过这也自有人家的道理。现在外面有N个红盟,我不屑于去关注他们。在此祝福lion,bkbll等人。对sharpwinner我就懒的说什么了。

中国鹰派 http://www.chinawill.com/

与红客联盟一样,都是2000年末创立,并且在2001年参与了中美黑客大战。站长万涛也是早期的绿色兵团成员。并且也参与了在2000年前的几次网络战争。至尽这个组织依然没有倒下。近几年中并没有什么大的事件发生。所以很多人对他都已经没有了什么印象。希望他们越走越好。

邪恶八进制 http://www.eviloctal.com/

2002年由冰血封情(从他的BLOG看出来,他是咱桂林人)创立,当时是以小组模式运营的,而发展到现在已经成为一个30多人的信息安全团队。主页做的很简单,但论坛内容非常丰富,涉及领域众多,在下经常去那里下载学术资料,论坛管理的是我所见过的论坛中最好的。而且讨论氛围也很不错。鄙人与那里的几个核心成员有所接触,都是比较热心的,而且技术都是比较强的。祝愿这个组织越办越好。我对这个团队的发展充满期望。

幻影旅团 http://www.ph4nt0m.org/

2001年创立,发展到现在组织成员已经达到20人,近期组织推出了WIKI平台(http://www.secwiki.com),内容涉及无线网络,病毒与反病毒,以及黑客技术等众多领域。所有的朋友都可以到那里去涂鸦。2002年开放了论坛,目前论坛的技术讨论氛围还是可以的,而且热心人也是很多的。相信这个组织也能走下去。

白细胞(whitecell) http://www.whitecell.org/

2001年创立的一个纯技术交流站点。当时核心成员有sinister,无花果等人,都是国内著名的高手。在2002年后就关闭了,而最近它由回来了。主页与论坛依然都很简单。希望这次回归会带给大家新的气象。

中华安全网 http://www.safechina.net/

2001年4月创立,经过了几次改版后,队伍也发展的比较大,我所熟悉的有yellow,Phoenix等人。到现在,这个网站还在改版中,不过论坛依然开放,在这里还是有讨论空间的。组织内的人技术也都是不错的。希望这次改版后会有大的变化:)。

第八军团

2000年左右由陈三公子组织成立,后经过多次改版。成为了一个VIP制的站点,资源收集量还是不错的。鄙人经常去那里找代理服务器,更新的速度还是可以的。论坛里讨论气氛不是很热烈。希望今后发展的会更好。

来说说这两年成立的黑客组织

BCT http://www.cnbct.org/

2004年底成立的一个专门挖掘脚本漏洞的组织。已经发展了一年了。在下与H4K_B4N,fpx到是有些接触。感觉这个组织是不错的,虽然没有做什么影响力大的事情,但是这种默默研究技术的精神还是值得发扬的,与那些招摇的比,要好多了。网站上收集了一些漏洞资料,这点到是做的比较好。希望继续努力,发展的越来越好。

火狐技术联盟 http://www.wrsky.com/

2004年建立的一个组织,致力于破解软件的组织。对于他们组织现在很有争议,也曾经一度遭受到猛烈的拒绝服务攻击,造成网站瘫痪长大数月,到现在是一个论坛系统。对于这个组织,到目前为止还是有争议。希望能早日平息。

黑客技术 http://www.hackart.org/

2003年成立的组织,之前使用的是乔客的整站程序,后来就关闭了。也是最近重开的站点,使用的是论坛系统,在下与那里的版主风般的男人和Jambalaya 是好朋友。他们的技术还是不错的。现在论坛人气虽然不怎么高,但显然是老站重开,知道的人还不多,希望日后可以恢复元气,继续发展下去。

国内三大商业黑客站点

黑客基地

http://www.hackbase.com/ 2003年成立。站长龙哥。

黑鹰基地 http://www.3800cc.com/ 由米特创立的商业黑客站点。

华夏黑客同盟 http://www.77169.com/ 2004年由怪狗创立的站点。

其实黑客组织多的数不胜数,我也只是举几个著名的而已。

黑客人物

下面来说说人物吧,现在的黑客越来越多,我重点介绍那些专著于系统与漏洞的高手,当然他们对入侵也是有所造诣的。注意,这并不是排名,不是第一个就是最厉害,而最后一个是这里面技术最差的,如果这样想就错了。因为有时候想着费劲所以就进入一下牛人的BLOG以及一些论坛去看,这样就可以容易记起来,知道写谁。先从这里找http://www.0x557.org/~kkqq/ 然后在从安全焦点的文章和论坛里还有绿盟去找。

alert7 QQ:415451 email:alert7@xfocus.org http://blog.xfocus.net/index.php?blogId=12 安全焦点核心成员,曾经在补天的。精通linux操作系统,对于linux下的漏洞很有研究。

baozi(fatb) QQ:48448355 http://blog.xfocus.net/index.php?blogId=3 对windows与linux下的入侵很精通。

CoolQ QQ:49462335 http://coolq.blogdriver.com/coolq/index.html email:qufuping@ercist.iscas.ac.cn 我也是在安全焦点05年的峰会上认识的他,是中科院软件研究所的学生,对于linux非常有研究。在最后一期的Phrack杂志上有他的文章《Hacking Grub for fun and profit》也是在这期杂志中发表文章的三位中国人之一。另外著作还有《ways to hide files in ext2/3filesystem》

bkbll(dumplogin) email:bkbll@cnhonker.com http://blog.0x557.org/dumplogin/ 原中国红客联盟核心成员,与lion曾经一起参加过中美黑客大战。对windows与linux都很有研究。著作有《POSIX子系统权限提升漏洞的分析》

flashsky QQ:16202253 email:flashsky@xfocus.org http://www.qjclub.net/blog/user1/497/index.html 安全焦点核心成员。精通windows操作系统上的缓冲区溢出,当年就是他一连公布了微软的N个漏洞,微软就此还谴责过安全焦点。现就职于启明星辰。

Flier Lu email:flier@nsfocus.com http://flier_lu.blogcn.com/ 绿盟的高手,精通windows操作系统内核,著作有《MS.Net CLR扩展PE结构分》《自动验证 Windows NT 系统服务描述表》《CLR 中代码访问安全检测实现原理》等等。

funnywei QQ:25044885 email:funnywei@xfocus.org http://blog.xfocus.net/index.php?blogId=28 安全焦点核心成员,熟悉windows操作系统。著作有《WindowsXpSp2溢出保护》

glacier QQ:1937435 email:glacier@xfocus.org http://blog.xfocus.net/index.php?blogId=15 安全焦点核心成员,精通windows编程,网络编程,delphi等等。是冰河木马以及著名扫描软件x-scan的作者。

icbm email:icbm@0x557.org http://blog.0x557.org/icbm/ 精通linux操作系统内核以及漏洞。就职于启明星辰。翻译过文章《Building ptrace injecting shellcodes》是《浅析Linux内核漏洞》的作者。

killer QQ:6362602 email:killer@xfocus.org http://blog.xfocus.net/index.php?blogId=2 安全焦点灌水区版主。精通逆向工程,程序破解。

pjf QQ:85863144 http://pjf.blogcn.com/ 著名的检测工具icesword(冰刃)的作者。很多程序员以及编写rootkit的高手以绕过它的检测工具为目标。熟悉windows操作系统内核。

refdom email:refdom@xfocus.org http://blog.xfocus.net/index.php?blogId=11 安全焦点核心成员,《反垃圾邮件技术解析》的作者。似乎曾经是红客联盟的人。

stardust QQ:6269692 email:stardust@xfocus.org http://blog.xfocus.net/index.php?blogId=7 安全焦点核心成员。熟悉linux,精通IDS。著作有《从漏洞及攻击分析到NIDS规则设计》《Bro NIDS的规则》《Snort 2.x数据区搜索规则选项的改进》《Bro NIDS的安装与配置》

sunwear QQ:47347 email:shellcoder@163.com http://blog.csdn.net/sunwear/ 邪恶八进制核心成员。精通windows操作系统内核。著作有《利用NTLDR进入RING0的方法及MGF病毒技术分析笔记》《浅析本机API》《智能ABC输入法溢出分析》

swan email:swan@xfocus.org http://blog.0x557.org/swan/ 对缓冲区溢出漏洞很有研究。最近的ms05051 Microsoft Windows DTC 漏洞的exploit作者就是他。

tombkeeper QQ:644909 http://blog.xfocus.net/index.php?blogId=9 安全焦点核心成员。精通windows操作系统内核。著作有《用Bochs调试NTLDR》《修改Windows SMB相关服务的默认端口》等等。在焦点峰会上见到过他,很帅。

watercloud http://blog.xfocus.net/index.php?blogId=6 安全焦点核心成员。精通windows,linux操作系统。著作有《手工打造微型Win32可执行文件》《溢出利用程序和编程语言大杂烩》《RSA算法基础->实践》

zwell email:suei8423@163.com http://blog.donews.com/zwell NB联盟核心成员。精通windows操作系统,著作有《安全稳定的实现进线程监控》《一种新的穿透防火墙的数据传输技术》

zzzevazzz QQ:49322630 http://zzzevazzz.bokee.com/index.html 幻影旅团核心成员。原灰色轨迹的人。精通windows操作系统内核。著作有《Do All in Cmd Shell》《无驱动执行ring0代码》等等。

小榕 http://www.netxeyes.org 流光,乱刀,溺雪及命令行SQL注入工具的作者。中国第二代黑客。

lion QQ:21509 email:lion@cnhonker.com http://www.cnhonker.com 原中国红客联盟站长,对缓冲区溢出很有研究。精通linux.windows.

isno QQ:1070681 email:isno@xfocus.org 安全焦点核心成员,精通缓冲区溢出漏洞。webdav溢出程序的作者。写过IDQ,IDA漏洞溢出的分析等等。

sinister QQ:3350124 email:sinister@whitecell.org 白细胞成员。精通windows内核,AIX。著作有《NT 内核的进程调度分析笔记》《NT 下动态切换进程分析笔记》《AIX 内核的虚拟文件系统框架》《AIX 内核的文件操作流程》

袁哥 email:yuange@nsfocus.com 现就职于中联绿盟公司。精通windows操作系统内核以及漏洞利用。

warning3 email:warning3@nsfocus.com msn:warning3@hotmail.com 精通linux unix内核及漏洞。现就职于中联绿盟公司。著作有《Heap/BSS 溢出机理分析》

SoBeIt QQ:27324838 email:kinvis@hotmail.com 精通windows编程以及系统内核还有溢出。著作有《Windows内核调试器原理浅析》,《挂钩Windows API》等等翻译过《在NT系列操作系统里让自己“消失”》

xhacker QQ:66680800 精通渗透入侵以及脚本入侵。著作有《详述虚拟网站的权限突破及防范》,《如何利用黑客技术跟踪并分析一名目标人物》

eyas QQ:320236 email:eyas@xfocus.org 安全焦点核心成员,熟悉windows操作系统,windows编程。著作有《NT平台拨号连接密码恢复原理》,《WS_FTP FTPD STAT命令远程溢出分析》

孤独剑客 QQ:5385757 email:Janker@Hackbase.Com Http://Www.Janker.Org 精通编程。以及入侵技术。winshell的作者。中国第二代黑客。

sunx QQ:239670 http://www.sunx.org 对溢出有研究,写过IDA漏洞和printer漏洞的溢出程序。精通汇编。著作很多。

analysist QQ:20116789 精通数据库与脚本入侵。早年对跨站脚本以及很多脚本漏洞很有研究。著作有《跨站脚本执行漏洞详解》,《BBS2000和BBS3000所存在的安全隐患》

Frankie http://cnns.net 精通windows操作系统,与linux。中国第一代黑客。

rootshell(fzk) QQ:1734398 http://www.ns-one.com 精通windows操作系统,熟悉缓冲区溢出漏洞。老一代的黑客。著作有《最近发现的一个Distributed File System服务远程溢出问题》。

PP QQ:82928 精通windows操作系统。名言:如果想飞得高,就该把地平线忘掉。

tianxing OICQ:911189 网站:http://www.tianxing.org/ 精通windows操作系统与漏洞利用。RPC漏洞利用程序以及网络刺客,网络卫兵的作者。

grip2 QQ:1007270 精通linux操作系统。著作有《一个Linux病毒原型分析》

san QQ:56941 精通windows操作系统以及linux。而且对windows CE很有研究。phrack最后一期的杂志中,刊登过他的文章。

hume QQ:8709369 精通汇编以及windows操作系统。著作有《SEH in ASM 的研究》

backend email:backend@antionline.org 精通linux操作系统。翻译过很多文章,是绿盟的高手。不过已经几年没见在网络上游走了。到是现实中偶尔看到他。

Adam QQ:15898 email:adam@nsfocus.com 绿盟的高手,windows安全版版主。精通windows操作系统。

ipxodi QQ:16175535 email:ipxodi@nsfocus.com 很长时间没看见他了。精通windows操作系统以及缓冲区溢出。著作有《window系统下的堆栈溢出》《Windows 2000缓冲区溢出入门》。这些文章都是造福了很多后辈的。

zer9 QQ:573369 email:zer9@21cn.com 也是很长时间没见到了。早期写过不少文章和工具。也是老一辈的革命家。

whg QQ:312016 email:whg0001@163.com http://WWW.CNASM.COM 不知道算不算白细胞的成员,呵呵。病毒高手。精通汇编。写过不少软件,例如lan下sniff QQ的工具以及sniff工具等等,可以到他的主页上去看。

lg_wu email:lgwu2000@sina.com 在绿盟论坛和安全焦点都见过,对linux精通的很。技术不一般的说。

wowocock QQ:37803144 精通windows操作系统内核,汇编。在驱动开发网以及cvc 轻描淡写见过,技术不一般。著作很多,但是见到的很少。不知道为什么这么好的文章没人转!

baiyuanfan QQ:51449276 这小子在峰会上给我的印象很深。在技术上很下工夫。对windows也算有研究了。著作当然是byshell了。

vxk QQ:355852911 汇编技术很硬,精通windows内核。经常在cvc论坛看到他。

冰血封情 QQ:124839278 邪恶八进制的创始人。中国第四代黑客。感觉这个人很不错。对他的组织比较认可。技术上面还可以。

Polymorphours(shadow3) QQ:120974745白细胞成员。以前叫shadow3.好象换名字了。熟悉windows操作系统,以及缓冲区溢出。著作有《MS05-010许可证记录服务漏洞允许执行代码的分析》《Media Player 8.0 vulnerability》等等。

e4gle QQ:1949479白细胞成员。老一代的黑客。精通linux系统内核以及病毒技术,缓冲区溢出。著作有《程序攻击原理》《Unix系统病毒概述》《高级缓冲溢出的使用》

bingle QQ:45671342很早就认识的一个兄弟了。著作不少,很多都很实用。

wollf QQ:228095 glacier的老婆。一定不能让他知道我是谁,要不我就死定了。她是黑客;)美女黑客!

goodwell:中国早期著名黑客组织-绿色兵团 创始人之一。

yellow QQ:12398890:中华安全网核心成员。熟悉缓冲区溢出与windows编程。

江海客 QQ:741534:曾经参加某个会议的时候见过他,聊的还可以,后来在焦点峰会时又碰了面。此人是反病毒方面的高手。

icmb:启明星辰linux方面的高手。对于漏洞方面很有研究。不带眼睛更漂亮…… 

posted @ 2007-04-23 22:16 榕树藤 阅读(838) | 评论 (0) | 编辑 收藏

VC6.0编译器参数设置

VC6.0编译器参数的设置主要通过VC的菜单项Project->Settings->C/C++页来完成。我们可以看到这一页的最下面Project Options中的内容,一般如下:

/nologo /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_AFXDLL" /D "_MBCS" /Fp"Debug/WritingDlgTest.pch" /Yu"stdafx.h" /Fo"Debug/" /Fd"Debug/" /FD /GZ /c

各个参数代表的意义,可以参考Msdn。比如/nologo表示编译时不在输出窗口显示这些设置(我们可以把这个参数去掉来看看效果)等等。一般我们不会直接修改这些设置,而是通过这一页最上面的Category中的各项来完成......
-转载-


主要通过VC的菜单项Project->Settings->C/C++页来完成。我们可以看到这一页的最下面Project Options中的内容,一般如下:

/nologo /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_AFXDLL" /D "_MBCS" /Fp"Debug/WritingDlgTest.pch" /Yu"stdafx.h" /Fo"Debug/" /Fd"Debug/" /FD /GZ /c

各个参数代表的意义,可以参考Msdn。比如/nologo表示编译时不在输出窗口显示这些设置(我们可以把这个参数去掉来看看效果)等等。一般我们不会直接修改这些设置,而是通过这一页最上面的Category中的各项来完成。

1) General:一些总体设置。

Warning level 用来控制警告信息,其中Level 1是最严重的级别;
Warnings as errors 将警告信息当作错误处理;
Optimizations 代码优化,可以在Category的Optimizations项中进行更细的设置;
Generate browse info 用以生成.sbr文件,记录类、变量等符号信息,可以在Category的Listing Files项中进行更多的设置。
Debug info 生成调试信息:
None,不产生任何调试信息(编译比较快);
Line Numbers Only,仅生成全局的和外部符号的调试信息到.OBJ文件或.EXE文件,减小目标文件的尺寸;
C 7.0- Compatible,记录调试器用到的所有符号信息到.OBJ文件和.EXE文件;
Program Database,创建.PDB文件记录所有调试信息;
Program Database for "Edit & Continue",创建.PDB文件记录所有调试信息,并且支持调试时编辑。

2) C++ Language

Pointer-to-member representation 用来设置类定义/引用的先后关系:
Best-Case Always,表示在引用类之前该类肯定已经定义;
General-Purpose Always,?
Point to Any Class
Point to Single- and Multiple-Inheritance Classes
Point to Single-Inheritance Classes
Enable Exception Handling,进行同步的异常处理;
Enable Run-Time Type Information 迫使编译器增加代码在运行时进行对象类型检查;
Disable Construction Displacements 设置类构造/析构函数调用虚函数问题。

3) Code Generation

Processor 表示代码指令优化,可以为80386、80486、Pentium、Pentium Pro,或者Blend表示混合以上各种优化。
Use run-time library 用以指定程序运行时使用的运行时库,有一个原则就是,一个进程不要同时使用几个版本的运行时库。连接了单线程库就不支持多线程调用,连接了多线程库就要求创建多线程的应用程序。
Single-Threaded,单线程Release版本,静态连接LIBC.LIB库;
Debug Single-Threaded,单线程Debug版本,静态连接LIBCD.LIB库;
Multithreaded,多线程Release版本,静态连接LIBCMT.LIB库;
Debug Multithreaded,多线程Debug版本,静态连接LIBCMTD.LIB库;
Multithreaded DLL,动态连接MSVCRT.DLL库;
Debug Multithreaded DLL,动态连接MSVCRTD.DLL库。
Calling convention 可以用来设定调用约定,有三种:__cdecl、__fastcall和__stdcall。
  各种调用约定的主要区别在于:1. 函数调用时,函数的参数是从左到右压入堆栈还是从右到左压入堆栈;2. 在函数返回时,由函数的调用者来清理压入堆栈的参数还是由函数本身来清理;3. 以及在编译时对函数名进行的命名修饰(可以通过Listing Files看到各种命名修饰方式)。
Struct member alignment 用以指定数据结构中的成员变量在内存中是按几字节对齐的,根据计算机数据总线的位数,不同的对齐方式存取数据的速度不一样。这个参数对数据包网络传输等应用尤为重要,不是存取速度问题,而是数据位的精确定义问题,一般在程序中使用#pragma pack来指定。

4) Customize

Disable Language Extensions,表示不使用微软为标准C做的语言扩展;
Eliminate Duplicate Strings,主要用于字符串优化(将字符串放到缓充池里以节省空间),使用这个参数,使得
            char *sBuffer = "This is a character buffer";
            char *tBuffer = "This is a character buffer";
            sBuffer 和tBuffer指向的是同一块内存空间;
Enable Function-Level Linking ,告诉编译器将各个函数按打包格式编译;
Enables minimal rebuild,通过保存关联信息到.IDB文件,使编译器只对最新类定义改动过的源文件进行重编译,提高编译速度;
Enable Incremental Compilation,同样通过.IDB文件保存的信息,只重编译最新改动过的函数;
Suppress Startup Banner and Information Messages,用以控制参数是否在output窗口输出。


5) Listing Files

Generate browse info 上面已经提到过。这里可以进行更多的设置。
Exclude Local Variables from Browse Info 表示是否将局部变量的信息放到.SBR文件中。
Listing file type 可以设置生成的列表信息文件的内容:
Assembly-Only Listing 仅生成汇编代码文件(.ASM扩展名);
Assembly With Machine Code 生成机器代码和汇编代码文件(.COD扩展名);
Assembly With Source Code 生成源代码和汇编代码文件(.ASM扩展名);
Assembly, Machine Code, and Source 生成机器码、源代码和汇编代码文件(.COD扩展名)。
Listing file name 生成的信息文件的路径,一般为Debug或Release目录下,生成的文件名自动取源文件的文件名。

6) Optimizations 代码优化设置。

Maximize Speed 生成最快速的代码;
Minimize Size 生成最小尺寸的程序;
Customize 定制优化。定制的内容包括:
Assume No Aliasing,不使用别名(提高速度);
Assume Aliasing Across Function Calls,仅函数内部不使用别名;
Global Optimizations,全局优化,比如经常用到的变量使用寄存器保存,或者循环内的计算优化,如i = -100;while( i < 0 ){i += x + y;}会被优化为i = -100;t = x + y;while( i < 0 ){i += t;};
Generate Intrinsic Functions,使用内部函数替换一些函数调用(提高速度);
Improve Float Consistency,浮点运算方面的优化;
Favor Small Code,程序(exe或dll)尺寸优化优先于代码速度优化;
Favor Fast Code,程序(exe或dll)代码速度优化优先于尺寸优化;
Frame-Pointer Omission,不使用帧指针,以提高函数调用速度;
Full Optimization,组合了几种参数,以生成最快的程序代码。
Inline function expansion,内联函数扩展的三种优化(使用内联可以节省函数调用的开销,加快程序速度):
Disable,不使用内联;
Only __inline,仅函数定义前有inline或__inline标记使用内联;
Any Suitable,除了inline或__inline标记的函数外,编译器"觉得"应该使用内联的函数,都使用内联。

7) Precompiled Headers 预编译头文件的设置。使用预编译可以提高重复编译的速度。VC一般将一些公共的、不大变动的头文件(比如afxwin.h等)集中放到stdafx.h中,这一部分代码就不必每次都重新编译(除非是Rebuild All)。


8) Preprocessor 预编译处理。可以定义/解除定义一些常量。

Additional include directories,可以指定额外的包含目录,一般是相对于本项目的目录,如..\Include。


--------------------------------------------------------------------------------
连接参数的设置
主要通过VC的菜单项Project->Settings->Link页来完成。我们可以看到这一页的最下面Project Options中的内容,一般如下:

/nologo /subsystem:windows /incremental:yes /pdb:"Debug/WritingDlgTest.pdb" /debug /machine:I386 /out:"Debug/WritingDlgTest.exe" /pdbtype:sept

下面我们分别来看一下Category中的各项设置。

1) General 一些总体设置。可以设置生成的文件路径、文件名;连接的库文件;

Generate debug info,生成Debug信息到.PDB文件(具体格式可以在Category->Debug中设置);
Ignore All Default Libraries,放弃所有默认的库连接;
Link Incrementally,通过生成. ILK文件实现递增式连接以提高后续连接速度,但一般这种方式下生成的文件(EXE或DLL)较大;
Generate Mapfile,生成.MAP文件记录模块相关信息;
Enable Profiling,这个参数通常与Generate Mapfile参数同时使用,而且如果产生Debug信息的话,不能用.PDB文件,而且必须用Microsoft Format。

2) Customize 这里可以进行使用程序数据库文件的设置。

Force File Output,强制产生输出文件(EXE或DLL);
Print Progress Messages,可以将连接过程中的进度信息输出到Output窗口。

3) Debug 设置是否生成调试信息,以及调试信息的格式。

Dubug info,格式可以有Microsoft Format、COFF Format(Common Object File Format)和Both Formats三种选择;
Separate Types,表示将Debug格式信息以独立的.PDB文件存放,还是直接放在各个源文件的.PDB文件中。选中的话,表示采用后者的方式,这种方式调试启动比较快。


4) Input 这里可以指定要连接的库文件,放弃连接的库文件。还可以增加额外的库文件目录,一般是相对于本项目的目录,如..\Lib。

Force Symbol References,可以指定连接特定符号定义的库。

5) Output 

Base Address 可以改变程序默认的基地址(EXE文件默认为0x400000,DLL默认为0x10000000),操作系统装载一个程序时总是试着先从这个基地址开始。
Entry-Point Symbol 可以指定程序的入口地址,一般为一个函数名(且必须采用__stdcall调用约定)。一般Win32的程序,EXE的入口为 WinMain,DLL的入口为DllEntryPoint;最好让连接器自动设置程序的入口点。默认情况下,通过一个C的运行时库函数来实现:控制台程序采用mainCRTStartup (或wmainCRTStartup)去调用程序的main (或wmain)函数;Windows程序采用WinMainCRTStartup (或 wWinMainCRTStartup)调用程序的WinMain (或 wWinMain,必须采用__stdcall调用约定);DLL采用_DllMainCRTStartup调用DllMain函数(必须采用 __stdcall调用约定)。
Stack allocations 用以设置程序使用的堆栈大小(请使用十进制),默认为1兆字节。
Version Information 告诉连接器在EXE或DLL文件的开始部分放上版本号。

值得注意的是:

上面各个参数是大小写敏感的;
在参数后加上"-"表示该参数无效;
各个参数值选项有"*"的表示为该参数的默认值;
可以使用页右上角的"Reset"按钮来恢复该页的所有默认设置。

--------------------------------------------------------------------------------
其它一些参数设置。

1) Project->Settings->General,可以设置连接MFC库的方式(静态或动态)。如果是动态连接,在MFC软件发布时不要忘了带上MFC的DLL。

2) Project->Settings->Debug,可以设置调试时运行的可执行文件,以及命令行参数等。

3) Project->Settings->Custom Build,可以设置编译/连接成功后自动执行一些操作。比较有用的是,写COM时希望VC对编译通过的COM文件自动注册,可以如下设置:

Description: Register COM

Commands: regsvr32 /s /c $(TargetPath)

echo regsvr32 exe.time > $(TargetDir)\$(TargetName).trg

Outputs: $(TargetDir)\$(TargetName).trg


4) Tools->Options->Directories,设置系统的Include、Library路径。

--------------------------------------------------------------------------------
小窍门: 项目中的.ncb、.opt、.aps、.clw文件以及Debug、Release目录下的所有文件都可以删掉的。

posted @ 2007-04-23 19:51 榕树藤 阅读(1558) | 评论 (0) | 编辑 收藏

VC中基于 Windows 的精确定时

在工业生产控制系统中,有许多需要定时完成的操作,如定时显示当前时间,定时刷新屏幕上的进度条,上位 机定时向下位机发送命令和传送数据等。特别是在对控制性能要求较高的实时控制系统和数据采集系统中,就更需要精确定时操作。
  众所周知,Windows 是基于消息机制的系统,任何事件的执行都是通过发送和接收消息来完成的。 这样就带来了一些问题,如一旦计算机的CPU被某个进程占用,或系统资源紧张时,发送到消息队列 中的消息就暂时被挂起,得不到实时处理。因此,不能简单地通过Windows消息引发一个对定时要求 严格的事件。另外,由于在Windows中已经封装了计算机底层硬件的访问,所以,要想通过直接利用 访问硬件来完成精确定时,也比较困难。所以在实际应用时,应针对具体定时精度的要求,采取相适 应的定时方法。
  VC中提供了很多关于时间操作的函数,利用它们控制程序能够精确地完成定时和计时操作。本文详细介绍了 VC中基于Windows的精确定时的七种方式,如下图所示:


图一 图像描述

  方式一:VC中的WM_TIMER消息映射能进行简单的时间控制。首先调用函数SetTimer()设置定时 间隔,如SetTimer(0,200,NULL)即为设置200ms的时间间隔。然后在应用程序中增加定时响应函数 OnTimer(),并在该函数中添加响应的处理语句,用来完成到达定时时间的操作。这种定时方法非常 简单,可以实现一定的定时功能,但其定时功能如同Sleep()函数的延时功能一样,精度非常低,最小 计时精度仅为30ms,CPU占用低,且定时器消息在多任务操作系统中的优先级很低,不能得到及时响 应,往往不能满足实时控制环境下的应用。只可以用来实现诸如位图的动态显示等对定时精度要求不高的情况。如示例工程中的Timer1。
  方式二:VC中使用sleep()函数实现延时,它的单位是ms,如延时2秒,用sleep(2000)。精度非常 低,最小计时精度仅为30ms,用sleep函数的不利处在于延时期间不能处理其他的消息,如果时间太 长,就好象死机一样,CPU占用率非常高,只能用于要求不高的延时程序中。如示例工程中的Timer2。
  方式三:利用COleDateTime类和COleDateTimeSpan类结合WINDOWS的消息处理过程来实现秒级延时。如示例工程中的Timer3和Timer3_1。以下是实现2秒的延时代码:
COleDateTime      start_time = COleDateTime::GetCurrentTime();
COleDateTimeSpan  end_time= COleDateTime::GetCurrentTime()-start_time;
while(end_time.GetTotalSeconds()< 2) //实现延时2秒
{
MSG   msg;
GetMessage(&msg,NULL,0,0);
TranslateMessage(&msg);
DispatchMessage(&msg);
//以上四行是实现在延时或定时期间能处理其他的消息,
//虽然这样可以降低CPU的占有率,
//但降低了延时或定时精度,实际应用中可以去掉。
end_time = COleDateTime::GetCurrentTime()-start_time;
}//这样在延时的时候我们也能够处理其他的消息。        方式四:在精度要求较高的情况下,VC中可以利用GetTickCount()函数,该函数的返回值是  DWORD型,表示以ms为单位的计算机启动后经历的时间间隔。精度比WM_TIMER消息映射高,在较 短的定时中其计时误差为15ms,在较长的定时中其计时误差较低,如果定时时间太长,就好象死机一样,CPU占用率非常高,只能用于要求不高的延时程序中。如示例工程中的Timer4和Timer4_1。下列代码可以实现50ms的精确定时:
DWORD dwStart = GetTickCount();
DWORD dwEnd   = dwStart;
do
{
dwEnd = GetTickCount()-dwStart;
}while(dwEnd <50);
为使GetTickCount()函数在延时或定时期间能处理其他的消息,可以把代码改为:
DWORD dwStart = GetTickCount();
DWORD dwEnd   = dwStart;
do
{
MSG   msg;
GetMessage(&msg,NULL,0,0);
TranslateMessage(&msg);
DispatchMessage(&msg);
dwEnd = GetTickCount()-dwStart;
}while(dwEnd <50);
虽然这样可以降低CPU的占有率,并在延时或定时期间也能处理其他的消息,但降低了延时或定时精度。
  方式五:与GetTickCount()函数类似的多媒体定时器函数DWORD timeGetTime(void),该函数定时精 度为ms级,返回从Windows启动开始经过的毫秒数。微软公司在其多媒体Windows中提供了精确定时器的底 层API持,利用多媒体定时器可以很精确地读出系统的当前时间,并且能在非常精确的时间间隔内完成一 个事件、函数或过程的调用。不同之处在于调用DWORD timeGetTime(void) 函数之前必须将 Winmm.lib  和 Mmsystem.h 添加到工程中,否则在编译时提示DWORD timeGetTime(void)函数未定义。由于使用该 函数是通过查询的方式进行定时控制的,所以,应该建立定时循环来进行定时事件的控制。如示例工程中的Timer5和Timer5_1。
  方式六:使用多媒体定时器timeSetEvent()函数,该函数定时精度为ms级。利用该函数可以实现周期性的函数调用。如示例工程中的Timer6和Timer6_1。函数的原型如下:
MMRESULT timeSetEvent( UINT uDelay,
UINT uResolution,
LPTIMECALLBACK lpTimeProc,
WORD dwUser,
UINT fuEvent )
  该函数设置一个定时回调事件,此事件可以是一个一次性事件或周期性事件。事件一旦被激活,便调用指定的回调函数, 成功后返回事件的标识符代码,否则返回NULL。函数的参数说明如下:
uDelay:以毫秒指定事件的周期。
Uresolution:以毫秒指定延时的精度,数值越小定时器事件分辨率越高。缺省值为1ms。
LpTimeProc:指向一个回调函数。
DwUser:存放用户提供的回调数据。
FuEvent:指定定时器事件类型:
TIME_ONESHOT:uDelay毫秒后只产生一次事件
TIME_PERIODIC :每隔uDelay毫秒周期性地产生事件。      
  具体应用时,可以通过调用timeSetEvent()函数,将需要周期性执行的任务定义在LpTimeProc回调函数 中(如:定时采样、控制等),从而完成所需处理的事件。需要注意的是,任务处理的时间不能大于周期间隔时间。另外,在定时器使用完毕后, 应及时调用timeKillEvent()将之释放。
  方式七:对于精确度要求更高的定时操作,则应该使用QueryPerformanceFrequency()和 QueryPerformanceCounter()函数。这两个函数是VC提供的仅供Windows 95及其后续版本使用的精确时间函数,并要求计算机从硬件上支持精确定时器。如示例工程中的Timer7、Timer7_1、Timer7_2、Timer7_3。
QueryPerformanceFrequency()函数和QueryPerformanceCounter()函数的原型如下:
BOOL  QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
BOOL  QueryPerformanceCounter(LARGE_INTEGER *lpCount);
  数据类型ARGE_INTEGER既可以是一个8字节长的整型数,也可以是两个4字节长的整型数的联合结构, 其具体用法根据编译器是否支持64位而定。该类型的定义如下:
typedef union _LARGE_INTEGER
{
struct
{
DWORD LowPart ;// 4字节整型数
LONG  HighPart;// 4字节整型数
};
LONGLONG QuadPart ;// 8字节整型数
}LARGE_INTEGER ;
  在进行定时之前,先调用QueryPerformanceFrequency()函数获得机器内部定时器的时钟频率, 然后在需要严格定时的事件发生之前和发生之后分别调用QueryPerformanceCounter()函数,利用两次获得的计数之差及时钟频率,计算出事件经 历的精确时间。下列代码实现1ms的精确定时:
LARGE_INTEGER litmp;
LONGLONG QPart1,QPart2;
double dfMinus, dfFreq, dfTim;
QueryPerformanceFrequency(&litmp);
dfFreq = (double)litmp.QuadPart;// 获得计数器的时钟频率
QueryPerformanceCounter(&litmp);
QPart1 = litmp.QuadPart;// 获得初始值
do
{
QueryPerformanceCounter(&litmp);
QPart2 = litmp.QuadPart;//获得中止值
dfMinus = (double)(QPart2-QPart1);
dfTim = dfMinus / dfFreq;// 获得对应的时间值,单位为秒
}while(dfTim<0.001);
  其定时误差不超过1微秒,精度与CPU等机器配置有关。 下面的程序用来测试函数Sleep(100)的精确持续时间:
LARGE_INTEGER litmp;
LONGLONG QPart1,QPart2;
double dfMinus, dfFreq, dfTim;
QueryPerformanceFrequency(&litmp);
dfFreq = (double)litmp.QuadPart;// 获得计数器的时钟频率
QueryPerformanceCounter(&litmp);
QPart1 = litmp.QuadPart;// 获得初始值
Sleep(100);
QueryPerformanceCounter(&litmp);
QPart2 = litmp.QuadPart;//获得中止值
dfMinus = (double)(QPart2-QPart1);
dfTim = dfMinus / dfFreq;// 获得对应的时间值,单位为秒     
  由于Sleep()函数自身的误差,上述程序每次执行的结果都会有微小误差。下列代码实现1微秒的精确定时:
LARGE_INTEGER litmp;
LONGLONG QPart1,QPart2;
double dfMinus, dfFreq, dfTim;
QueryPerformanceFrequency(&litmp);
dfFreq = (double)litmp.QuadPart;// 获得计数器的时钟频率
QueryPerformanceCounter(&litmp);
QPart1 = litmp.QuadPart;// 获得初始值
do
{
QueryPerformanceCounter(&litmp);
QPart2 = litmp.QuadPart;//获得中止值
dfMinus = (double)(QPart2-QPart1);
dfTim = dfMinus / dfFreq;// 获得对应的时间值,单位为秒
}while(dfTim<0.000001);
其定时误差一般不超过0.5微秒,精度与CPU等机器配置有关。

示例工程下载

posted @ 2007-04-20 21:03 榕树藤 阅读(712) | 评论 (1) | 编辑 收藏

STL实践指南

介绍
这是一篇指导您如何在Microsoft Visual Studio下学习STL并进行实践的文章。这篇文章从STL的基础知识讲起,循序渐进,逐步深入,涉及到了STL编写代码的方法、STL代码的编译和调试、命名空间(namespace)、STL中的ANSI / ISO字符串、各种不同类型的容器(container)、模板(template)、游标(Iterator)、算法(Algorithms)、分配器(Allocator)、容器的嵌套等方面的问题,作者在这篇文章中对读者提出了一些建议,并指出了使用STL时应该注意的问题。这篇文章覆盖面广,视角全面。不仅仅适合初学者学习STL,更是广大读者使用STL编程的实践指南。


这是一篇指导您如何在Microsoft Visual Studio下学习STL并进行实践的文章。这篇文章从STL的基础知识讲起,循序渐进,逐步深入,涉及到了STL编写代码的方法、STL代码的编译和调试、命名空间(namespace)、STL中的ANSI / ISO字符串、各种不同类型的容器(container)、模板(template)、游标(Iterator)、算法(Algorithms)、分配器(Allocator)、容器的嵌套等方面的问题,作者在这篇文章中对读者提出了一些建议,并指出了使用STL时应该注意的问题。这篇文章覆盖面广,视角全面。不仅仅适合初学者学习STL,更是广大读者使用STL编程的实践指南。

STL简介

STL (标准模版库,Standard Template Library)是当今每个从事C++编程的人需要掌握的一项不错的技术。我觉得每一个初学STL的人应该花费一段时间来熟悉它,比如,学习STL时会有急剧升降的学习曲线,并且有一些命名是不太容易凭直觉就能够记住的(也许是好记的名字已经被用光了),然而如果一旦你掌握了STL,你就不会觉得头痛了。和MFC相比,STL更加复杂和强大。
STL有以下的一些优点:

可以方便容易地实现搜索数据或对数据排序等一系列的算法;

调试程序时更加安全和方便;

即使是人们用STL在UNIX平台下写的代码你也可以很容易地理解(因为STL是跨平台的)。

背景知识

写这一部分是让一些初学计算机的读者在富有挑战性的计算机科学领域有一个良好的开端,而不必费力地了解那无穷无尽的行话术语和沉闷的规则,在这里仅仅把那些行话和规则当作STLer们用于自娱的创造品吧。

使用代码
本文使用的代码在STL实践中主要具有指导意义。

一些基础概念的定义

模板(Template)——类(以及结构等各种数据类型和函数)的宏(macro)。有时叫做甜饼切割机(cookie cutter),正规的名称应叫做范型(generic)——一个类的模板叫做范型类(generic class),而一个函数的模板也自然而然地被叫做范型函数(generic function)。
STL——标准模板库,一些聪明人写的一些模板,现在已成为每个人所使用的标准C++语言中的一部分。
容器(Container)——可容纳一些数据的模板类。STL中有vector,set,map,multimap和deque等容器。
向量(Vector)——基本数组模板,这是一个容器。
游标(Iterator)——这是一个奇特的东西,它是一个指针,用来指向STL容器中的元素,也可以指向其它的元素。

Hello World程序

我愿意在我的黄金时间在这里写下我的程序:一个hello world程序。这个程序将一个字符串传送到一个字符向量中,然后每次显示向量中的一个字符。向量就像是盛放变长数组的花园,大约所有STL容器中有一半是基于向量的,如果你掌握了这个程序,你便差不多掌握了整个STL的一半了。


//程序:vector演示一
//目的:理解STL中的向量

// #include "stdafx.h" -如果你使用预编译的头文件就包含这个头文件
#include <vector>  // STL向量的头文件。这里没有".h"。
#include <iostream>  // 包含cout对象的头文件。
using namespace std;  //保证在程序中可以使用std命名空间中的成员。

char* szHW = "Hello World";  
//这是一个字符数组,以”\0”结束。

int main(int argc, char* argv[])
{
  vector <char> vec;  //声明一个字符向量vector (STL中的数组)

  //为字符数组定义一个游标iterator。
  vector <char>::iterator vi;

  //初始化字符向量,对整个字符串进行循环,
  //用来把数据填放到字符向量中,直到遇到”\0”时结束。
  char* cptr = szHW;  // 将一个指针指向“Hello World”字符串
  while (*cptr != '\0')
  {  vec.push_back(*cptr);  cptr++;  }
  // push_back函数将数据放在向量的尾部。

  // 将向量中的字符一个个地显示在控制台
  for (vi=vec.begin(); vi!=vec.end(); vi++)  
  // 这是STL循环的规范化的开始——通常是 "!=" , 而不是 "<"
  // 因为"<" 在一些容器中没有定义。
  // begin()返回向量起始元素的游标(iterator),end()返回向量末尾元素的游标(iterator)。
  {  cout << *vi;  }  // 使用运算符 “*” 将数据从游标指针中提取出来。
  cout << endl;  // 换行

  return 0;
}


push_back是将数据放入vector(向量)或deque(双端队列)的标准函数。Insert是一个与之类似的函数,然而它在所有容器中都可以使用,但是用法更加复杂。end()实际上是取末尾加一(取容器中末尾的前一个元素),以便让循环正确运行——它返回的指针指向最靠近数组界限的数据。就像普通循环中的数组,比如for (i=0; i<6; i++) {ar[i] = i;} ——ar[6]是不存在的,在循环中不会达到这个元素,所以在循环中不会出现问题。

STL的烦恼之一——初始化

STL令人烦恼的地方是在它初始化的时候。STL中容器的初始化比C/C++数组初始化要麻烦的多。你只能一个元素一个元素地来,或者先初始化一个普通数组再通过转化填放到容器中。我认为人们通常可以这样做:


//程序:初始化演示
//目的:为了说明STL中的向量是怎样初始化的。

#include <cstring>  // <cstring>和<string.h>相同
#include <vector>
using namespace std;

int ar[10] = {  12, 45, 234, 64, 12, 35, 63, 23, 12, 55  };
char* str = "Hello World";

int main(int argc, char* argv[])
{
  vector <int> vec1(ar, ar+10);
  vector <char> vec2(str, str+strlen(str));
  return 0;
}



在编程中,有很多种方法来完成同样的工作。另一种填充向量的方法是用更加熟悉的方括号,比如下面的程序:

//程序:vector演示二
//目的:理解带有数组下标和方括号的STL向量

#include <cstring>
#include <vector>
#include <iostream>
using namespace std;

char* szHW = "Hello World";
int main(int argc, char* argv[])
{
  vector <char> vec(strlen(sHW)); //为向量分配内存空间
  int i, k = 0;
  char* cptr = szHW;
  while (*cptr != '\0')
  {  vec[k] = *cptr;  cptr++;  k++;  }
  for (i=0; i<vec.size(); i++)
  {  cout << vec[i];  }
  cout << endl;
  return 0;
}


这个例子更加清晰,但是对游标(iterator)的操作少了,并且定义了额外的整形数作为下标,而且,你必须清楚地在程序中说明为向量分配多少内存空间。

命名空间(Namespace)

与STL相关的概念是命名空间(namespace)。STL定义在std命名空间中。有3种方法声明使用的命名空间:

1.用using关键字使用这个命名空间,在文件的顶部,但在声明的头文件下面加入:
using namespace std;
这对单个工程来说是最简单也是最好的方法,这个方法可以把你的代码限定在std命名空间中。

2.使用每一个模板前对每一个要使用的对象进行声明(就像原形化):
using std::cout;
using std::endl;
using std::flush;
using std::set;
using std::inserter;
尽管这样写有些冗长,但可以对记忆使用的函数比较有利,并且你可以容易地声明并使用其他命名空间中的成员。

3.在每一次使用std命名空间中的模版时,使用std域标识符。比如:
typedef std::vector VEC_STR;
这种方法虽然写起来比较冗长,但是是在混合使用多个命名空间时的最好方法。一些STL的狂热者一直使用这种方法,并且把不使用这种方法的人视为异类。一些人会通过这种方法建立一些宏来简化问题。

除此之外,你可以把using namespace std加入到任何域中,比如可以加入到函数的头部或一个控制循环体中。

一些建议

为了避免在调试模式(debug mode)出现恼人的警告,使用下面的编译器命令:

#pragma warning(disable: 4786)

另一条需要注意的是,你必须确保在两个尖括号之间或尖括号和名字之间用空格隔开,因为是为了避免同“>>”移位运算符混淆。比如
vector <list<int>> veclis;
这样写会报错,而这样写:
vector <list <int> > veclis;
就可以避免错误。

                                STL实践指南(中)

STL实践指南  Practical Guide to STL
作者:Jeff Bogan
翻译:周翔


(接上篇)

另一种容器——集合(set)

这是微软帮助文档中对集合(set)的解释:“描述了一个控制变长元素序列的对象(注:set中的key和value是Key类型的,而map中的key和value是一个pair结构中的两个分量)的模板类,每一个元素包含了一个排序键(sort key)和一个值(value)。对这个序列可以进行查找、插入、删除序列中的任意一个元素,而完成这些操作的时间同这个序列中元素个数的对数成比例关系,并且当游标指向一个已删除的元素时,删除操作无效。”
而一个经过更正的和更加实际的定义应该是:一个集合(set)是一个容器,它其中所包含的元素的值是唯一的。这在收集一个数据的具体值的时候是有用的。集合中的元素按一定的顺序排列,并被作为集合中的实例。如果你需要一个键/值对(pair)来存储数据,map是一个更好的选择。一个集合通过一个链表来组织,在插入操作和删除操作上比向量(vector)快,但查找或添加末尾的元素时会有些慢。
下面是一个例子:

//程序:set演示
//目的:理解STL中的集合(set)

#include <string>
#include <set>
#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
  set <string> strset;
  set <string>::iterator si;
  strset.insert("cantaloupes");
  strset.insert("apple");
  strset.insert("orange");
  strset.insert("banana");
  strset.insert("grapes");
  strset.insert("grapes");  
  for (si=strset.begin(); si!=strset.end(); si++)  
  {  cout << *si << " ";  }
  cout << endl;
  return 0;
}

// 输出: apple banana cantaloupes grapes orange
//注意:输出的集合中的元素是按字母大小顺序排列的,而且每个值都不重复。


如果你感兴趣的话,你可以将输出循环用下面的代码替换:

copy(strset.begin(), strset.end(), ostream_iterator<string>(cout, " "));

.集合(set)虽然更强大,但我个人认为它有些不清晰的地方而且更容易出错,如果你明白了这一点,你会知道用集合(set)可以做什么。

所有的STL容器

容器(Container)的概念的出现早于模板(template),它原本是一个计算机科学领域中的一个重要概念,但在这里,它的概念和STL混合在一起了。下面是在STL中出现的7种容器:

vector(向量)——STL中标准而安全的数组。只能在vector 的“前面”增加数据。
deque(双端队列double-ended queue)——在功能上和vector相似,但是可以在前后两端向其中添加数据。
list(列表)——游标一次只可以移动一步。如果你对链表已经很熟悉,那么STL中的list则是一个双向链表(每个节点有指向前驱和指向后继的两个指针)。
set(集合)——包含了经过排序了的数据,这些数据的值(value)必须是唯一的。
map(映射)——经过排序了的二元组的集合,map中的每个元素都是由两个值组成,其中的key(键值,一个map中的键值必须是唯一的)是在排序或搜索时使用,它的值可以在容器中重新获取;而另一个值是该元素关联的数值。比如,除了可以ar[43] = "overripe"这样找到一个数据,map还可以通过ar["banana"] = "overripe"这样的方法找到一个数据。如果你想获得其中的元素信息,通过输入元素的全名就可以轻松实现。
multiset(多重集)——和集合(set)相似,然而其中的值不要求必须是唯一的(即可以有重复)。
multimap(多重映射)——和映射(map)相似,然而其中的键值不要求必须是唯一的(即可以有重复)。
注意:如果你阅读微软的帮助文档,你会遇到对每种容器的效率的陈述。比如:log(n*n)的插入时间。除非你要处理大量的数据,否则这些时间的影响是可以忽略的。如果你发现你的程序有明显的滞后感或者需要处理时间攸关(time critical)的事情,你可以去了解更多有关各种容器运行效率的话题。

怎样在一个map中使用类?

Map是一个通过key(键)来获得value(值)的模板类。
另一个问题是你希望在map中使用自己的类而不是已有的数据类型,比如现在已经用过的int。建立一个“为模板准备的(template-ready)”类,你必须确保在该类中包含一些成员函数和重载操作符。下面的一些成员是必须的:

缺省的构造函数(通常为空)

拷贝构造函数

重载的”=”运算符

你应该重载尽可能多的运算符来满足特定模板的需要,比如,如果你想定义一个类作为 map中的键(key),你必须重载相关的运算符。但在这里不对重载运算符做过多讨论了。

//程序:映射自定义的类。
//目的:说明在map中怎样使用自定义的类。

#include <string>
#include <iostream>
#include <vector>
#include <map>
using namespace std;

class CStudent
{
public :
  int nStudentID;
  int nAge;
public :
  //缺省构造函数——通常为空
  CStudent()  {  }
  // 完整的构造函数
  CStudent(int nSID, int nA)  {  nStudentID=nSID; nAge=nA;  }
  //拷贝构造函数
  CStudent(const CStudent& ob)  
    {  nStudentID=ob.nStudentID; nAge=ob.nAge;  }
  // 重载“=”
  void operator = (const CStudent& ob)  
    {  nStudentID=ob.nStudentID; nAge=ob.nAge;  }
};

int main(int argc, char* argv[])
{
  map <string, CStudent> mapStudent;

  mapStudent["Joe Lennon"] = CStudent(103547, 22);
  mapStudent["Phil McCartney"] = CStudent(100723, 22);
  mapStudent["Raoul Starr"] = CStudent(107350, 24);
  mapStudent["Gordon Hamilton"] = CStudent(102330, 22);

  // 通过姓名来访问Cstudent类中的成员
  cout << "The Student number for Joe Lennon is " <<
    (mapStudent["Joe Lennon"].nStudentID) << endl;

  return 0;
}



TYPEDEF

如果你喜欢使用typedef关键字,下面是个例子:
typedef set <int> SET_INT;
typedef SET_INT::iterator SET_INT_ITER


编写代码的一个习惯就是使用大写字母和下划线来命名数据类型。

ANSI / ISO字符串

ANSI/ISO字符串在STL容器中使用得很普遍。这是标准的字符串类,并得到了广泛地提倡,然而在缺乏格式声明的情况下就会出问题。你必须使用“<<”和输入输出流(iostream)代码(如dec, width等)将字符串串联起来。
可在必要的时候使用c_str()来重新获得字符指针。
        
                
                                STL实践指南(下)
STL实践指南  Practical Guide to STL
作者:Jeff Bogan
翻译:周翔


(接中篇)

游标(Iterator)

我说过游标是指针,但不仅仅是指针。游标和指针很像,功能很像指针,但是实际上,游标是通过重载一元的”*”和”->”来从容器中间接地返回一个值。将这些值存储在容器中并不是一个好主意,因为每当一个新值添加到容器中或者有一个值从容器中删除,这些值就会失效。在某种程度上,游标可以看作是句柄(handle)。通常情况下游标(iterator)的类型可以有所变化,这样容器也会有几种不同方式的转变:
iterator——对于除了vector以外的其他任何容器,你可以通过这种游标在一次操作中在容器中朝向前的方向走一步。这意味着对于这种游标你只能使用“++”操作符。而不能使用“--”或“+=”操作符。而对于vector这一种容器,你可以使用“+=”、“—”、“++”、“-=”中的任何一种操作符和“<”、“<=”、“>”、“>=”、“==”、“!=”等比较运算符。
reverse_iterator ——如果你想用向后的方向而不是向前的方向的游标来遍历除vector之外的容器中的元素,你可以使用reverse_iterator 来反转遍历的方向,你还可以用rbegin()来代替begin(),用rend()代替end(),而此时的“++”操作符会朝向后的方向遍历。
const_iterator ——一个向前方向的游标,它返回一个常数值。你可以使用这种类型的游标来指向一个只读的值。
const_reverse_iterator ——一个朝反方向遍历的游标,它返回一个常数值。

Set和Map中的排序

除了类型和值外,模板含有其他的参数。你可以传递一个回调函数(通常所说的声明“predicate”——这是带有一个参数的函数返回一个布尔值)。例如,如果你想自动建立一个集合,集合中的元素按升序排列,你可以用简明的方法建立一个set类:

set <int, greater<int> > set1

greater 是另一个模板函数(范型函数),当值放置在容器中后,它用来为这些值排序。如果你想按降序排列这些值,你可以这样写:

set <int, less<int> > set1

在实现算法时,将声明(predicate)作为一个参数传递到一个STL模板类中时会遇到很多的其他情况,下面将会对这些情况进行详细描述。

STL 的烦恼之二——错误信息

这些模板的命名需要对编译器进行扩充,所以当编译器因某种原因发生故障时,它会列出一段很长的错误信息,并且这些错误信息晦涩难懂。我觉得处理这样的难题没有什么好办法。但最好的方法是去查找并仔细研究错误信息指明代码段的尾端。还有一个烦恼就是:当你双击错误信息时,它会将错误指向模版库的内部代码,而这些代码就更难读了。一般情况下,纠错的最好方法是重新检查一下你的代码,运行时忽略所有的警告信息。

算法(Algorithms)

算法是模板中使用的函数。这才真正开始体现STL的强大之处。你可以学习一些大多数模板容器中都会用到的一些算法函数,这样你可以通过最简便的方式进行排序、查找、交换等操作。STL中包含着一系列实现算法的函数。比如:sort(vec.begin()+1, vec.end()-1)可以实现对除第一个和最后一个元素的其他元素的排序操作。
容器自身不能使用算法,但两个容器中的游标可以限定容器中使用算法的元素。既然这样,算法不直接受到容器的限制,而是通过采用游标,算法才能够得到支持。此外,很多次你会遇到传递一个已经准备好了的函数(以前提到的声明:predicate)作为参数,你也可以传递以前的旧值。
下面的例子演示了怎样使用算法:

//程序:测试分数统计
//目的:通过对向量中保存的分数的操作说明怎样使用算法

#include <algorithm>  //如果要使用算法函数,你必须要包含这个头文件。
#include <numeric>  // 包含accumulate(求和)函数的头文件
#include <vector>
#include <iostream>
using namespace std;

int testscore[] = {67, 56, 24, 78, 99, 87, 56};

//判断一个成绩是否通过了考试
bool passed_test(int n)
{
  return (n >= 60);
}

// 判断一个成绩是否不及格
bool failed_test(int n)
{
  return (n < 60);
}

int main(int argc, char* argv[])
{
  int total;
  // 初始化向量,使之能够装入testscore数组中的元素
  vector <int> vecTestScore(testscore,
     testscore + sizeof(testscore) / sizeof(int));
  vector <int>::iterator vi;

  // 排序并显示向量中的数据
  sort(vecTestScore.begin(), vecTestScore.end());
  cout << "Sorted Test Scores:" << endl;
  for (vi=vecTestScore.begin(); vi != vecTestScore.end(); vi++)
  {  cout << *vi << ", ";  }
  cout << endl;

  // 显示统计信息

  // min_element 返回一个 _iterator_ 类型的对象,该对象指向值最小的那个元素。
  //“*”运算符提取元素中的值。
  vi = min_element(vecTestScore.begin(), vecTestScore.end());
  cout << "The lowest score was " << *vi << "." << endl;

  //与min_element类似,max_element是选出最大值。
  vi = max_element(vecTestScore.begin(), vecTestScore.end());
  cout << "The highest score was " << *vi << "." << endl;

  // 使用声明函数(predicate function,指vecTestScore.begin()和vecTestScore.end())来确定通过考试的人数。
  cout << count_if(vecTestScore.begin(), vecTestScore.end(), passed_test) <<
    " out of " << vecTestScore.size() <<
    " students passed the test" << endl;

  // 确定有多少人考试挂了
  cout << count_if(vecTestScore.begin(),
    vecTestScore.end(), failed_test) <<
    " out of " << vecTestScore.size() <<
    " students failed the test" << endl;

  //计算成绩总和
  total = accumulate(vecTestScore.begin(),
     vecTestScore.end(), 0);
  // 计算显示平均成绩
  cout << "Average score was " <<
    (total / (int)(vecTestScore.size())) << endl;

  return 0;
}


Allocator(分配器)

Allocator用在模板的初始化阶段,是为对象和数组进行分配内存空间和释放空间操作的模板类。它在各种情况下扮演着很神秘的角色,它关心的是高层内存的优化,而且对黑盒测试来说,使用Allocator是最好的选择。通常,我们不需要明确指明它,因为它们通常是作为不用添加的缺省的参数出现的。如果在专业的测试工作中出现了Allocator,你最好搞清楚它是什么。

Embed Templates(嵌入式模版)和Derive Templates(基模板)

每当你使用一个普通的类的时候,你也可以在其中使用一个STL类。它是可以被嵌入的:

class CParam
{
  string name;
  string unit;
  vector <double> vecData;
};


或者将它作为一个基类:

class CParam : public vector <double>
{
  string name;
  string unit;
};


STL模版类作为基类时需要谨慎。这需要你适应这种编程方式。

模版中的模版

为构建一个复杂的数据结构,你可以将一个模板植入另一个模板中(即“模版嵌套”)。一般最好的方法是在程序前面使用typedef关键字来定义一个在另一个模板中使用的模版类型。

// 程序:在向量中嵌入向量的演示。
//目的:说明怎样使用嵌套的STL容器。

#include <iostream>
#include <vector>

using namespace std;

typedef vector <int> VEC_INT;

int inp[2][2] = {{1, 1}, {2, 0}};  
  // 要放入模板中的2x2的正则数组

int main(int argc, char* argv[])
{
  int i, j;
  vector <VEC_INT> vecvec;
  // 如果你想用一句话实现这样的嵌套,你可以这样写:
  // vector <vector <int> > vecvec;
  
  // 将数组填入向量
  VEC_INT v0(inp[0], inp[0]+2);  
    // 传递两个指针
    // 将数组中的值拷贝到向量中
  VEC_INT v1(inp[1], inp[1]+2);

  vecvec.push_back(v0);
  vecvec.push_back(v1);

  for (i=0; i<2; i++)
  {
    for (j=0; j<2; j++)
    {
      cout << vecvec[i][j] << "  ";
    }
    cout << endl;
  }
  return 0;
}

// 输出:
// 1 1
// 2 0


虽然在初始化时很麻烦,一旦你将数据填如向量中,你就实现了一个变长的可扩充的二维数组(大小可扩充直到使用完内存)。根据实际需要,可以使用各种容器的嵌套组合。

总结

STL是有用的,但是使用过程中的困难和麻烦是再所难免的。就像中国人所说的:“如果你掌握了它,便犹如虎添翼。”


由于CSDN的文章编辑器可能将"<"和">"之间的部分作为html标签过滤掉了,现贴出正确的“测试分数统计”程序如下:

//程序:测试分数统计
//目的:通过对向量中保存的分数的操作说明怎样使用算法

#include <algorithm> //如果要使用算法函数,你必须要包含这个头文件。
#include <numeric> // 包含accumulate(求和)函数的头文件
#include <vector>
#include <iostream>
using namespace std;

int testscore[] = {67, 56, 24, 78, 99, 87, 56};

//判断一个成绩是否通过了考试
bool passed_test(int n)
{
return (n >= 60);
}

// 判断一个成绩是否不及格
bool failed_test(int n)
{
return (n < 60);
}

int main(int argc, char* argv[])
{
int total;
// 初始化向量,使之能够装入testscore数组中的元素
vector <int> vecTestScore(testscore,
testscore + sizeof(testscore) / sizeof(int));
vector <int>::iterator vi;

// 排序并显示向量中的数据
sort(vecTestScore.begin(), vecTestScore.end());
cout << "Sorted Test Scores:" << endl;
for (vi=vecTestScore.begin(); vi != vecTestScore.end(); vi++)
{ cout << *vi << ", "; }
cout << endl;

// 显示统计信息

// min_element 返回一个 _iterator_ 类型的对象,该对象指向值最小的那个元素。
//“*”运算符提取元素中的值。
vi = min_element(vecTestScore.begin(), vecTestScore.end());
cout << "The lowest score was " << *vi << "." << endl;

//与min_element类似,max_element是选出最大值。
vi = max_element(vecTestScore.begin(), vecTestScore.end());
cout << "The highest score was " << *vi << "." << endl;

// 使用声明函数(predicate function,指vecTestScore.begin()和vecTestScore.end())来确定通过考试的人数。
cout << count_if(vecTestScore.begin(), vecTestScore.end(), passed_test) <<
" out of " << vecTestScore.size() <<
" students passed the test" << endl;

// 确定有多少人考试挂了
cout << count_if(vecTestScore.begin(),
vecTestScore.end(), failed_test) <<
" out of " << vecTestScore.size() <<
" students failed the test" << endl;

//计算成绩总和
total = accumulate(vecTestScore.begin(),
vecTestScore.end(), 0);
// 计算显示平均成绩
cout << "Average score was " <<
(total / (int)(vecTestScore.size())) << endl;

return 0;
}

posted @ 2007-04-20 08:18 榕树藤 阅读(1475) | 评论 (0) | 编辑 收藏

使用VC6.0实现窗口的任意分割

一、关于CSplitterWnd类
 我们在使用CuteFtp或者NetAnt等工具的时候,一般都会被其复杂的界面所吸引,在这些界面中窗口被分割为若干的区域,真正做到了窗口的任意分割。 那么我们自己如何创建类似的界面,也实现窗口的任意的分割呢 ?在VC6.0中这就需要使用到CSplitterWnd类。CSplitterWnd看上去像是一种特殊的框架窗口,每个窗口都被相同的或者不同的视图所填充。当窗口被切分后用户可以使用鼠标移动切分条来调整窗口的相对尺寸。虽然VC6.0支持从AppWizard中创建分割窗口,但是自动加入的分割条总是不能让我们满意,因此我们还是通过手工增加代码来熟悉这个类。
 CSplitterWnd的构造函数主要包括下面三个。
BOOL Create(CWnd* pParentWnd,int nMaxRows,int nMaxCols,SIZE sizeMin,CCreateContext* pContext,DWORD dwStyle,UINT nID);
    功能描述:该函数用来创建动态切分窗口。
    参数含义:pParentWnd 切分窗口的父框架窗口。
    nMaxRows,nMaxCols是创建的最大的列数和行数。
    sizeMin是窗格的现实大小。
    pContext 大多数情况下传给父窗口。
    nID是字窗口的ID号.
  BOOL CreateStatic(CWnd* pParentWnd,int nRows,int nCols,DWORD dwStyle,UINT nID)
  功能描述:用来创建切分窗口。
  参数含义同上。
BOOL CreateView (int row,int col,CruntimeClass* pViewClass,SIZE sizeinit,CcreateContext* pContext);
  功能描述:为静态切分的窗口的网格填充视图。在将视图于切分窗口联系在一起的时候必 须先将切分窗口创建好。
  参数含义:同上。
  从CSplitterWnd源程序可以看出不管是使用动态创建Create还是使用静态创建CreateStatic,在函数中都调用了一个保护函数CreateCommon,从下面的CreateCommon函数中的关键代码可以看出创建CSplitterWnd的实质是创建了一系列的MDI子窗口。
    DWORD dwCreateStyle = dwStyle & ~(WS_HSCROLL|WS_VSCROLL);
    if (afxData.bWin4)
       dwCreateStyle &= ~WS_BORDER; // create with the same wnd-class as MDI-Frame (no erase bkgnd)
    if (!CreateEx(0, _afxWndMDIFrame, NULL, dwCreateStyle, 0, 0, 0, 0,pParentWnd->m_hWnd, (HMENU)nID, NULL))
      return FALSE; // create invisible


二、创建嵌套分割窗口
  2.1创建动态分割窗口
  动态分割窗口使用Create方法。下面的代码将创建2x2的窗格。
  m_wndSplitter.Create(this,2,2,CSize(100,100),pContext);
  但是动态创建的分割窗口的窗格数目不能超过2x2,而且对于所有的窗格,都必须共享同一个视图,所受的限制也比较多,因此我们不将动态创建作为重点。我们的主要精力放在静态分割窗口的创建上。
  2.2创建静态分割窗口
与动态创建相比,静态创建的代码要简单许多,而且可以最多创建16x16的窗格。不同的窗格我们可以使用CreateView填充不同的视图。
在这里我们将创建CuteFtp的窗口分割。CuteFtp的分割情况如下:

CCuteFTPView
CView2
CView3
CView4

  创建步骤:
  ▲ 在创建之前我们必须先用AppWizard生成单文档CuteFTP,生成的视类为 CCuteFTPView.同时在增加三个视类或者从视类继承而来的派生类CView2,CView3 CView4.
  ▲ 增加成员:
  在Cmainfrm.h中我们将增加下面的代码:
  CSplitterWnd wndSplitter1;
  CSplitterWnd wndSplitter2;

  ▲ 重载CMainFrame::OnCreateClient()函数:
  BOOL CMainFrame::OnCreateClient( LPCREATESTRUCT /*lpcs*/, CCreateContext* pContext)
  { //创建一个静态分栏窗口,分为三行一列
    if(m_wndSplitter1.CreateStatic(this,3,1)==NULL)
      return FALSE;
    //将CCuteFTPView连接到0行0列窗格上
    m_wndSplitter1.CreateView(0,0,RUNTIME_CLASS(CCuteFTPView),CSize(100,100), pContext);
    m_wndSplitter1.CreateView(2,0,RUNTIME_CLASS(CView4),CSize(100,100),pContext); //将CView4连接到0行2列
    if(m_wndSplitter2.CreateStatic(&m_wndSplitter,1,2,WS_CHILD|WS_VISIBLE, m_wndSplitter.IdFromRowCol(1, 0))==NULL)
      return FALSE; //将第1行0列再分开1行2列
    //将CView2类连接到第二个分栏对象的0行0列
    m_wndSplitter2.CreateView(0,0,RUNTIME_CLASS(CView2),CSize(400,300),pContext); //将CView3类连接到第二个分栏对象的0行1列
    m_wndSplitter2.CreateView(0,1,RUNTIME_CLASS(CView3),CSize(400,300),pContext);
    return TRUE;
  }

2.3实现各个分割区域的通信
■有文档相连的视图之间的通信
由AppWizard生成的CCuteFTPView是与文档相连的,同时我们也让CView2与文档相连,因此我们需要修改CCuteFTPApp的InitInstance()函数,我们将增加下面的部分。
AddDocTemplate (new CMultiDocTemplate(IDR_VIEW2TYPE,
RUNTIME_CLASS(CMainDoc),
RUNTIME_CLASS(CMDIChildWnd),
RUNTIME_CLASS(CView2)));

我们现在来实现CCuteFTPView与CView2之间的通信。由于跟文档类相连的视图类 是不能安全的与除文档类之外的其余的视图类通信的。因此我们只能让他们都与文档 类通信。在文档中我们设置相应的指针以用来获的各个视图。我们重载 CCuteFTPView::OnOpenDocument()函数;
CCuteFTPView* pCuteFTPView;
CView2* pView2;
POSITION pos;
CView* pView;
while(pos!=NULL)
{
  pView=GetNextView(pos);
  if(pView->IsKindOf(RUNTIME_CLASS(CCuteFTPView))==NULL)
    pCuteFTPView=(CCuteFTPView*)pView;
  else(pView->IsKindOf(RUNTIME_CLASS(CCuteFTPView))==NULL)
    pView2=(CView2*)pView;
}

这样我们在文档类中就获的了跟它相连的所有的视图的指针。
如果需要在 CCuteFTPView中调用CView2中的一个方法DoIt()则代码如下:
CCuteFTPDoc* pDoc=GetDocument();
CView2* pView2=pDoc->pView3;
pView3.DoIt();

■无文档视图与文档关联视图之间的通信
CView3和CView4都是不与文档相关联的。我们现在实现CView3与CView2的通信.正如前面所说,CView2只能安全的与CCuteFTPDoc通信,因此,CView3如果需要跟CView2通信,也必须借助于文档类。因此程序的关键是如何在CView3中获得文档的指针。视图类中没有这样的类成员可以用来直接访问文档类。但是我们知道在主窗口类MainFrame中我们可以获得程序的任意窗口类的指针。因此我们只要获得程序主窗口了的指针,就可以解决问题了。代码实现在CView3中访问CView2中的DoIt()方法。

CView3中的代码如下:

CMainFrame* MainFrame=(CMainFrame*)this->GetParent()->GetParent();
CCuteFTPDoc* Doc=(CCuteFTPDoc*)MainFrame->GetActiveDocument();
if(Doc!=NULL) Doc->DoIt();

CCuteFTPDoc中的相应的处理函数DoIt()代码如下:

CView2* pView2;
POSITION pos;
CView* pView;
while(pos!=NULL)
{
  pView=GetNextView(pos);
  if(pView->IsKindOf(RUNTIME_CLASS(CView2))==NULL)
  pView2=(CView2*)pView;
}
pView2->DoIt();

■无文档关联视图之间的通信
CView3和CView4都是不跟文档相连的,如何实现他们之间的通信呢。 正如我们在上面所说的那样,由于在主框架中我们可以访问任意的视图,因此我们的主要任 务还是在程序中获得主框架的指针。在CView3中访问CView4中的方法DoIt()。
CMainFrame* MainFrame=(CMainFrame*)this->GetParent()->GetParent();
CView4* View4=(CView4*)MainFrame->m_wndSplitter1.GetPane(2,0);
View4->DoIt();


到现在我们已经实现了CuteFTP的主窗口的框架并且能够实现他们之间相互通信的框架。 同样的我们可以实现其他的一些流行界面例如NetAnts,Foxmail的分割。

三、关于对话框的分割
到目前为止,只有基于文档/视图的程序才能使用CSplitterWnd,而基于对话框的应用程序却不支持CSplitterWnd,但是如果我们在继承类中重载一些虚拟方法,也能使CSplitterWnd 在对话框程序中使用。从MFC的源程序WinSplit.cpp中可以看出,为了获得父窗口的地方程序都调用了虚拟方法GetParentFrame(),因此如果在对话框中使用,我们必须将它改为GetParent();因此我们将CSplitterWnd的下面几个方法重载。
  virtual void StartTracking(int ht);
  virtual CWnd* GetActivePane(int* pRow = NULL, int* pCol = NULL);
  virtual void SetActivePane( int row, int col, CWnd* pWnd = NULL );
  virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
  virtual BOOL OnNotify( WPARAM wParam, LPARAM lParam, LRESULT* pResult );
  virtual BOOL OnWndMsg( UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult );

具体实现如下,实现中我将给出原有代码的主要部分以及修改后的代码以作对比。
在cpp文件中加入下面的枚举类型。
enum HitTestValue
{
  noHit = 0,//表示没有选中任何对象
  vSplitterBox = 1,
  hSplitterBox = 2,
  bothSplitterBox = 3,
  vSplitterBar1 = 101,//代表各个方向的水平分割条
  vSplitterBar15 = 115,
  hSplitterBar1 = 201,//代表垂直方向的各个分割条
  hSplitterBar15 = 215,
  splitterIntersection1 = 301,//代表各个交叉点
  splitterIntersection225 = 525
};

CWnd* CxSplitterWnd::GetActivePane(int* pRow, int* pCol)
{
  ASSERT_VALID(this);
  //获得当前的获得焦点的窗口
  //下面注释粗体的是原有的代码的主要部分。
  // CWnd* pView = NULL;
  //CFrameWnd* pFrameWnd = GetParentFrame();
  //ASSERT_VALID(pFrameWnd);
  //pView = pFrameWnd->GetActiveView();
  //if (pView == NULL)
  // pView = GetFocus();
  CWnd* pView = GetFocus();
  if (pView != NULL && !IsChildPane(pView, pRow, pCol))
    pView = NULL;
  return pView;
}

void CxSplitterWnd::SetActivePane( int row, int col, CWnd* pWnd)
{
  CWnd* pPane = pWnd == NULL ? GetPane(row, col) : pWnd;
  //下面加注释粗体的是原有代码的主要部分。
  //FrameWnd* pFrameWnd = GetParentFrame();
  //ASSERT_VALID(pFrameWnd);
  //pFrameWnd->SetActiveView((CView*)pPane);
  pPane->SetFocus();//修改后的语句
}

void CxSplitterWnd::StartTracking(int ht)
{
  ASSERT_VALID(this);
  if (ht == noHit)
    return;
  // GetHitRect will restrict 'm_rectLimit' as appropriate
  GetInsideRect(m_rectLimit);
  if (ht >= splitterIntersection1 && ht <= splitterIntersection225)
  {
    // split two directions (two tracking rectangles)
    int row = (ht - splitterIntersection1) / 15;
    int col = (ht - splitterIntersection1) % 15;
    GetHitRect(row + vSplitterBar1, m_rectTracker);
    int yTrackOffset = m_ptTrackOffset.y;
    m_bTracking2 = TRUE;
    GetHitRect(col + hSplitterBar1, m_rectTracker2);
    m_ptTrackOffset.y = yTrackOffset;
  }
  else if (ht == bothSplitterBox)
  {
  // hit on splitter boxes (for keyboard)
  GetHitRect(vSplitterBox, m_rectTracker);
  int yTrackOffset = m_ptTrackOffset.y;
  m_bTracking2 = TRUE;
  GetHitRect(hSplitterBox, m_rectTracker2);
  m_ptTrackOffset.y = yTrackOffset; // center it
  m_rectTracker.OffsetRect(0, m_rectLimit.Height()/2); m_rectTracker2.OffsetRect(m_rectLimit.Width()/2, 0);
  }
  else
  {
  // only hit one bar
  GetHitRect(ht, m_rectTracker);
  }

//下面加注释的将从程序中删去。
//CView* pView = (CView*)GetActivePane();
//if (pView != NULL && pView->IsKindOf(RUNTIME_CLASS(CView)))
//{
// ASSERT_VALID(pView);
// CFrameWnd* pFrameWnd = GetParentFrame();
//ASSERT_VALID(pFrameWnd);
//pView->OnActivateFrame(WA_INACTIVE, pFrameWnd);
// }
// steal focus and capture
  SetCapture();
  SetFocus();
  // make sure no updates are pending
  RedrawWindow(NULL, NULL, RDW_ALLCHILDREN | RDW_UPDATENOW);
  // set tracking state and appropriate cursor
  m_bTracking = TRUE;
  OnInvertTracker(m_rectTracker);
  if (m_bTracking2)
    OnInvertTracker(m_rectTracker2);
  m_htTrack = ht;
  SetSplitCursor(ht);
  }

BOOL CxSplitterWnd::OnCommand(WPARAM wParam, LPARAM lParam)
{
  if (CWnd::OnCommand(wParam, lParam))
    return TRUE;
  //下面粗体的是原程序的语句
//return GetParentFrame()->SendMessage(WM_COMMAND, wParam, lParam);
  return GetParent()->SendMessage(WM_COMMAND, wParam, lParam);
}
BOOL CxSplitterWnd::OnNotify( WPARAM wParam, LPARAM lParam, LRESULT* pResult )
{
  if (CWnd::OnNotify(wParam, lParam, pResult))
    return TRUE;
  //下面粗体的是源程序的语句
  //*pResult = GetParentFrame()->SendMessage(WM_NOTIFY, wParam, lParam);
  *pResult = GetParent()->SendMessage(WM_NOTIFY, wParam, lParam);
  return TRUE;
}

BOOL CxSplitterWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
  // The code line below is necessary if using CxSplitterWnd in a regular dll
  // AFX_MANAGE_STATE(AfxGetStaticModuleState());
  return CWnd::OnWndMsg(message, wParam, lParam, pResult);
}

这样我们就可以在对话框中使用CxSplitterWnd类了。

四、CSplitterWnd的扩展
CSplitterWnd扩展话题是很多的,我们可以通过对原有方法的覆盖或者增加新的方法来扩展CSplitterWnd。我们在此仅举两个方面的例子。
4.1锁定切分条
当用户创建好分割窗口后,有时并不希望通过拖动切分条来调节窗口的大小。这时就必须锁定切分条。锁定切分条的最简单的方法莫过于不让CSplitterWnd来处理WM_LBUTTONDOWN,WM_MOUSEMOVE,WM_SETCURSOR消息,而是将这些消息交给CWnd窗口进行处理,从而屏蔽掉这些消息。拿WM_LBUTTONDOWN处理过程来说。修改为如下:
void CXXSplitterWnd::OnLButtonDown(UINT nFlags,CPoint point)
{
  CWnd::OnLButtonDown(nFlags,point);
}

其余的处理方法类似。
4.2切分条的定制
由Window自己生成的切分条总是固定的,没有任何的变化,我们在使用一些软件比如ACDSee的时候却能发现它们的切分条却是和自动生成的切分条不一样的。那么如何定制自己的切分条呢?通过重载CSplitterWnd的虚方法OnDrawSplitter和OnInvertTracker可以达到这样的目的。下面的代码生成的效果是分割窗口的边界颜色为红色,分割条的颜色为绿色.代码如下:
void CSplitterWndEx::OnDrawSplitter(CDC *pDC, ESplitType nType, const CRect &rectArg)
{
  if(pDC==NULL)
  {
  RedrawWindow(rectArg,NULL,RDW_INVALIDATE|RDW_NOCHILDREN);
  return;
  }
  ASSERT_VALID(pDC);
  CRect rc=rectArg;
  switch(nType)
  {
  case splitBorder:
  //重画分割窗口边界,使之为红色
    pDC->Draw3dRect(rc,RGB(255,0,0),RGB(255,0,0));
    rc.InflateRect(-CX_BORDER,-CY_BORDER);
    pDC->Draw3dRect(rc,RGB(255,0,0),RGB(255,0,0));
    return;
  case splitBox:
    pDC->Draw3dRect(rc,RGB(0,0,0),RGB(0,0,0));
    rc.InflateRect(-CX_BORDER,-CY_BORDER);
    pDC->Draw3dRect(rc,RGB(0,0,0),RGB(0,0,0));
    rc.InflateRect(-CX_BORDER,-CY_BORDER);
    pDC->FillSolidRect(rc,RGB(0,0,0));
    pDC->Draw3dRect(rc,RGB(0,0,0),RGB(0,0,0));
    return;
  case splitBar:
  //重画分割条,使之为绿色
    pDC->FillSolidRect(rc,RGB(255,255,255));
    rc.InflateRect(-5,-5);
    pDC->Draw3dRect(rc,RGB(255,0,0),RGB(255,0,0));
    return;
  default:
    ASSERT(FALSE);
  }
  pDC->FillSolidRect(rc,RGB(0,0,255));
}
void CSplitterWndEx::OnInvertTracker(CRect &rect)
{
  ASSERT_VALID(this);
  ASSERT(!rect.IsRectEmpty());
  ASSERT((GetStyle()&WS_CLIPCHILDREN)==0);
  CRect rc=rect;
  rc.InflateRect(2,2);
  CDC* pDC=GetDC();
  CBrush* pBrush=CDC::GetHalftoneBrush();
  HBRUSH hOldBrush=NULL;
  if(pBrush!=NULL) hOldBrush=(HBRUSH)SelectObject(pDC->m_hDC,pBrush->m_hObject);
  pDC->PatBlt(rc.left,rc.top,rc.Width(),rc.Height(),BLACKNESS);
  if(hOldBrush!=NULL)
  SelectObject(pDC->m_hDC,hOldBrush);
  ReleaseDC(pDC);
}

同样我们只要继承CSplitterWnd中的其余的一些虚拟方法就可以生成具有自己个性的分割窗口了。

posted @ 2007-04-18 21:06 榕树藤 阅读(606) | 评论 (1) | 编辑 收藏

pragma指令简介

在编写程序的时候,我们经常要用到#pragma指令来设定编译器的状态或者是指示编译器完成一些特定的动作.
 下面介绍了一下该指令的一些常用参数,希望对大家有所帮助!

 一. message 参数。
 message
 它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。

 其使用方法为:  #pragma message("消息文本")

 当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。
 当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法:
#ifdef _X86
#pragma message("_X86 macro activated!")
#endif
 当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示
 "_X86 macro activated!"
 这样,我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了。

 二. 另一个使用得比较多的#pragma参数是code_seg。
 格式如:
 #pragma code_seg( [ [ { push | pop}, ] [ identifier, ] ] [ "segment-name" [, "segment-class" ] )
 该指令用来指定函数在.obj文件中存放的节,观察OBJ文件可以使用VC自带的dumpbin命令行程序,函数在.obj文件中默认的存放节为.text节,如果code_seg没有带参数的话,则函数存放在.text节中。

 push (可选参数) 将一个记录放到内部编译器的堆栈中,可选参数可以为一个标识符或者节名
 pop(可选参数) 将一个记录从堆栈顶端弹出,该记录可以为一个标识符或者节名
 identifier (可选参数) 当使用push指令时,为压入堆栈的记录指派的一个标识符,当该标识符被删除的时候和其相关的堆栈中的记录将被弹出堆栈
   "segment-name" (可选参数) 表示函数存放的节名
 例如:
 //默认情况下,函数被存放在.text节中
 void func1() {   // stored in .text
 }

 //将函数存放在.my_data1节中
 #pragma code_seg(".my_data1")
 void func2() {   // stored in my_data1
 }

 //r1为标识符,将函数放入.my_data2节中
 #pragma code_seg(push, r1, ".my_data2")
 void func3() {   // stored in my_data2
 }

 int main() {
 }

 三. #pragma once (比较常用)
 这是一个比较常用的指令,只要在头文件的最开始加入这条指令就能够保证头文件被编译一次

 四. #pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。
 BCB可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。
 有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma startup指定编译优先级,如果使用了#pragma package(smart_init) ,BCB就会根据优先级的大小先后编译。

 五. #pragma warning指令
 该指令允许有选择性的修改编译器的警告消息的行为
 指令格式如下:
 #pragma warning( warning-specifier : warning-number-list [; warning-specifier : warning-number-list...]
 #pragma warning( push[ ,n ] )
 #pragma warning( pop )

 主要用到的警告表示有如下几个:

 once:只显示一次(警告/错误等)消息
 default:重置编译器的警告行为到默认状态
 1,2,3,4:四个警告级别
 disable:禁止指定的警告信息
 error:将指定的警告信息作为错误报告

 如果大家对上面的解释不是很理解,可以参考一下下面的例子及说明

 #pragma warning( disable : 4507 34; once : 4385; error : 164 )
 等价于:
 #pragma warning(disable:4507 34)  // 不显示4507和34号警告信息
 #pragma warning(once:4385)   // 4385号警告信息仅报告一次
 #pragma warning(error:164)   // 把164号警告信息作为一个错误。
 同时这个pragma warning 也支持如下格式:
 #pragma warning( push [ ,n ] )
 #pragma warning( pop )
 这里n代表一个警告等级(1---4)。
 #pragma warning( push )保存所有警告信息的现有的警告状态。
 #pragma warning( push, n)保存所有警告信息的现有的警告状态,并且把全局警告等级设定为n。
 #pragma warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的一切改动取消。例如:
 #pragma warning( push )
 #pragma warning( disable : 4705 )
 #pragma warning( disable : 4706 )
 #pragma warning( disable : 4707 )
 #pragma warning( pop )

 在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)

 在使用标准C++进行编程的时候经常会得到很多的警告信息,而这些警告信息都是不必要的提示,所以我们可以使用#pragma warning(disable:4786)来禁止该类型的警告在vc中使用ADO的时候也会得到不必要的警告信息,这个时候我们可以通过#pragma warning(disable:4146)来消除该类型的警告信息

 六. pragma comment(...)
 该指令的格式为:  #pragma comment( "comment-type" [, commentstring] )
 该指令将一个注释记录放入一个对象文件或可执行文件中,comment-type(注释类型):可以指定为五种预定义的标识符的其中一种。
 五种预定义的标识符为:

 1、compiler:将编译器的版本号和名称放入目标文件中,本条注释记录将被编译器忽略
如果你为该记录类型提供了commentstring参数,编译器将会产生一个警告
例如:#pragma comment( compiler )

 2、exestr:将commentstring参数放入目标文件中,在链接的时候这个字符串将被放入到可执行文件中,当操作系统加载可执行文件的时候,该参数字符串不会被加载到内存中.但是,该字符串可以被dumpbin之类的程序查找出并打印出来,你可以用这个标识符将版本号码之类的信息嵌入到可执行文件中!

 3、lib:这是一个非常常用的关键字,用来将一个库文件链接到目标文件中常用的lib关键字,可以帮我们连入一个库文件。
 例如:
 #pragma comment(lib, "user32.lib")
 该指令用来将user32.lib库文件加入到本工程中

 4、linker:将一个链接选项放入目标文件中,你可以使用这个指令来代替由命令行传入的或者在开发环境中设置的链接选项,你可以指定/include选项来强制包含某个对象,例如:
#pragma comment(linker, "/include:__mySymbol")
你可以在程序中设置下列链接选项
/DEFAULTLIB
/EXPORT
/INCLUDE
/MERGE
/SECTION

 这些选项在这里就不一一说明了,详细信息请看msdn!

 5、user:将一般的注释信息放入目标文件中commentstring参数包含注释的文本信息,这个注释记录将被链接器忽略
 例如:
 #pragma comment( user, "Compiled on " __DATE__ " at " __TIME__ )

posted @ 2007-04-18 10:24 榕树藤 阅读(358) | 评论 (1) | 编辑 收藏

仅列出标题
共2页: 1 2 
 
Powered by:
C++博客
Copyright © 榕树藤