关于作者

用户名:langzi0115
笔名:小k
地区: 北京
行业:本科

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



VC BLOG

访问统计:
文章个数:132
评论个数:16
留言条数:142




Powered by BlogDriver 2.1

小K的博客

 


文章

PE文件结构与虚拟地址空间

本章提要

·           PE文件格式概述

·           PE文件结构

·           如何获取PE文件中的OEP

·           如何获取PE文件中的资源

·           如何修改PE文件使其显示MessageBox的实例

2.1  引言
通常Windows下的EXE文件都采用PE格式。PE是英文Portable Executable的缩写,它是一种针对于微软Windows NT、Windows 95和Win32s系统,由微软公司设计的可执行的二进制文件(DLLs和执行程序)格式,目标文件和库文件通常也是这种格式。这种格式由TIS(Tool Interface Standard)委员会(Microsoft、Intel、Borland、Watcom、IBM等)在1993进行了标准化。显然,它参考了一些UNIXes和VMS的COFF(Common Object File Format)格式。

认识可执行文件的结构非常重要,在DOS下是这样,在Windows系统下更是如此。了解了这种结构后就可以对可执行程序进行加密、加壳和修改等,一些黑客也利用了这些技术。为了使读者对PE文件格式有进一步的认识,本章从一个程序员的角度出发再次介绍PE文件格式。如果已经熟悉这方面的知识,可以跳过这一章。

2.2  PE文件格式概述
认识PE文件,既要懂得它的结构布局,又要知道它是如何装载到计算机内存中的。下面分别对它们进行说明。

2.2.1  PE文件结构布局
找到文件中某一结构信息有两种定位方法。第一种是通过链表方法,对于这种方法,数据在文件的存放位置比较自由。第二种方法是采用紧凑或固定位置存放,这种方法要求数据结构大小固定,它在文件中的存放位置也相对固定。在PE文件结构中同时采用以上两种方法。

因为在PE文件头中的每个数据结构大小是固定的,因此能够编写计算程序来确定某一个PE文件中的某个参数值。在编写程序时,所用到的数据结构定义,包括数据结构中变量类型、变量位置和变量数组大小都必须采用Windows提供的原型。图2.1所示的PE文件结构的总体层次分布如下:

PE文件结构总体层次分布

·         DOS MZ Header

所有 PE文件(甚至32位的DLLs)必须以简单的DOS MZ header开始,它是一个IMAGE_DOS_HEADER结构。有了它,一旦程序在DOS下执行,DOS就能识别出这是有效的执行体,然后运行紧随MZ Header之后的DOS Stub。

·         DOS Stub 

DOS Stub实际上是个有效的EXE,在不支持PE文件格式的操作系统中,它将简单显示一个错误提示,类似于字符串“This program requires Windows”或者程序员可根据自己的意图实现完整的DOS代码。大多数情况下DOS Stub由汇编器/编译器自动生成。

·         PE Header

紧接着DOS Stub的是PE Header。它是一个IMAGE_NT_HEADERS结构。其中包含了很多PE文件被载入内存时需要用到的重要域。执行体在支持PE文件结构的操作系统中执行时,PE装载器将从DOS MZ header中找到PE header的起始偏移量。因而跳过DOS Stub直接定位到真正的文件头 PE header。

·         Section Table

PE Header之后是数组结构Section Table(节表)。如果PE文件里有5个节,那么此Section Table结构数组内就有5个(IMAGE_SECTION_HEADER)成员,每个成员包含对应节的属性、文件偏移量、虚拟偏移量等。排在节表中的最前面的第一个默认成员是text,即代码节头。通过遍历查找方法可以找到其他节表成员(节表头)。

·         Sections

PE文件的真正内容划分成块,称为Sections(节)。每个标准节的名字均以圆点开头,但也可以不以圆点开头,节名的最大长度为8个字节。Sections是以其起始位址来排列,而不是以其字母次序来排列。通过节表提供的信息,可以找到这些节。程序的代码,资源等就放在这些节中。

节的划分是基于各组数据的共同属性,而不是逻辑概念。每节是一块拥有共同属性的数据,比如代码/数据、读/写等。如果PE文件中的数据/代码拥有相同属性,它们就能被归入同一节中。节名称仅仅是个区别不同节的符号而已,类似“data”,“code”的命名只为了便于识别,唯有节的属性设置决定了节的特性和功能。

2.2.2  PE文件内存映射
在Windows系统下,当一个PE应用程序运行时,这个PE文件在磁盘中的数据结构布局和内存中的数据结构布局是一致的。系统在载入一个可执行程序时,首先是Windows装载器(又称PE装载器)把磁盘中的文件映射到进程的地址空间,它遍历PE文件并决定文件的哪一部分被映射。其方式是将文件较高的偏移位置映射到较高的内存地址中。磁盘文件一旦被装入内存中,其某项的偏移地址可能与原始的偏移地址有所不同,但所表现的是一种从磁盘文件偏移到内存偏移的转换,如图2.2所示。

PE文件内存映射

当PE文件被加载到内存后,内存中的版本称为模块(Module),映射文件的起始地址称为模块句柄(hModule),可以通过模块句柄访问内存中的其他数据结构。这个初始内存地址也称为文件映像基址(ImageBase)。载入一个PE程序的主要步骤如下:

(1)当PE文件被执行时,PE装载器首先为进程分配一个4GB的虚拟地址空间,然后把程序所占用的磁盘空间作为虚拟内存映射到这个4GB的虚拟地址空间中。一般情况下,会映射到虚拟地址空间中0x400000的位置。装载一个应用程序的时间比一般人所设想的要少,因为装载一个PE文件并不是把这个文件一次性地从磁盘读到内存中,而是简单地做一个内存映射,映射一个大文件和映射一个小文件所花费的时间相差无几。当然,真正执行文件中的代码时,操作系统还是要把存在于磁盘上的虚拟内存中的代码交换到物理内存(RAM)中。但是,这种交换也不是把整个文件所占用的虚拟地址空间一次性地全部从磁盘交换到物理内存中,操作系统会根据需要和内存占用情况交换一页或多页。当然,这种交换是双向的,即存在于物理内存中的一部分当前没有被使用的页,也可能被交换到磁盘中。

(2)PE装载器在内核中创建进程对象和主线程对象以及其他内容。

(3)PE装载器搜索PE文件中的Import Table(引入表),装载应用程序所使用的动态链接库。对动态链接库的装载与对应用程序的装载方法完全类似。

(4)PE装载器执行PE文件首部所指定地址处的代码,开始执行应用程序主线程。

2.2.3  Big-endian和Little-endian
PE Header中IMAGE_FILE_HEADER的成员Machine 中的值,根据winnt.h中的定义,对于Intel CPU应该为0x014c。但是用十六进制编辑器打开PE文件时,看到这个WORD显示的却是4c 01。其实4c 01就是0x014c,只不过由于Intel CPU是Little-endian,所以显示出来是这样的。对于Big-endian和Little-endian,请看下面的例子。一个整型int变量,长度为4个字节。当这个整形变量的值为0x12345678时,对于Big-endian来说,显示的是{12,34,45,78},而对于Little-endian来说,显示的却是{78,45,34,12}。注意Intel使用的是Little-endian。

2.2.4  3种不同的地址
PE文件的各种结构中,涉及到很多地址、偏移。有些是指在文件中的偏移,有些    是指在内存中的偏移。以下的第一种是指在文件中的地址,第二、三种是指在内存中的地址。

第一种,文件中的地址。比如用十六进制编辑器打开PE文件,看到的地址(偏移)就是文件中的地址,使用某个结构的文件地址,就可以在文件中找到该结构。

第二种,当文件被整个映射到内存时,例如某些PE分析软件,把整个PE文件映射到内存中,这时是内存中的虚拟地址(VA)。如果知道在这个文件中某一个结构的内存地址的话,那么它等于这个PE文件被映射到内存的地址加上该结构在文件中的地址。

第三种,当执行PE时,PE文件会被载入器载入内存,这时经常需要的是RVA。例如知道一个结构的RVA,那么程序载入点加上RVA就可以得到该结构的内存地址。比如,如果PE文件装入虚拟地址(VA)空间的0x400000处,某一结构的RVA 为0x1000,那么其虚拟地址为0x401000。

PE文件格式要用到RVA,主要是为了减少PE装载器的负担。因为每个模块都有可能被重载到任何虚拟地址空间,如果让PE装载器修正每个重定位项,这肯定是个梦魇。相反,如果所有重定位项都使用RVA,那么PE装载器就不必操心那些东西了,即它只要将整个模块重定位到新的起始VA。这就像相对路径和绝对路径的概念:RVA类似相对路径,VA就像绝对路径。

注意,RVA和VA是指内存中,不是指文件中。是指相对于载入点的偏移而不是一个内存地址,只有RVA加上载入点的地址,才是一个实际的内存地址。

2.3  PE文件结构
在win32 SDK的文件winnt.h中有PE文件格式的定义。本文所用到的变量,如果没有特别说明,都在文件winnt.h中定义。

有关一些PE头文件结构一般都有32位和64位之分,如IMAGE_NT_HEADERS32和IMAGE_NT_HEADERS64等,除了在64位版本中的一些扩展域外,这些结构总是一样的。是采用32位还是64位,需要用#define _WIN64来定义,如果没有这种定义,则采用的是32位的文件结构。编译器将根据此定义选择相应的编译模式。

2.3.1  MS-DOS头部
MS-DOS头部占据了PE文件的头64个字节,描述它内容的结构如下:

l          

// 此结构包含于WINNT.H中

//

typedef struct _IMAGE_DOS_HEADER {   // DOS的.EXE头部

    WORD e_magic;       // 魔术数字

    WORD e_cblp;        // 文件最后页的字节数

    WORD e_cp;          // 文件页数

    WORD e_crlc;        // 重定义元素个数

    WORD e_cparhdr;     // 头部尺寸,以段落为单位

    WORD e_minalloc;    // 所需的最小附加段

    WORD e_maxalloc;    // 所需的最大附加段

    WORD e_ss;          // 初始的SS值(相对偏移量)

    WORD e_sp;          // 初始的SP值

    WORD e_csum;        // 校验和

    WORD e_ip;          // 初始的IP值

    WORD e_cs;          // 初始的CS值(相对偏移量)

    WORD e_lfarlc;      // 重分配表文件地址

    WORD e_ovno;        // 覆盖号

    WORD e_res[4];      // 保留字

    WORD e_oemid;       // OEM标识符(相对e_oeminfo)

    WORD e_oeminfo;     // OEM信息

    WORD e_res2[10];    // 保留字

    LONG e_lfanew;      // 新exe头部的文件地址

} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

l          

其中第一个域e_magic,被称为魔术数字,它用于表示一个MS-DOS兼容的文件类型。所有MS-DOS兼容的可执行文件都将这个值设为0x5A4D,表示ASCII字符MZ。MS-DOS头部之所以有的时候被称为MZ头部,就是这个缘故。还有许多其他的域对于MS-DOS操作系统来说都有用,但是对于Windows NT来说,这个结构中只有一个有用的域——最后一个域e_lfnew,一个4字节的文件偏移量,PE文件头部就是由它定位的。

2.3.2  IMAGE_NT_HEADER头部
PE Header是紧跟在MS-DOS头部和实模式程序残余之后的,描述它内容的结构   如下:

l          

typedef struct  _IMAGE_NT_HEADERS {

    DWORD Signature;                           // PE文件头标志:"PE\0\0"

    IMAGE_FILE_HEADER FileHeader;               // PE文件物理分布的信息

    IMAGE_OPTIONAL_HEADER32 OptionalHeader; // PE文件逻辑分布的信息

} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

紧接PE文件头标志之后是PE文件头结构,由20个字节组成,它被定义为:

l          

typedef struct _IMAGE_FILE_HEADER {

    WORD    Machine;

    WORD    NumberOfSections;

    DWORD   TimeDateStamp;

    DWORD   PointerToSymbolTable;

    DWORD   NumberOfSymbols;

    WORD    SizeOfOptionalHeader;

    WORD    Characteristics;

} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

#define IMAGE_SIZEOF_FILE_HEADER 20

l          

其中请注意这个文件头部的大小已经定义在这个包含文件之中了,这样一来,想要得到这个结构的大小就很方便了。

Machine:表示该程序要执行的环境及平台,现在已知的值如表2.1所示。

应用程序执行的环境及平台代码

IMAGE_FILE_MACHINE_I386(0x14c)
 Intel 80386  处理器以上
 
0x014d
 Intel 80486 处理器以上
 
0x014e
 Intel Pentium 处理器以上
 
0x0160
 R3000(MIPS)处理器,big endian
 
IMAGE_FILE_MACHINE_R3000(0x162)
 R3000(MIPS)处理器,little endian
 
IMAGE_FILE_MACHINE_R4000(0x166)
 R4000(MIPS)处理器,little endian
 
IMAGE_FILE_MACHINE_R10000(0x168)
 R10000(MIPS)处理器,little endian
 
IMAGE_FILE_MACHINE_ALPHA(0x184)
 DEC Alpha AXP处理器
 
IMAGE_FILE_MACHINE_POWERPC(0x1f0)
 IBM Power PC,little endian
 
   

NumberOfSections:段的个数。

TimeDateStamp:文件建立的时间。可用这个值来区分同一个文件的不同的版本,即使它们的商业版本号相同。这个值的格式并没有明确的规定,但是很显然地大多数的C编译器都把它定为从1970.1.1 00:00:00以来的秒数(time_t)。这个值有时也被用做绑定输入目录表。注意:一些编译器将忽略这个值。

PointerToSymbolTable及NumberOfSymbols:用在调试信息中,用途不太明确,不过它们的值总为0。

SizeOfOptionalHeader:可选头的长度(sizeof IMAGE_OPTIONAL_HEADER),可以用它来检验PE文件的正确性。

Characteristics:是一个标志的集合,其大部分位用于OBJ或LIB文件中。

文件头下面就是可选择头,这是一个叫做IMAGE_OPTIONAL_HEADER的结构,由224个字节组成。虽然它的名字是“可选头部”,但是请确信:这个头部并非“可选”,而是“必需”的。可选头部包含了很多关于可执行映像的重要信息。例如,初始的堆栈大小、程序入口点的位置、首选基地址、操作系统版本、段对齐的信息等。IMAGE_ OPTIONAL_HEADER结构如下:

l          

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES     16

typedef struct _IMAGE_OPTIONAL_HEADER {

    //

    // 标准域

    //

    WORD    Magic;                    

    BYTE    MajorLinkerVersion;        

    BYTE    MinorLinkerVersion;        

    DWORD   SizeOfCode;                  

    DWORD   SizeOfInitializedData;      

    DWORD   SizeOfUninitializedData;        

    DWORD   AddressOfEntryPoint;        

    DWORD   BaseOfCode;                  

    DWORD   BaseOfData;                  

    //

    // NT附加域      

    //

    DWORD   ImageBase;                   

    DWORD   SectionAlignment;           

    DWORD   FileAlignment;          

    WORD    MajorOperatingSystemVersion;

    WORD    MinorOperatingSystemVersion;

    WORD    MajorImageVersion;       

    WORD    MinorImageVersion;       

    WORD    MajorSubsystemVersion;      

    WORD    MinorSubsystemVersion;      

    DWORD   Win32VersionValue;      

    DWORD   SizeOfImage;                

    DWORD   SizeOfHeaders;          

    DWORD   CheckSum;                

    WORD    Subsystem;                   

    WORD    DllCharacteristics;     

    DWORD   SizeOfStackReserve;     

    DWORD   SizeOfStackCommit;      

    DWORD   SizeOfHeapReserve;      

    DWORD   SizeOfHeapCommit;           

    DWORD   LoaderFlags;                

    DWORD   NumberOfRvaAndSizes;        

    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];

} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;

l          

其中参数含义如下所述。

Magic:这个值好像总是0x010b。

MajorLinkerVersion及MinorLinkerVersion:链接器的版本号,这个值不太可靠。

SizeOfCode:可执行代码的长度。

SizeOfInitializedData:初始化数据的长度(数据段)。

SizeOfUninitializedData:未初始化数据的长度(bss段)。

AddressOfEntryPoint:代码的入口RVA地址,程序从这儿开始执行,常称为程序的原入口点OEP(Original Entry Point)。

BaseOfCode:可执行代码起始位置。

BaseOfData:初始化数据起始位置。

ImageBase:载入程序首选的RVA地址。这个地址可被Loader改变。

SectionAlignment:段加载后在内存中的对齐方式。

FileAlignment:段在文件中的对齐方式。

MajorOperatingSystemVersion及MinorOperatingSystemVersion:操作系统版本。

MajorImageVersion及MinorImageVersion:程序版本。

MajorSubsystemVersion及MinorSubsystemVersion:子系统版本号,这个域系统支持。例如,程序运行于NT下,子系统版本号如果不是4.0,对话框不能显示3D风格。

Win32VersionValue:这个值总是为0。

SizeOfImage:程序调入后占用内存大小(字节),等于所有段的长度之和。

SizeOfHeaders:所有文件头长度之和,它等于从文件开始到第一个段的原始数据之间的大小。

CheckSum:校验和,仅用在驱动程序中,在可执行文件中可能为0。它的计算方法Microsoft不公开,在imagehelp.dll中的CheckSumMappedFile()函数可以计算它。

Subsystem:一个标明可执行文件所期望的子系统的枚举值。

DllCharacteristics:DLL状态。

SizeOfStackReserve:保留堆栈大小。

SizeOfStackCommit:启动后实际申请的堆栈数,可随实际情况变大。

SizeOfHeapReserve:保留堆大小。

SizeOfHeapCommit:实际堆大小。

LoaderFlags:目前没有用。

NumberOfRvaAndSizes:下面的目录表入口个数,这个值也不可靠,可用常数IMAGE_NUMBEROF_DIRECTORY_ENTRIES来代替它,这个值在目前Windows版本中设为16。注意,如果这个值不等于16,那么这个数据结构大小就不能固定下来,也就不能确定其他变量位置。

DataDirectory:是一个IMAGE_DATA_DIRECTORY数组,数组元素个数为IMAGE_NUMBEROF_DIRECTORY_ENTRIES,结构如下:

l          

typedef struct _IMAGE_DATA_DIRECTORY {

    DWORD   VirtualAddress;         // 起始RVA地址

    DWORD   Size;                      // 长度

} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

2.3.3  IMAGE_SECTION_HEADER头部
PE文件格式中,所有的节头部位于可选头部之后。每个节头部为40个字节长,并且没有任何填充信息。节头部被定义为以下的结构:

l          

#define IMAGE_SIZEOF_SHORT_NAME 8

typedef struct _IMAGE_SECTION_HEADER {

    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];   // 节表名称,如".text"

    union {

        DWORD   PhysicalAddress;        // 物理地址

        DWORD   VirtualSize;            // 真实长度

    } Misc;

    DWORD   VirtualAddress;                 // RVA

    DWORD   SizeOfRawData;                  // 物理长度

    DWORD   PointerToRawData;               // 节基于文件的偏移量

    DWORD   PointerToRelocations;           // 重定位的偏移

    DWORD   PointerToLinenumbers;           // 行号表的偏移

    WORD    NumberOfRelocations;         // 重定位项数目

    WORD    NumberOfLinenumbers;         // 行号表的数目

    DWORD   Characteristics;            // 节属性

} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

l          

其中IMAGE_SIZEOF_SHORT_NAME等于8。注意,如果不是这个值,那么这个数据结构大小就不能固定下来,也就不能确定其他变量位置。

2.4  如何获取PE文件中的OEP
OEP(Original Entry Point)是每个PE文件被加载时的起始地址,如何获得这个地址很重要,因为修改程序中的这个值是文件加壳和脱壳时的必须步骤,一些黑客程序也是通过修改OEP值来获得对目标程序的控制权从而实施攻击。下面分别介绍如何通过文件直接访问和通过内存映射访问读取OEP值的方法,并给出完整的程序代码。

2.4.1  通过文件读取OEP值
获得OEP值的最简单方法是,直接从一个PE文件中读取OEP。根据以上对PE文件结构的介绍可知,OEP是PE文件的IMAGE_OPTIONAL_HEADER结构的AddressOfEntryPoint成员,在偏移此结构头40个字节处。而IMAGE_OPTIONAL_ HEADER在PE文件的起始位置由IMAGE_DOS_HEADER的e_lfanew成员来计算。注意,以上两个结构在PE文件中不是紧跟在一起的,它之间是DOS Stub,而在每个PE文件DOS Stub的长度可能不一定相等。在PE文件的头部是IMAGE_ DOS_HEADER结构,读取这个结构可以得到e_lfanew的值,因而可以得到IMAGE_ OPTIONAL_HEADER在PE文件中的位置,也就得到了OEP值。以下是通过文件访问的方法读取OEP的程序代码,即:

l          

// 通过文件读取OEP值

BOOL ReadOEPbyFile(LPCSTR szFileName)

{

    HANDLE hFile;

   

    // 打开文件

    if ((hFile = CreateFile(szFileName, GENERIC_READ,

        FILE_SHARE_READ, 0, OPEN_EXISTING,

        FILE_FLAG_SEQUENTIAL_SCAN, 0)) == INVALID_HANDLE_VALUE)

    {

        printf("Can't not open file.\n");

        return FALSE;

    }

   

    DWORD dwOEP,cbRead;

    IMAGE_DOS_HEADER dos_head[sizeof(IMAGE_DOS_HEADER)];

    if (!ReadFile(hFile, dos_head, sizeof(IMAGE_DOS_HEADER), &cbRead, NULL)){

        printf("Read image_dos_header failed.\n");

        CloseHandle(hFile);

        return FALSE;

    }

   

    int nEntryPos=dos_head->e_lfanew+40;

    SetFilePointer(hFile, nEntryPos, NULL, FILE_BEGIN);

   

    if (!ReadFile(hFile, &dwOEP, sizeof(dwOEP), &cbRead, NULL)){

        printf("read OEP failed.\n");

        CloseHandle(hFile);

        return FALSE;

    }

   

    // 关闭文件

    CloseHandle(hFile);

   

    // 显示OEP地址

    printf("OEP by file:%d\n",dwOEP);

    return TRUE;

}

2.4.2  通过内存映射读取OEP值
获得OEP值的另一种方法是通过内存映射来实现,此方法也需要熟悉PE的文件结构。与直接访问PE的方法不同,内存映射的方法首先把PE文件映射到计算机的内存,再通过内存的基指针获得IMAGE_DOS_HEADER的头指针,由此再获得IMAGE_ OPTIONAL_HEADER指针,这样就可以得到AddressOfEntryPoint的值。下面是通过内存映射获得OEP值的方法:

l          

// 通过文件内存映射读取OEP值

BOOL ReadOEPbyMemory(LPCSTR szFileName)

{

    struct PE_HEADER_MAP

    {

        DWORD signature;

        IMAGE_FILE_HEADER _head;

        IMAGE_OPTIONAL_HEADER opt_head;

        IMAGE_SECTION_HEADER section_header[6];

    } *header;

    HANDLE hFile;

    HANDLE hMapping;

    void *basepointer;

   

    // 打开文件

    if ((hFile = CreateFile(szFileName, GENERIC_READ,

        FILE_SHARE_READ,0,OPEN_EXISTING,

        FILE_FLAG_SEQUENTIAL_SCAN,0)) == INVALID_HANDLE_VALUE)

    {

        printf("Can't open file.\n");

        return FALSE;

    }

   

    // 创建内存映射文件

   if (!(hMapping = CreateFileMapping(hFile,0,PAGE_READONLY|SEC_COMMIT, 0,0,0)))

    {

        printf("Mapping failed.\n");

        CloseHandle(hFile);

        return FALSE;

    }

   

    // 把文件头映象存入baseointer

    if (!(basepointer = MapViewOfFile(hMapping,FILE_MAP_READ,0,0,0)))

    {

        printf("View failed.\n");

        CloseHandle(hMapping);

        CloseHandle(hFile);

        return FALSE;

    }

    IMAGE_DOS_HEADER * dos_head =(IMAGE_DOS_HEADER *)basepointer;

   

    // 得到PE文件头

    header = (PE_HEADER_MAP *)((char *)dos_head + dos_head->e_lfanew);

   

    // 得到OEP地址.

    DWORD dwOEP=header->opt_head.AddressOfEntryPoint;

   

    // 清除内存映射和关闭文件

    UnmapViewOfFile(basepointer);

    CloseHandle(hMapping);

    CloseHandle(hFile);

   

    // 显示OEP地址

    printf("OEP by memory:%d\n",dwOEP);

    return TRUE;

}

2.4.3  读取OEP值方法的测试
为了检验以上两种获取OEP值方法的正确性和一致性,可以用以下的方法来测试:

l          

// oep.cpp:读取OEP的实例

//

#include <windows.h>

#include <stdio.h>

BOOL ReadOEPbyMemory(LPCSTR szFileName);

BOOL ReadOEPbyFile(LPCSTR szFileName);

void main()

{

    ReadOEPbyFile("..\\calc.exe");

    ReadOEPbyMemory("..\\calc.exe");

}

l          

运行以上代码后,可以得到如图2.3所示的结果。从图中可以看出,以上两种获取OEP值方法所得到的结果是一致的。

获取OEP值方法的测试结果

2.5  PE文件中的资源
一些PE格式(Portable Executable)的EXE文件常常存在很多资源,如图标、位图、对话框、声音等。若要把这些资源取出为自己所用,或修改这些文件中的资源,则需要对PE文件中资源数据结构有所了解。

2.5.1  查找资源在文件中的起始位置
要找出一个PE文件中的某种资源,首先需要确定资源节在PE文件中的起始位置。有两种方法来确定资源在文件中的起始位置。

第一种方法,首先根据FileHeader中的成员NumberOfSections的值,确定文件中节的数目,再根据节的数目,遍历节表数组。也就是从0到(节表数–1)的每一个节表项。比较每一个节表项的Name字段,看看是否等于“.rsrc”,如果是,就找到了资源节的节表项。这个节表项的PointerToRawData 中的值,就是资源节在文件中的位置。

第二种方法,取得PE Header中的IMAGE_OPTIONAL_HEADER中的DataDirectory数组中的第三项,也就是资源项。DataDirectory[]数组的每项都是IMAGE_DATA_ DIRECTORY结构,该结构定义如下:

l          

typedef struct _IMAGE_DATA_DIRECTORY {

    DWORD VirtualAddress;

    DWORD Size;

} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

l          

从以上结构对象取得DataDirectory数组中的第三项中的成员VirtualAddress的值。这个值就是在内存中资源节的RVA。然后根据节的数目,遍历节表数组,也就是从0~(节表数–1)的每一个节表项。每个节在内存中的RVA的范围是从该节表项的成员VirtualAddress字段的值开始(包括这个值),到VirtualAddress+Misc.VirtualSize的值结束(不包括这个值)。遍历整个节表,看看所取得的资源节的RVA是否在那个节表项的RVA范围之内。如果在范围之内,就找到了资源节的节表项。这个节表项中的PointerToRawData 中的值,就是资源节在文件中的位置。如果这个PE文件没有资源   的话,DataDirectory数组中的第三项内容为0。这样也可以得到了资源在文件中开始的位置。

2.5.2  确定PE文件中的资源
得到了资源节在文件中的位置后,就可以确定某个资源类型及其二进制数据在PE文件中的位置和数据块的大小。

资源节最开始是一个IMAGE_RESOURCE_DIRECTORY结构,在winnt.h文件中有这个结构的定义。这个结构长度为16字节,共有6个参数,其结构的原型如下:

l          

typedef struct _IMAGE_RESOURCE_DIRECTORY {

    DWORD Characteristics;

    DWORD TimeDateStamp;

    WORD MajorVersion;

    WORD MinorVersion;

    WORD NumberOfNamedEntries;

    WORD NumberOfIdEntries;

    // IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];

} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;

l          

其中各个参数的含义如下所述

Characteristics: 标识此资源的类型。

TimeDateStamp:资源编译器产生资源的时间。

MajorVersion:资源主版本号。

MinorVersion:资源次版本号。

NumberOfNamedEntries和NumberofIDEntries:分别为用字符串和整形数字来进行标识的IMAGE_RESOURCE_DIRECTORY_ENTRY项数组的成员个数。

紧跟着IMAGE_RESOURCE_DIRECTORY后面的是一个IMAGE_RESOURCE_ DIRECTORY_ENTRY数组。这个结构长度为8个字节,共有两个字段,每个字段4个字节。其结构原型如下:

l          

typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {

    union {

        struct {

            DWORD NameOffset:31;

            DWORD NameIsString:1;

        };

        DWORD   Name;

        WORD    Id;

    };

    union {

        DWORD   OffsetToData;

        struct {

            DWORD   OffsetToDirectory:31;

            DWORD   DataIsDirectory:1;

        };

    };

} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;

l          

其中,对于第一个字段,当其最高位为1(0x80000000)时,这个DWORD剩下的31位表明相对于资源开始位置的偏移,偏移的内容是一个IMAGE_RESOURCE_DIR_ STRING_U,用其中的字符串来标明这个资源类型;当第一个字段的最高位为0时,表示这个DWORD的低WORD中的值作为Id标明这个资源类型。

对于第二个字段,当第二个字段的最高位为1时,表示还有下一层的结构。这个DWORD的剩下31位表明一个相对于资源开始位置的偏移,这个偏移的内容将是一个下一层的IMAGE_RESOURCE_DIRECTORY结构;当第二个字段的最高位为0时,表示已经没有下一层的结构了。这个DWORD的剩下31位表明一个相对于资源开始位置的偏移,这个偏移的内容会是一个IMAGE_RESOURCE_DATA _ENTRY结构,此结构会说明资源的位置。对于资源标示号Id,当Id等于1时,表示资源为光标,等于2时表示资源为位图等,等于3时表示资源为图标等。在winuser.h文件中有定义。

标识一个IMAGE_RESOURCE_DIRECTORY_ENTRY一般都是使用Id,就是一个整数。但是也有少数使用IMAGE_RESOURCE_DIR_STRING_U来标识一个资源类型。这个结构定义如下:

l          

typedef struct _IMAGE_RESOURCE_DIR_STRING_U {

    WORD Length;

    WCHAR NameString[1];

} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;

l          

这个结构中将有一个Unicode的字符串,是字对齐的。这个结构的长度可变,由第一个字段Length指明后面的Unicode字符串的长度。

经过3层IMAGE_RESOURCE_DIRECTORY_ENTRY(一般是3层,也有可能更少些)最终可以找到一个IMAGE_RESOURCE_DATA_ENTRY结构,这个结构中存有相应资源的位置和大小。这个结构长16个字节,有4个参数,其原型如下:

l          

typedef struct _IMAGE_RESOURCE_DATA_ENTRY {

    DWORD OffsetToData;

    DWORD Size;

    DWORD CodePage;

    DWORD Reserved;

} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;

l          

其中各个参数的含义如下所述。

OffsetToData:这是一个内存中的RVA,可以用来转化成文件中的位置。用这个值减去资源节的开始RVA,就可以得到相对于资源节开始的偏移。再加上资源节在文件中的开始位置,即节表中资源节中PointerToRawData的值,就是资源在文件中的位置。注意,资源节的开始RVA可以由Optional Header中的DataDirectory数组中的第三项中的VirtualAddress的值得到,或者节表中资源节那项中的VirtualAddress的值得到。

Size:资源的大小,以字节为单位。

CodePage:代码页。

Reserved:保留项。

总之,资源一般使用树来保存,通常包含3层,最高层是类型,其次是名字,最后是语言。在资源节开始的位置,首先是一个IMAGE_RESOURCE_DIRECTORY结构,后面紧跟着IMAGE_RESOURCE_DIRECTORY_ENTRY数组,这个数组的每个元素代表的资源类型不同;通过每个元素,可以找到第二层另一个IMAGE_RESOURCE_ DIRECTORY,后面紧跟着IMAGE_RESOURCE_DIRECTORY_ENTRY数组。这一层的数组的每个元素代表的资源名字不同;然后可以找到第三层的每个IMAGE_ RESOURCE_DIRECTORY,后面紧跟着IMAGE_RESOURCE_DIRECTORY_ENTRY数组。这一层的数组的每个元素代表的资源语言不同;最后通过每个IMAGE_RESOURCE_ DIRECTORY_ENTRY可以找到每个IMAGE_RESOURCE_DATA_ENTRY。通过每个IMAGE_RESOURCE_DATA_ENTRY,就可以找到每个真正的资源。

2.6  一个修改PE可执行文件的完整实例
在下面的实例中,将把一段MessageBoxA()的计算机代码根据PE文件的格式注入到一个PE程序中。有关把代码注入到一个应用程序的技术将在后面的章节专门介绍。

2.6.1  如何获得MessageBoxA代码
要实现代码注入PE程序且能够运行,首先要做的是如何得到这段代码。为了得到这种代码,作者编写了一段汇编源程序 msgbx.asm,然后用RadASM编译器进行编译,当然也可以使用其他的方法来实现代码的注入。编写这段代码最关键的问题是如何把对话框标题字符串和显示字符串一起存放在代码段,以便提取,否则无法提取。下面是生成MessageBoxA()的源代码:

l          

;msgbx.asm 文件.

;

.386p

.model flat, stdcall

option casemap:none

include         \masm32\include\windows.inc

include          \masm32\include\user32.inc

includelib      \masm32\lib\user32.lib

.code

start:

    push MB_ICONINFORMATION or MB_OK

    call Func1

    db "Test",0

Func1:

    call Func2

    db "Hello",0

Func2:

    push NULL   

    call MessageBoxA

;    ret

end start

l          

其中"Test"是MessageBoxA()对话框的标题,"Hello"是要显示的字符串。Message- BoxA()所用的Windows句柄为NULL。

用RadASM编译器对以上代码编译后,可以生成一个msgbx.obj文件,用VC++ 编辑器打开后,如图2.4所示,可以查看这个文件的机器代码。

Msgbx.obj文件的机器代码

把图2.4中所选择的计算机机器代码取出变成一个命令字符串,即:

l          

unsigned char cmdline[35]={

    0x6a,                               // (1) push 命令

    0x40,                               // (1) MB_ICONINFORMATION|MB_OK

    0xe8,                               // (1) call命令

    0x05,0x00,0x00,0x00,         // (4) 标题字符串字节个数,包括结束位

(DWORD)

    0x54,0x65,0x73,0x74, 0x00,      // (5) "Test",0(标题)

    0xe8,                           // (1) call命令

    0x06,0x00,0x00,0x00,            // (4) 标题字符串字节个数,包括结束位

(DWORD)

    0x48,0x65,0x6c,0x6c,0x6f,0x00,  // (6) "Hello",0(显示字符串)

    0x6a,                               // (1) push 命令

    0x00,                               // (1) 窗口句柄hWnd,NULL

    0xe8,                               // (1) call命令

    0x00,0x00,0x00,0x00,              // (4) MessageBoxA的地址 (DWORD)

    0x1a,                               // (1) 第26位,校验和

    0x00,0x00,0x00,0x0b               // (4) 返回地址 (DWORD)

};

l          

其中()中的数值表示这一行上代码的字节个数。0x6a是汇编语言中的push命令,0xe8是汇编语言中的call命令,而jmp命令为0xe9。“校验和”是从第一个push命令开始计算所得到的字节总数和(包括校验计数位),从以上代码第一个字节开始计数起到“校验和”位正好是第26位字节个数。字符串字节个数位为一个DWORD型,占4个字节,它是按Little-endian的方式存放的,要把这4个字节位的顺序颠倒才能得到实际数值,即把高位字节变成低位,把低位变换到高位。

要把以上代码注入到一个PE文件中,需要修改4个地方:(1)修改PE文件的入口地址,使PE装载器首先装载以上代码;(2)修改以上代码MessageBoxA()的地址,使以上的代码能够显示出一个对话框;(3)把“校验和”位变成跳转位,即变成jmp (0xe9);(4)修改返回地址,把程序引入到原来的装载点上。

2.6.2  把MessageBoxA()代码写入PE文件的完整实例
根据以上的对MessageBoxA()的分析,可以直接把以上代码注入到一个PE可执行 文件中。为了使程序有通用性,这里编写了一个产生显示任意长度字符的对话框的函数WriteMessageBox()。

下面是用于注入MessageBoxA()代码的头文件,取名为Pe.h,其中用 #include包含了相关的文件头,定义了peHeader结构,且定义了CPe类,其源代码如下:

l          

// Pe.h: 定义CPe类

//

#ifndef _PE_H__INCLUDED

#define _PE_H__INCLUDED

#include <io.h>

#include <fcntl.h>

#include <sys\stat.h>

typedef struct PE_HEADER_MAP

{

    DWORD signature;

    IMAGE_FILE_HEADER _head;

    IMAGE_OPTIONAL_HEADER opt_head;

    IMAGE_SECTION_HEADER section_header[6];

} peHeader;

class CPe 

{

public:

    CPe();

    virtual ~CPe();

public:

    void CalcAddress(const void *base);

    void ModifyPe(CString strFileName,CString strMsg);

    void WriteFile(CString strFileName,CString strMsg);

    BOOL WriteNewEntry(int ret,long offset,DWORD dwAddress);

    BOOL WriteMessageBox(int ret,long offset,CString strCap,CString

    strTxt);

    CString StrOfDWord(DWORD dwAddress);

public:

    DWORD dwSpace;

    DWORD dwEntryAddress;

    DWORD dwEntryWrite;

    DWORD dwProgRAV;

    DWORD dwOldEntryAddress;

    DWORD dwNewEntryAddress;

    DWORD dwCodeOffset;

    DWORD dwPeAddress;

    DWORD dwFlagAddress;

    DWORD dwVirtSize;

    DWORD dwPhysAddress;

    DWORD dwPhysSize;

    DWORD dwMessageBoxAadaddress;

};

#endif

l          

其中peHeader结构是前面所讲的PE Header结构与节表(Section Table)头结构(6个表头成员)的总结构。因为它们在PE文件中是紧凑排列的,所以可以这样写。其实只用一个节表头就可以。

下面分别介绍CPe类成员函数的定义,它们包含在Pe.cpp文件中。在这个文件开始用#include包含了stdafx.h和Pe.h文件。用MFC VC++编译器编译时,必须包括stdafx.h文件,即使这个文件是空的,也需要包括它,这是编译器设置所致,除非修改MFC的编译器的默认设置。CPe类的构造和析构函数这里没有用上,对系统内存的访问和其他操作主要是通过主成员函数ModifyPe()来进行。它们的源代码如下:

l          

// Pe.cpp: 实现 CPe类

//

#include "stdafx.h"

#include "Pe.h"

CPe::CPe()

{

}

CPe::~CPe()

{

}

void CPe::ModifyPe(CString strFileName,CString strMsg)

{

    CString strErrMsg;

    HANDLE hFile, hMapping;

    void *basepointer;

   

    // 打开要修改的文件

    if ((hFile = CreateFile(strFileName, GENERIC_READ|GENERIC_WRITE,

        FILE_SHARE_READ|FILE_SHARE_WRITE, 0,

        OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0)) == INVALID_HANDLE_ VALUE)

    {

        AfxMessageBox("Could not open file.");

        return;

    }

    // 创建一个映射文件

    if (!(hMapping = CreateFileMapping(hFile, 0, PAGE_READONLY | SEC_ COMMIT, 0, 0, 0)))

    {

        AfxMessageBox("Mapping failed.");

        CloseHandle(hFile);

        return;

    }

    // 把文件头映象存入baseointer

    if (!(basepointer = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0)))

    {

        AfxMessageBox("View failed.");

        CloseHandle(hMapping);

        CloseHandle(hFile);

        return;

    }

    CloseHandle(hMapping);

    CloseHandle(hFile);

    CalcAddress(basepointer); // 得到相关地址

    UnmapViewOfFile(basepointer);

   

    if(dwSpace<50)

    {

        AfxMessageBox("No room to write the data!");

    }

    else

    {

        WriteFile(strFileName,strMsg); // 写文件

    }

   

    if ((hFile = CreateFile(strFileName, GENERIC_READ|GENERIC_WRITE,

        FILE_SHARE_READ|FILE_SHARE_WRITE, 0,

OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0)) == INVALID_HANDLE_ VALUE)

    {

        AfxMessageBox("Could not open file.");

        return;

    }

   

    CloseHandle(hFile);

}

其中对一个PE文件进行MessageBoxA()代码的注入是通过ModifyPe()函数进行,它的入口参数是要被修改的PE可执行文件名。在这个函数中,首先创建所修改文件的句柄,然后创建映射文件,再通过映射文件的句柄获得这个PE文件的文件头指针,最后把这个指针传给函数CalcAddress()。通过CalcAddress()函数来计算PE Header的开始偏移、保存旧的程序入口地址、计算新的程序入口地址和计算PE文件的空隙空间等。

CalcAddress()函数的源代码如下:

l          

void CPe::CalcAddress(const void *base)

{

    IMAGE_DOS_HEADER * dos_head =(IMAGE_DOS_HEADER *)base;

    if (dos_head->e_magic != IMAGE_DOS_SIGNATURE)

    {

        AfxMessageBox("Unknown type of file.");

        return;

    }

   

    peHeader * header;

    // 得到PE文件头

    header = (peHeader *)((char *)dos_head + dos_head->e_lfanew);

    if(IsBadReadPtr(header, sizeof(*header)))

    {

        AfxMessageBox("No PE header, probably DOS executable.");

        return;

    }

    DWORD mods;

    char tmpstr[4]={0};

    if(strstr((const char *)header->section_header[0].Name,".text")!=

    NULL)

    {

        // 此段的真实长度

        dwVirtSize=header->section_header[0].Misc.VirtualSize;

        // 此段的物理偏移

        dwPhysAddress=header->section_header[0].PointerToRawData;

        // 此段的物理长度

        dwPhysSize=header->section_header[0].SizeOfRawData;

       

        // 得到PE文件头的开始偏移

        dwPeAddress=dos_head->e_lfanew;

       

        // 得到代码段的可用空间,用以判断可不可以写入我们的代码

        // 用此段的物理长度减去此段的真实长度就可以得到

        dwSpace=dwPhysSize-dwVirtSize;

        // 得到程序的装载地址,一般为0x400000

        dwProgRAV=header->opt_head.ImageBase;

        // 得到代码偏移,用代码段起始RVA减去此段的物理偏移

        // 应为程序的入口计算公式是一个相对的偏移地址,计算公式为:

        // 代码的写入地址+dwCodeOffset

        dwCodeOffset=header->opt_head.BaseOfCode-dwPhysAddress;

       

        // 代码写入的物理偏移

        dwEntryWrite=header->section_header[0].PointerToRawData+header->

            section_header[0].Misc.VirtualSize;

        //对齐边界

        mods=dwEntryWrite%16;

        if(mods!=0)

        {

            dwEntryWrite+=(16-mods);

        }

       

        // 保存旧的程序入口地址

        dwOldEntryAddress=header->opt_head.AddressOfEntryPoint;

        // 计算新的程序入口地址       

        dwNewEntryAddress=dwEntryWrite+dwCodeOffset;

        return;

    }

}  

l          

下面的StrOfDWord()函数是把一个DWORD值转换成一个字符串,因为一个DWORD值占有4个字节,因此把一个DWORD值变成一个字符串,若保持数值不变,就变成了一个4个字节的字符串。同时把这个值的位置顺序颠倒,这是为了把一个实际的值变成按Little-endian的方式写入PE文件中,其转换方法如下:

l          

CString CPe::StrOfDWord(DWORD dwAddress)

{

    unsigned char waddress[4]={0};

   

    waddress[3]=(char)(dwAddress>>24)&0xFF;

    waddress[2]=(char)(dwAddress>>16)&0xFF;

    waddress[1]=(char)(dwAddress>>8)&0xFF;

    waddress[0]=(char)(dwAddress)&0xFF;

  

    return waddress;

}

l          

下面的WriteNewEntry()函数把新的入口点写入PE程序原来的入口点处,使PE装载器在载入程序时,直接跳入到MessageBoxA()的入口处,该函数的源代码如下:

l          

BOOL CPe::WriteNewEntry(int ret,long offset, DWORD dwAddress)

{

    CString strErrMsg;

    long retf;

    unsigned char waddress[4]={0};

    retf=_lseek(ret,offset,SEEK_SET);

    if(retf==-1)

    {

        AfxMessageBox("Error seek.");

        return FALSE;

    }

    memcpy(waddress,StrOfDWord(dwAddress),4);

    retf=_write(ret,waddress,4);

   

    if(retf==-1)

    {

        strErrMsg.Format("Error write: %d",GetLastError());

        AfxMessageBox(strErrMsg);

        return FALSE;

    }

    return TRUE;

}

l          

下面的WriteMessageBox()函数是把MessageBoxA()的机器代码写入到PE文件中。这个函数显示的对话框标题和显示的字符串内容和长度不是固定的。在这个函数中,首先就计算MessageBoxA()函数的地址和函数的返回地址,然后把重新生成的对话框代码写入到程序中。WriteMessageBox()函数的源代码如下:

l          

BOOL CPe::WriteMessageBox(int ret,long offset,CString strCap,CString

strTxt)

{

    CString strAddress1,strAddress2;

    unsigned char waddress[4]={0};

    DWORD dwAddress;

    // 获取MessageBox在内存中的地址

    HINSTANCE gLibMsg=LoadLibrary("user32.dll");

    dwMessageBoxAadaddress=(DWORD)GetProcAddress(gLibMsg,"MessageBoxA");

    // 计算校验位

    int nLenCap1 =strCap.GetLength()+1;   // 加上字符串后面的结束位

    int nLenTxt1 =strTxt.GetLength()+1;   // 加上字符串后面的结束位

    int nTotLen=nLenCap1+nLenTxt1+24;

    // 重新计算MessageBox函数的地址

    dwAddress=dwMessageBoxAadaddress-(dwProgRAV+dwNewEntryAddress+nTot

    Len-5);

   strAddress1=StrOfDWord(dwAddress);

    // 计算返回地址

    dwAddress=0-(dwNewEntryAddress-dwOldEntryAddress+nTotLen);

    strAddress2=StrOfDWord(dwAddress);

    // 对话框头代码(固定)

    unsigned char cHeader[2]={0x6a,0x40};

   

    // 标题定义

    unsigned char cDesCap[5]={0xe8,nLenCap1,0x00,0x00,0x00};

   

    // 内容定义

    unsigned char cDesTxt[5]={0xe8,nLenTxt1,0x00,0x00,0x00};

   

    // 对话框后部分的代码段

    unsigned char cFix[12]

        ={0x6a,0x00,0xe8,0x00,0x00,0x00,0x00,0xe9,0x00,0x00,0x00,0x00};

   

    // 修改对话框后部分的代码段

    for(int i=0;i<4;i++)

        cFix[3+i]=strAddress1.GetAt(i);

    for(i=0;i<4;i++)

        cFix[8+i]=strAddress2.GetAt(i);

    char* cMessageBox=new char[nTotLen];

    char* cMsg;

    // 生成对话框命令字符串

    memcpy((cMsg  = cMessageBox),(char*)cHeader,2);

    memcpy((cMsg += 2),cDesCap,5);

    memcpy((cMsg += 5),strCap,nLenCap1);

    memcpy((cMsg += nLenCap1),cDesTxt,5);

    memcpy((cMsg += 5),strTxt,nLenTxt1);

    memcpy((cMsg += nLenTxt1),cFix,12);

    // 向应用程序写入对话框代码

    CString strErrMsg;

    long retf;

    retf=_lseek(ret,(long)dwEntryWrite,SEEK_SET);

    if(retf==-1)

    {

        delete[] cMessageBox;

        AfxMessageBox("Error seek.");

        return FALSE;

    }

    retf=_write(ret,cMessageBox,nTotLen);

    if(retf==-1)

    {

        delete[] cMessageBox;

        strErrMsg.Format("Error write: %d",GetLastError());

        AfxMessageBox(strErrMsg);

        return FALSE;

    }

    delete[] cMessageBox;

    return TRUE;

}

l          

下面的WriteFile()函数是总的写入函数。在这个函数中,先打开被修改的PE文件,然后调用WriteNewEntry()和WriteMessageBox()函数。WriteFile()函数的源代码如下:

l          

void CPe::WriteFile(CString strFileName)

{

    CString strAddress1,strAddress2;

    int ret;

    unsigned char waddress[4]={0};

   

    ret=_open(strFileName,_O_RDWR | _O_CREAT | _O_BINARY,_S_IREAD | _S_

    IWRITE);

    if(!ret)

    {

        AfxMessageBox("Error open");

        return;

    }

    // 把新的入口地址写入文件,程序的入口地址在偏移PE文件头开始第40位

    if(!WriteNewEntry(ret,(long)(dwPeAddress+40),dwNewEntryAddress))

    return;

    // 把对话框代码写入到应用程序中

   if(!WriteMessageBox(ret,(long)dwEntryWrite,"Test","We are the world!"))
return;

    _close(ret);

}

l          

仅仅利用以上CPe类还是不能对一个PE文件进行注入MessageBoxA()代码的修改,还必须要一个“载体程序”。例如:

l          

// Pefile.cpp:修改PE文件实例

//

#include "stdafx.h"

#include "Pe.h"

void main()

{

    CopyFile("..\\calc.exe","..\\calc_shell.exe",FALSE);

   

    CPe a;

    a.ModifyPe("..\\calc_shell.exe","We are the world!");

}

l          

这个修改后的PE文件运行时,就会先显示对话框,单击“确定”按钮后又继续执行。总之,在了解了PE文件格式后,就可以对某一个PE文件进行修改。本实例只是对PE文件处理的一种应用,在实际中还有更多的其他方面的应用。

2.7  本章小结
本章首先介绍了PE文件的基本结构,对一些容易混淆的名词进行了解释。通过介绍一个对PE文件注入对话框代码的实例,加强了对PE文件结构的认识。

本章所介绍的向PE文件注入代码的实例只是用来说明如何修改PE文件,有关如何向一个应用程序中注入代码的技术还要在以后的章节专门介绍。此外,还有其他的技术没有介绍,例如如何提取程序中的代码,在以后的章节中对此也还要专门介绍。总之,了解了PE文件结构,就可以很容易地对某个应用程序进行加壳、挂钩或捆绑。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/yczz/archive/2007/12/19/1954425.aspx


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/tomorrowsprogress/archive/2009/09/23/4582978.aspx

- 作者: 小浪 2009年09月29日, 星期二 16:09  回复(0) |  引用(0) 加入博采

Dll(动态链接库)学习笔记

DLL(Dynamic Link Libraries)专题:

    比较大的应用程序都由很多模块组成,这些模块分别完成相对独立的功能,它们彼此协作来完成整个软件系统的工作。可能存在一些模块的功能较为通用,在构造其它软件系统时仍会被使用。在构造软件系统时,如果将所有模块的源代码都静态编译到整个应用程序EXE文件中,会产生一些问题:一个缺点是增加了应用程序的大小,它会占用更多的磁盘空间,程序运行时也会消耗较大的内存空间,造成系统资源的浪费;另一个缺点是,在编写大的EXE程序时,在每次修改重建时都必须调整编译所有源代码,增加了编译过程的复杂性,也不利于阶段性的单元测试。

    Windows系统平台上提供了一种完全不同的较有效的编程和运行环境,你可以将独立的程序模块创建为较小的DLL(Dynamic Linkable Library)文件,并可对它们单独编译和测试。在运行时,只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将它们装载到内存空间中。这种方式不仅减少了EXE文件的大小和对内存空间的需求,而且使这些DLL模块可以同时被多个应用程序使用。Windows自己就将一些主要的系统功能以DLL模块的形式实现。

    一般来说,DLL是一种磁盘文件,以.DLL、.DRV、.FON、.SYS和许多以.EXE为扩展名的系统文件都可以是DLL。它由全局数据、服务函数和资源组成,在运行时被系统加载到进程的虚拟空间中,成为调用进程的一部分。如果与其它DLL之间没有冲突,该文件通常映射到进程虚拟空间的同一地址上。DLL模块中包含各种导出函数,用于向外界提供服务。DLL可以有自己的数据段,但没有自己的堆栈,使用与调用它的应用程序相同的堆栈模式;一个DLL在内存中只有一个实例;DLL实现了代码封装性;DLL的编制与具体的编程语言及编译器无关。

    在Win32环境中,每个进程都复制了自己的读/写全局变量。如果想要与其它进程共享内存,必须使用内存映射文件或者声明一个共享数据段。DLL模块需要的堆栈内存都是从运行进程的堆栈中分配出来的。Windows在加载DLL模块时将进程函数调用与DLL文件的导出函数相匹配。Windows操作系统对DLL的操作仅仅是把DLL映射到需要它的进程的虚拟地址空间里去。DLL函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有.       

一、关于调用方式:

1、静态调用方式:由编译系统完成对DLL的加载和应用程序结束时DLL卸载的编码(如还有其它程序使用该DLL,则Windows对DLL的应用记录减1,直到所有相关程序都结束对该DLL的使用时才释放它),简单实用,但不够灵活,只能满足一般要求。

隐式的调用:需要把产生动态连接库时产生的.LIB文件加入到应用程序的工程中,想使用DLL中的函数时,只须说明一下。隐式调用不需要调用LoadLibrary()和FreeLibrary()。程序员在建立一个DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件。该文件包含了每一个DLL导出函数的符号名和可选的标识号,但是并不含有实际的代码。LIB文件作为DLL的替代文件被编译到应用程序项目中。当程序员通过静态链接方式编译生成应用程序时,应用程序中的调用函数与LIB文件中导出符号相匹配,这些符号或标识号进入到生成的EXE文件中。LIB文件中也包含了对应的DLL文件名(但不是完全的路径名),链接程序将其存储在EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL函数的动态链接。所有被应用程序调用的DLL文件都会在应用程序EXE文件加载时被加载在到内存中。可执行程序链接到一个包含DLL输出函数信息的输入库文件(.LIB文件)。操作系统在加载使用可执行程序时加载DLL。可执行程序直接通过函数名调用DLL的输出函数,调用方法和程序内部其他的函数是一样的。


2、动态调用方式:是由编程者用API函数加载和卸载DLL来达到调用DLL的目的,使用上较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方式。

显式的调用:是指在应用程序中用LoadLibrary或MFC提供的AfxLoadLibrary显式的将自己所做的动态连接库调进来,动态连接库的文件名即是上面两个函数的参数,再用GetProcAddress()获取想要引入的函数。自此,你就可以象使用如同本应用程序自定义的函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxFreeLibrary释放动态连接库。直接调用Win32 的LoadLibary函数,并指定DLL的路径作为参数。LoadLibary返回HINSTANCE参数,应用程序在调用GetProcAddress函数时使用这一参数。GetProcAddress函数将符号名或标识号转换为DLL内部的地址。程序员可以决定DLL文件何时加载或不加载,显式链接在运行时决定加载哪个DLL文件。使用DLL的程序在使用之前必须加载(LoadLibrary)加载DLL从而得到一个DLL模块的句柄,然后调用GetProcAddress函数得到输出函数的指针,在退出之前必须卸载DLL(FreeLibrary)。

    Windows将遵循下面的搜索顺序来定位DLL:
1.包含EXE文件的目录,
2.进程的当前工作目录,
3.Windows系统目录,
4.Windows目录,
5.列在Path环境变量中的一系列目录。

二、MFC中的dll:

a、Non-MFC DLL:指的是不用MFC的类库结构,直接用C语言写的DLL,其输出的函数一般用的是标准C接口,并能被非MFC或MFC编写的应用程序所调用。

b、Regular DLL:和下述的Extension Dlls一样,是用MFC类库编写的。明显的特点是在源文件里有一个继承CWinApp的类。其又可细分成静态连接到MFC和动态连接到MFC上的。

静态连接到MFC的动态连接库只被VC的专业般和企业版所支持。该类DLL应用程序里头的输出函数可以被任意Win32程序使用,包括使用MFC的应用程序。输入函数有如下形式:
extern "C" EXPORT YourExportedFunction( );
如果没有extern “C”修饰,输出函数仅仅能从C++代码中调用。
DLL应用程序从CWinApp派生,但没有消息循环。

动态链接到MFC的规则DLL应用程序里头的输出函数可以被任意Win32程序使用,包括使用MFC的应用程序。但是,所有从DLL输出的函数应该以如下语句开始:
AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
此语句用来正确地切换MFC模块状态。

Regular DLL能够被所有支持DLL技术的语言所编写的应用程序所调用。在这种动态连接库中,它必须有一个从CWinApp继承下来的类,DllMain函数被MFC所提供,不用自己显式的写出来。

c、Extension DLL:用来实现从MFC所继承下来的类的重新利用,也就是说,用这种类型的动态连接库,可以用来输出一个从MFC所继承下来的类。它输出的函数仅可以被使用MFC且动态链接到MFC的应用程序使用。可以从MFC继承你所想要的、更适于你自己用的类,并把它提供给你的应用程序。你也可随意的给你的应用程序提供MFC或MFC继承类的对象指针。Extension DLL使用MFC的动态连接版本所创建的,并且它只被用MFC类库所编写的应用程序所调用。Extension DLLs 和Regular DLLs不一样,它没有一个从CWinApp继承而来的类的对象,所以,你必须为自己DllMain函数添加初始化代码和结束代码。

和规则DLL相比,有以下不同:

1、它没有一个从CWinApp派生的对象;
2、它必须有一个DllMain函数;
3、DllMain调用AfxInitExtensionModule函数,必须检查该函数的返回值,如果返回0,DllMmain也返回0;
4、如果它希望输出CRuntimeClass类型的对象或者资源(Resources),则需要提供一个初始化函数来创建一个CDynLinkLibrary对象。并且,有必要把初始化函数输出;
5、使用扩展DLL的MFC应用程序必须有一个从CWinApp派生的类,而且,一般在InitInstance里调用扩展DLL的初始化函数。

三、dll入口函数:

1、每一个DLL必须有一个入口点,DllMain是一个缺省的入口函数。DllMain负责初始化(Initialization)和结束(Termination)工作,每当一个新的进程或者该进程的新的线程访问DLL时,或者访问DLL的每一个进程或者线程不再使用DLL或者结束时,都会调用DllMain。但是,使用TerminateProcess或TerminateThread结束进程或者线程,不会调用DllMain。

DllMain的函数原型:
BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
.......
case DLL_THREAD_ATTACH:
.......
case DLL_THREAD_DETACH:
.......
case DLL_PROCESS_DETACH:
.......
return TRUE;
}
}

参数:
hMoudle:是动态库被调用时所传递来的一个指向自己的句柄(实际上,它是指向_DGROUP段的一个选择符);
ul_reason_for_call:是一个说明动态库被调原因的标志。当进程或线程装入或卸载动态连接库的时候,操作系统调用入口函数,并说明动态连接库被调用的原因。它所有的可能值为:
DLL_PROCESS_ATTACH: 进程被调用;
DLL_THREAD_ATTACH: 线程被调用;
DLL_PROCESS_DETACH: 进程被停止;
DLL_THREAD_DETACH: 线程被停止;
lpReserved:是一个被系统所保留的参数。

2、_DllMainCRTStartup

为了使用“C”运行库(CRT,C Run time Library)的DLL版本(多线程),一个DLL应用程序必须指定_DllMainCRTStartup为入口函数,DLL的初始化函数必须是DllMain。

_DllMainCRTStartup完成以下任务:当进程或线程捆绑(Attach)到DLL时为“C”运行时的数据(C Runtime Data)分配空间和初始化并且构造全局“C++”对象,当进程或者线程终止使用DLL(Detach)时,清理C Runtime Data并且销毁全局“C++”对象。它还调用DllMain和RawDllMain函数。

RawDllMain在DLL应用程序动态链接到MFC DLL时被需要,但它是静态的链接到DLL应用程序的。在讲述状态管理时解释其原因。

四、关于约定:

动态库输出函数的约定有两种:调用约定和名字修饰约定。

1)调用约定(Calling convention):决定函数参数传送时入栈和出栈的顺序,由调用者还是被调用者把参数弹出栈,以及编译器用来识别函数名字的修饰约定。

函数调用约定有多种,这里简单说一下:

   1、__stdcall调用约定相当于16位动态库中经常使用的PASCAL调用约定。在32位的VC++5.0中PASCAL调用约定不再被支持(实际上它已被定义为__stdcall。除了__pascal外,__fortran和__syscall也不被支持),取而代之的是__stdcall调用约定。两者实质上是一致的,即函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,但不同的是函数名的修饰部分(关于函数名的修饰部分在后面将详细说明)。

    _stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。

    2、C调用约定(即用__cdecl关键字说明)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。

    _cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。

    3、__fastcall调用约定是“人”如其名,它的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。

    _fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。   

    4、thiscall仅仅应用于“C++”成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。

    5、naked call采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。

    关键字 __stdcall、__cdecl和__fastcall可以直接加在要输出的函数前,也可以在编译环境的Setting...\C/C++ \Code Generation项选择。当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。它们对应的命令行参数分别为/Gz、/Gd和/Gr。缺省状态为/Gd,即__cdecl。

    要完全模仿PASCAL调用约定首先必须使用__stdcall调用约定,至于函数名修饰约定,可以通过其它方法模仿。还有一个值得一提的是WINAPI宏,Windows.h支持该宏,它可以将出函数翻译成适当的调用约定,在WIN32中,它被定义为__stdcall。使用WINAPI宏可以创建自己的APIs。

2)名字修饰约定

1、修饰名(Decoration name)

“C”或者“C++”函数在内部(编译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的,如在模块定义文件里头指定输出“C++”重载函数、构造函数、析构函数,又如在汇编代码里调用“C””或“C++”函数等。

修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。

2、名字修饰约定随调用约定和编译种类(C或C++)的不同而变化。函数名修饰约定随编译种类和调用约定的不同而不同,下面分别说明。

    a、C编译时函数名修饰约定规则:

__stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number

__cdecl调用约定仅在输出函数名前加上一个下划线前缀,格式为_functionname。
  
__fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式为@functionname@number。

    它们均不改变输出函数名中的字符大小写,这和PASCAL调用约定不同,PASCAL约定输出的函数名无任何修饰且全部大写。

    b、C++编译时函数名修饰约定规则:

__stdcall调用约定:
          1、以“?”标识函数名的开始,后跟函数名;
          2、函数名后面以“@@YG”标识参数表的开始,后跟参数表;
          3、参数表以代号表示:
             X--void ,
             D--char,
             E--unsigned char,
             F--short,
             H--int,
             I--unsigned int,
             J--long,
             K--unsigned long,
             M--float,
             N--double,
             _N--bool,
             ....
             PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复;
          4、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;
          5、参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。

    其格式为“?functionname@@YG*****@Z”或“?functionname@@YG*XZ”,例如
          int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z
          void Test2()                       -----“?Test2@@YGXXZ

__cdecl调用约定:
规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YA”。

__fastcall调用约定:
规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YI”。

    VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用.
   
五、关于DLL的函数:

    动态链接库中定义有两种函数:导出函数(export function)和内部函数(internal function)。导出函数可以被其它模块调用,内部函数在定义它们的DLL程序内部使用。

输出函数的方法有以下几种:

1、传统的方法

在模块定义文件的EXPORT部分指定要输入的函数或者变量。语法格式如下:
entryname[=internalname] [@ordinal[NONAME]] [DATA] [PRIVATE]

其中:

entryname是输出的函数或者数据被引用的名称;

internalname同entryname;

@ordinal表示在输出表中的顺序号(index);

NONAME仅仅在按顺序号输出时被使用(不使用entryname);

DATA表示输出的是数据项,使用DLL输出数据的程序必须声明该数据项为_declspec(dllimport)。

上述各项中,只有entryname项是必须的,其他可以省略。

对于“C”函数来说,entryname可以等同于函数名;但是对“C++”函数(成员函数、非成员函数)来说,entryname是修饰名。可以从.map映像文件中得到要输出函数的修饰名,或者使用DUMPBIN /SYMBOLS得到,然后把它们写在.def文件的输出模块。DUMPBIN是VC提供的一个工具。

如果要输出一个“C++”类,则把要输出的数据和成员的修饰名都写入.def模块定义文件。

2、在命令行输出

对链接程序LINK指定/EXPORT命令行参数,输出有关函数。

3、使用MFC提供的修饰符号_declspec(dllexport)

在要输出的函数、类、数据的声明前加上_declspec(dllexport)的修饰符,表示输出。__declspec(dllexport)在C调用约定、C编译情况下可以去掉输出函数名的下划线前缀。extern "C"使得在C++中使用C编译方式成为可能。在“C++”下定义“C”函数,需要加extern “C”关键词。用extern "C"来指明该函数使用C编译方式。输出的“C”函数可以从“C”代码里调用。
   
    例如,在一个C++文件中,有如下函数:
    extern "C" {void __declspec(dllexport) __cdecl Test(int var);}
其输出函数名为:Test

MFC提供了一些宏,就有这样的作用。

AFX_CLASS_IMPORT:__declspec(dllexport)

AFX_API_IMPORT:__declspec(dllexport)

AFX_DATA_IMPORT:__declspec(dllexport)

AFX_CLASS_EXPORT:__declspec(dllexport)

AFX_API_EXPORT:__declspec(dllexport)

AFX_DATA_EXPORT:__declspec(dllexport)

AFX_EXT_CLASS: #ifdef _AFXEXT
     AFX_CLASS_EXPORT
     #else
     AFX_CLASS_IMPORT

AFX_EXT_API:#ifdef _AFXEXT
    AFX_API_EXPORT
    #else
    AFX_API_IMPORT

AFX_EXT_DATA:#ifdef _AFXEXT
     AFX_DATA_EXPORT
     #else
     AFX_DATA_IMPORT

像AFX_EXT_CLASS这样的宏,如果用于DLL应用程序的实现中,则表示输出(因为_AFX_EXT被定义,通常是在编译器的标识参数中指定该选项/D_AFX_EXT);如果用于使用DLL的应用程序中,则表示输入(_AFX_EXT没有定义)。

要输出整个的类,对类使用_declspec(_dllexpot);要输出类的成员函数,则对该函数使用_declspec(_dllexport)。如:

class AFX_EXT_CLASS CTextDoc : public CDocument
{

}

extern "C" AFX_EXT_API void WINAPI InitMYDLL();

这几种方法中,最好采用第三种,方便好用;其次是第一种,如果按顺序号输出,调用效率会高些;最次是第二种。

六、模块定义文件(.DEF)

模块定义文件(.DEF)是一个或多个用于描述DLL属性的模块语句组成的文本文件,每个DEF文件至少必须包含以下模块定义语句:

* 第一个语句必须是LIBRARY语句,指出DLL的名字;
* EXPORTS语句列出被导出函数的名字;将要输出的函数修饰名罗列在EXPORTS之下,这个名字必须与定义函数的名字完全一致,如此就得到一个没有任何修饰的函数名了。
* 可以使用DESCRIPTION语句描述DLL的用途(此句可选);
* ";"对一行进行注释(可选)。

七、DLL程序和调用其输出函数的程序的关系

1、dll与进程、线程之间的关系

DLL模块被映射到调用它的进程的虚拟地址空间。
DLL使用的内存从调用进程的虚拟地址空间分配,只能被该进程的线程所访问。
DLL的句柄可以被调用进程使用;调用进程的句柄可以被DLL使用。
DLL使用调用进程的栈。

2、关于共享数据段

DLL定义的全局变量可以被调用进程访问;DLL可以访问调用进程的全局数据。使用同一DLL的每一个进程都有自己的DLL全局变量实例。如果多个线程并发访问同一变量,则需要使用同步机制;对一个DLL的变量,如果希望每个使用DLL的线程都有自己的值,则应该使用线程局部存储(TLS,Thread Local Strorage)。

    在程序里加入预编译指令,或在开发环境的项目设置里也可以达到设置数据段属性的目的.必须给这些变量赋初值,否则编译器会把没有赋初始值的变量放在一个叫未被初始化的数据段中。

- 作者: 小浪 2009年09月29日, 星期二 16:08  回复(0) |  引用(0) 加入博采

关于char, wchar_t, TCHAR, _T(),L,宏 _T、TEXT,_TEXT、L

http://www.cnblogs.com/wanghao111/archive/2009/05/25/1488816.html

char :单字节变量类型,最多表示256个字符,

wchar_t :宽字节变量类型,用于表示Unicode字符,

它实际定义在<string.h>里:typedef unsigned short wchar_t。

为了让编译器识别Unicode字符串,必须以在前面加一个“L”,定义宽字节类型方法如下:

    wchar_t c = `A' ;
wchar_t * p = L"Hello!" ;
wchar_t a[] = L"Hello!" ;

其中,宽字节类型每个变量占用2个字节,故上述数组a的sizeof(a) = 14

TCHAR / _T( ) :
如果在程序中既包括ANSI又包括Unicode编码,需要包括头文件tchar.h。TCHAR是定义在该头文件中的宏,它视你是否定义了_UNICODE宏而定义成:
定义了_UNICODE:    typedef wchar_t TCHAR ;
没有定义_UNICODE: typedef char TCHAR ;

#ifdef UNICODE
typedef char TCHAR;
#else
typede wchar_t TCHAR;
#endif
_T( )也是定义在该头文件中的宏,视是否定义了_UNICODE宏而定义成:
定义了_UNICODE:    #define _T(x) L##x
没有定义_UNICODE: #define _T(x) x
注意:如果在程序中使用了TCHAR,那么就不应该使用ANSI的strXXX函数或者Unicode的wcsXXX函数了,而必须使用tchar.h中定义的_tcsXXX函数。

以strcpy函数为例子,总结一下:

关于char, wchar_t, TCHAR, _T(),L,宏 _T、TEXT,_TEXT、L  - 纷飞 - 小窝儿
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->//如果你想使用ANSI字符串,那么请使用这一套写法: 
char szString[100]; 
strcpy(szString,
"test"); 
//如果你想使用Unicode字符串,那么请使用这一套: 
wchar_t szString[100]; 
wcscpy(szString,L
"test"); 
//如果你想通过定义_UNICODE宏,而编译ANSI或者Unicode字符串代码: 
TCHAR szString[100]; 
_tcscpy(szString,_TEXT(
"test"));

CSDN:superarhow说: 不要再使用TCHAR和_T了!他分析了原因后总结:如 果您正开始一个新的项目,请无论如何也要顶住压力,直接使用UNICODE编码!切记!您只需要对您的组员进行10分钟的培训,记住strcpy用 wcscpy,sprintf用swprintf代替,常数前加L,就可以了!它不会花您很多时间的,带给您的是稳定和安全!相信偶,没错的!!

一、 在字符串前加一个L作用:
   如  L"我的字符串"    表示将ANSI字符串转换成unicode的字符串,就是每个字符占用两个字节。
  strlen("asd")   =   3;  
  strlen(L"asd")   =   6;
  二、  _T宏可以把一个引号引起来的字符串,根据你的环境设置,使得编译器会根据编译目标环境选择合适的(Unicode还是ANSI)字符处理方式
   如果你定义了UNICODE,那么_T宏会把字符串前面加一个L。这时 _T("ABCD") 相当于 L"ABCD" ,这是宽字符串。
   如果没有定义,那么_T宏不会在字符串前面加那个L,_T("ABCD") 就等价于 "ABCD"
三、TEXT,_TEXT 和_T 一样的
如下面三语句:  
  TCHAR   szStr1[]   =   TEXT("str1");  
  char   szStr2[]   =   "str2";  
  WCHAR   szStr3[]   =   L("str3");  
  那么第一句话在定义了UNICODE时会解释为第三句话,没有定义时就等于第二句话。  
  但二句话无论是否定义了UNICODE都是生成一个ANSI字符串,而第三句话总是生成UNICODE字符串。  
  为了程序的可移植性,建议都用第一种表示方法。  
  但在某些情况下,某个字符必须为ANSI或UNICODE,那就用后两种方法。

- 作者: 小浪 2009年09月14日, 星期一 22:40  回复(0) |  引用(0) 加入博采

SQLite3 C/C++ 开发接口简介(API函数)

1.0 总览
       SQLite3是SQLite一个全新的版本,它虽然是在SQLite 2.8.13的代码基础之上开发的,但是使用了和之前的版本不兼容的数据库格式和API. SQLite3是为了满足以下的需求而开发的:

支持UTF-16编码.
用户自定义的文本排序方法.
可以对BLOBs字段建立索引.
因此为了支持这些特性我改变了数据库的格式,建立了一个与之前版本不兼容的3.0版. 至于其他的兼容性的改变,例如全新的API等等,都将在理论介绍之后向你说明,这样可以使你最快的一次性摆脱兼容性问题.

3.0版的和2.X版的API非常相似,但是有一些重要的改变需要注意. 所有API接口函数和数据结构的前缀都由"sqlite_"改为了"sqlite3_". 这是为了避免同时使用SQLite 2.X和SQLite 3.0这两个版本的时候发生链接冲突.

由于对于C语言应该用什么数据类型来存放UTF-16编码的字符串并没有一致的规范. 因此SQLite使用了普通的void* 类型来指向UTF-16编码的字符串. 客户端使用过程中可以把void*映射成适合他们的系统的任何数据类型.

2.0 C/C++ 接口
SQLite 3.0一共有83个API函数,此外还有一些数据结构和预定义(#defines). (完整的API介绍请参看另一份文档.) 不过你们可以放心,这些接口使用起来不会像它的数量所暗示的那么复杂. 最简单的程序仍然使用三个函数就可以完成: sqlite3_open(), sqlite3_exec(), 和 sqlite3_close(). 要是想更好的控制数据库引擎的执行,可以使用提供的sqlite3_prepare()函数把SQL语句编译成字节码,然后在使用sqlite3_step()函数来执行编译后的字节码. 以sqlite3_column_开头的一组API函数用来获取查询结果集中的信息. 许多接口函数都是成对出现的,同时有UTF-8和UTF-16两个版本. 并且提供了一组函数用来执行用户自定义的SQL函数和文本排序函数.

2.1 如何打开关闭数据库
   typedef struct sqlite3 sqlite3;
   int sqlite3_open(const char*, sqlite3**);
   int sqlite3_open16(const void*, sqlite3**);
   int sqlite3_close(sqlite3*);
   const char *sqlite3_errmsg(sqlite3*);
   const void *sqlite3_errmsg16(sqlite3*);
   int sqlite3_errcode(sqlite3*);
sqlite3_open() 函数返回一个整数错误代码,而不是像第二版中一样返回一个指向sqlite3结构体的指针. sqlite3_open() 和 sqlite3_open16() 的不同之处在于sqlite3_open16() 使用UTF-16编码(使用本地主机字节顺序)传递数据库文件名. 如果要创建新数据库, sqlite3_open16() 将内部文本转换为UTF-16编码, 反之sqlite3_open() 将文本转换为UTF-8编码.

打开或者创建数据库的命令会被缓存,直到这个数据库真正被调用的时候才会被执行. 而且允许使用PRAGMA声明来设置如本地文本编码或默认内存页面大小等选项和参数.

sqlite3_errcode() 通常用来获取最近调用的API接口返回的错误代码. sqlite3_errmsg() 则用来得到这些错误代码所对应的文字说明. 这些错误信息将以 UTF-8 的编码返回,并且在下一次调用任何SQLite API函数的时候被清除. sqlite3_errmsg16() 和 sqlite3_errmsg() 大体上相同,除了返回的错误信息将以 UTF-16 本机字节顺序编码.

SQLite3的错误代码相比SQLite2没有任何的改变,它们分别是:

#define SQLITE_OK           0   /* Successful result */
#define SQLITE_ERROR        1   /* SQL error or missing database */
#define SQLITE_INTERNAL     2   /* An internal logic error in SQLite */
#define SQLITE_PERM         3   /* Access permission denied */
#define SQLITE_ABORT        4   /* Callback routine requested an abort */
#define SQLITE_BUSY         5   /* The database file is locked */
#define SQLITE_LOCKED       6   /* A table in the database is locked */
#define SQLITE_NOMEM        7   /* A malloc() failed */
#define SQLITE_READONLY     8   /* Attempt to write a readonly database */
#define SQLITE_INTERRUPT    9   /* Operation terminated by sqlite_interrupt() */
#define SQLITE_IOERR       10   /* Some kind of disk I/O error occurred */
#define SQLITE_CORRUPT     11   /* The database disk image is malformed */
#define SQLITE_NOTFOUND    12   /* (Internal Only) Table or record not found */
#define SQLITE_FULL        13   /* Insertion failed because database is full */
#define SQLITE_CANTOPEN    14   /* Unable to open the database file */
#define SQLITE_PROTOCOL    15   /* Database lock protocol error */
#define SQLITE_EMPTY       16   /* (Internal Only) Database table is empty */
#define SQLITE_SCHEMA      17   /* The database schema changed */
#define SQLITE_TOOBIG      18   /* Too much data for one row of a table */
#define SQLITE_CONSTRAINT  19   /* Abort due to contraint violation */
#define SQLITE_MISMATCH    20   /* Data type mismatch */
#define SQLITE_MISUSE      21   /* Library used incorrectly */
#define SQLITE_NOLFS       22   /* Uses OS features not supported on host */
#define SQLITE_AUTH        23   /* Authorization denied */
#define SQLITE_ROW         100  /* sqlite_step() has another row ready */
#define SQLITE_DONE        101  /* sqlite_step() has finished executing */
2.2 执行 SQL 语句

       typedef int (*sqlite_callback)(void*,int,char**, char**);
       int sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void*, char**);

sqlite3_exec 函数依然像它在SQLite2中一样承担着很多的工作. 该函数的第二个参数中可以编译和执行零个或多个SQL语句. 查询的结果返回给回调函数. 更多地信息可以查看API 参考.

在SQLite3里,sqlite3_exec一般是被准备SQL语句接口封装起来使用的.

       typedef struct sqlite3_stmt sqlite3_stmt;
       int sqlite3_prepare(sqlite3*, const char*, int, sqlite3_stmt**, const char**);
       int sqlite3_prepare16(sqlite3*, const void*, int, sqlite3_stmt**, const void**);
       int sqlite3_finalize(sqlite3_stmt*);
       int sqlite3_reset(sqlite3_stmt*);

sqlite3_prepare 接口把一条SQL语句编译成字节码留给后面的执行函数. 使用该接口访问数据库是当前比较好的的一种方法.

sqlite3_prepare() 处理的SQL语句应该是UTF-8编码的. 而sqlite3_prepare16() 则要求是UTF-16编码的. 输入的参数中只有第一个SQL语句会被编译. 第四个参数则用来指向输入参数中下一个需要编译的SQL语句存放的SQLite statement对象的指针, 任何时候如果调用 sqlite3_finalize() 将销毁一个准备好的SQL声明. 在数据库关闭之前,所有准备好的声明都必须被释放销毁. sqlite3_reset() 函数用来重置一个SQL声明的状态,使得它可以被再次执行.

SQL声明可以包含一些型如"?" 或 "?nnn" 或 ":aaa"的标记, 其中"nnn" 是一个整数,"aaa" 是一个字符串. 这些标记代表一些不确定的字符值(或者说是通配符),可以在后面用sqlite3_bind 接口来填充这些值. 每一个通配符都被分配了一个编号(由它在SQL声明中的位置决定,从1开始),此外也可以用 "nnn" 来表示 "?nnn" 这种情况. 允许相同的通配符在同一个SQL声明中出现多次, 在这种情况下所有相同的通配符都会被替换成相同的值. 没有被绑定的通配符将自动取NULL值.

       int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
       int sqlite3_bind_double(sqlite3_stmt*, int, double);
       int sqlite3_bind_int(sqlite3_stmt*, int, int);
       int sqlite3_bind_int64(sqlite3_stmt*, int, long long int);
       int sqlite3_bind_null(sqlite3_stmt*, int);
       int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*));
       int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
       int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);

以上是 sqlite3_bind 所包含的全部接口,它们是用来给SQL声明中的通配符赋值的. 没有绑定的通配符则被认为是空值. 绑定上的值不会被sqlite3_reset()函数重置. 但是在调用了sqlite3_reset()之后所有的通配符都可以被重新赋值.

在SQL声明准备好之后(其中绑定的步骤是可选的), 需要调用以下的方法来执行:

       int sqlite3_step(sqlite3_stmt*);

如果SQL返回了一个单行结果集,sqlite3_step() 函数将返回 SQLITE_ROW , 如果SQL语句执行成功或者正常将返回 SQLITE_DONE , 否则将返回错误代码. 如果不能打开数据库文件则会返回 SQLITE_BUSY . 如果函数的返回值是 SQLITE_ROW, 那么下边的这些方法可以用来获得记录集行中的数据:

       const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
       int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
       int sqlite3_column_bytes16(sqlite3_stmt*, int iCol);
       int sqlite3_column_count(sqlite3_stmt*);
       const char *sqlite3_column_decltype(sqlite3_stmt *, int iCol);
       const void *sqlite3_column_decltype16(sqlite3_stmt *, int iCol);
       double sqlite3_column_double(sqlite3_stmt*, int iCol);
       int sqlite3_column_int(sqlite3_stmt*, int iCol);
       long long int sqlite3_column_int64(sqlite3_stmt*, int iCol);
       const char *sqlite3_column_name(sqlite3_stmt*, int iCol);
       const void *sqlite3_column_name16(sqlite3_stmt*, int iCol);
       const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
       const void *sqlite3_column_text16(sqlite3_stmt*, int iCol);
       int sqlite3_column_type(sqlite3_stmt*, int iCol);

sqlite3_column_count()函数返回结果集中包含的列数. sqlite3_column_count() 可以在执行了 sqlite3_prepare()之后的任何时刻调用. sqlite3_data_count()除了必需要在sqlite3_step()之后调用之外,其他跟sqlite3_column_count() 大同小异. 如果调用sqlite3_step() 返回值是 SQLITE_DONE 或者一个错误代码, 则此时调用sqlite3_data_count() 将返回 0 ,然而 sqlite3_column_count() 仍然会返回结果集中包含的列数.

返回的记录集通过使用其它的几个 sqlite3_column_***() 函数来提取, 所有的这些函数都把列的编号作为第二个参数. 列编号从左到右以零起始. 请注意它和之前那些从1起始的参数的不同.

sqlite3_column_type()函数返回第N列的值的数据类型. 具体的返回值如下:

       #define SQLITE_INTEGER  1
       #define SQLITE_FLOAT    2
       #define SQLITE_TEXT     3
       #define SQLITE_BLOB     4
       #define SQLITE_NULL     5

sqlite3_column_decltype() 则用来返回该列在 CREATE TABLE 语句中声明的类型. 它可以用在当返回类型是空字符串的时候. sqlite3_column_name() 返回第N列的字段名. sqlite3_column_bytes() 用来返回 UTF-8 编码的BLOBs列的字节数或者TEXT字符串的字节数. sqlite3_column_bytes16() 对于BLOBs列返回同样的结果,但是对于TEXT字符串则按 UTF-16 的编码来计算字节数. sqlite3_column_blob() 返回 BLOB 数据. sqlite3_column_text() 返回 UTF-8 编码的 TEXT 数据. sqlite3_column_text16() 返回 UTF-16 编码的 TEXT 数据. sqlite3_column_int() 以本地主机的整数格式返回一个整数值. sqlite3_column_int64() 返回一个64位的整数. 最后, sqlite3_column_double() 返回浮点数.

不一定非要按照sqlite3_column_type()接口返回的数据类型来获取数据. 数据类型不同时软件将自动转换.

- 作者: 小浪 2009年07月7日, 星期二 11:28  回复(0) |  引用(0) 加入博采

Sqlite3简单介绍与一些常用的例子

1:常用接口

  个人比较喜欢sqlite, 使用最方便,唯一的准备工作是下载250K的源;而且作者很热心,有问必答。

以下演示一下使用sqlite的步骤,先创建一个数据库,然后查询其中的内容。2个重要结构体和5个主要函数:

sqlite3               *pdb, 数据库句柄,跟文件句柄FILE很类似

sqlite3_stmt      *stmt, 这个相当于ODBC的Command对象,用于保存编译好的SQL语句

 

sqlite3_open(),   打开数据库

sqlite3_exec(),   执行非查询的sql语句

sqlite3_prepare(), 准备sql语句,执行select语句或者要使用parameter bind时,用这个函数(封装了sqlite3_exec).

Sqlite3_step(), 在调用sqlite3_prepare后,使用这个函数在记录集中移动。

Sqlite3_close(), 关闭数据库文件

 

还有一系列的函数,用于从记录集字段中获取数据,如

sqlite3_column_text(), 取text类型的数据。

sqlite3_column_blob(),取blob类型的数据

sqlite3_column_int(), 取int类型的数据

 2:sqlite数据类型介绍

     在进行数据库Sql操作之前,首先有个问题需要说明,就是Sqlite的数据类型,和其他的数据库不同,Sqlite支持的数据类型有他自己的特色,这个特色有时会被认为是一个潜在的缺点,但是这个问题并不在我们的讨论范围之内。
大多数的数据库在数据类型上都有严格的限制,在建立表的时候,每一列都必须制定一个数据类型,只有符合该数据类型的数据可以被保存在这一列当中。而在Sqlite 2.X中,数据类型这个属性只属于数据本生,而不和数据被存在哪一列有关,也就是说数据的类型并不受数据列限制(有一个例外:INTEGER PRIMARY KEY,该列只能存整型数据)。
但是当Sqlite进入到3.0版本的时候,这个问题似乎又有了新的答案,Sqlite的开发者开始限制这种无类型的使用,在3.0版本当中,每一列开始拥有自己的类型,并且在数据存入该列的时候,数据库会试图把数据的类型向该类型转换,然后以转换之后的类型存储。当然,如果转换被认为是不可行的,Sqlite仍然会存储这个数据,就像他的前任版本一样。
举个例子,如果你企图向一个INTEGER类型的列中插入一个字符串,Sqlite会检查这个字符串是否有整型数据的特征, 如果有而且可以被数据库所识别,那么该字符串会被转换成整型再保存,如果不行,则还是作为整型存储。

总的来说,所有存在Sqlite 3.0版本当中的数据都拥有以下之一的数据类型:
空(NULL):该值为空
整型(INTEGEER):有符号整数,按大小被存储成1,2,3,4,6或8字节。
实数(REAL):浮点数,以8字节指数形式存储。
文本(TEXT):字符串,以数据库编码方式存储(UTF-8, UTF-16BE 或者 UTF-16-LE)。
BLOB:BLOB数据不做任何转换,以输入形式存储。

ps: 在关系数据库中,CLOB和BLOB类型被用来存放大对象。BOLB表示二进制大对象,这种数据类型通过用来保存图片,图象,视频等。CLOB表示字符大对象,能够存放大量基于字符的数据。

对应的,对于数据列,同样有以下的数据类型:

TEXT
NUMERIC
INTEGER
REAL
NONE
数据列的属性的作用是确定对插入的数据的转换方向:

TEXT 将数据向文本进行转换,对应的数据类型为NULL,TEXT 或 BLOB
NUMERIC 将数据向数字进行转换,对应的数据类型可能为所有的五类数据,当试图存入文本 时将执行向整型或浮点类型的转换(视具体的数值而定),转换若不可行,则保留文本类型存储,NULL或BLOB不做变化
INTEGER 将数据向整型转换,类似于NUMERIC,不同的是没有浮点标志的浮点数将转换为整型保存
REAL 将数据向浮点数类型转换,类似于NUMERIC,不同的是整数将转换为浮点数保存
NULL 不做任何转换的数据列类型
 

实例代码如下,

附件工程可直接编译,例子使用了blob数据类型。

#include "sqlite3.h"                                //包含一个头文件就可以使用所以sqlite的接口了

#include "stdlib.h"

#include "stdio.h"

#include "string.h"

 

#pragma comment(lib, "sqlite.lib")           //我把sqlite编译成了一个静态的lib文件。

 

void       createdb();

void       querydb();

 

int         main()

{

            createdb();

            querydb();

 

            return 0;

}

 

void       createdb()

{

            int                     ret;

            sqlite3               *pdb = 0;

            sqlite3_stmt      *stmt = 0;

            char                  *error = 0;

            char                  *sql = "insert into table1 values('value11',:aaa)";

            int                     index;

            static void          *value = "asdfadsfasdfjasdfjaksdfaskjdfakdsfaksfja";

 

            ret = sqlite3_open("db1.sdb", &pdb);                    //打开数据库,跟打开文本文件一样

            if( ret != SQLITE_OK )

                        return;

            ret = sqlite3_exec(pdb, "create table table1(col1 char(20), col2 BLOB)", 0,0, &error );

            if( ret != SQLITE_OK )

                        return;

 

            ret = sqlite3_prepare(pdb, sql,strlen(sql), &stmt, &error);

            if( ret != SQLITE_OK )

                        return;

 

            index = sqlite3_bind_parameter_index(stmt, ":aaa");

 

            ret = sqlite3_bind_blob(stmt, index, value, strlen(value), SQLITE_STATIC);

            if( ret != SQLITE_OK )

                        return;

 

            ret = sqlite3_step(stmt);

           

            if( ret != SQLITE_DONE )

                        return;

 

            sqlite3_close(pdb);        

}

 

void       querydb()

{

            int                     ret;

            sqlite3   *pdb = 0;

            sqlite3_stmt      *pstmt = 0;

            char      *error = 0;

            char      *sql = "select * from table1";

            int                     len;

            int                     i;

            char      *name;

            void       *value;

 

            ret = sqlite3_open("db1.sdb", &pdb);

            if( ret != SQLITE_OK )

                        return;

 

            ret = sqlite3_prepare(pdb, sql, strlen(sql), &pstmt, &error);

            if( ret != SQLITE_OK )

                        return;

 

            while( 1 )

            {

                        ret = sqlite3_step(pstmt);

                        if( ret != SQLITE_ROW )

                                    break;

 

                        name = sqlite3_column_text(pstmt, 0);

                        value = sqlite3_column_blob(pstmt, 1);

                        len = sqlite3_column_bytes(pstmt,1 );

            }

}

 

 

实例二:SQLite中如何用api操作blob类型的字段

 

   在实际的编程开发当中我们经常要处理一些大容量二进制数据的存储,如图片或者音乐等等。对于这些二进制数据(blob字段)我们不能像处理普通的文本那样简单的插入或者查询,为此SQLite提供了一组函数来处理这种BLOB字段类型。下面的代码演示了如何使用这些API函数。

 

首先我们要建立一个数据库:

sqlite3_exec(db, "CREATE TABLE list (fliename varchar(128) UNIQUE, fzip blob);", 0, 0, &zErrMsg);

 

//由于mmmm.rar是一个二进制文件,所以要在使用insert语句时先用?号代替

     sqlite3_prepare(db, "insert into list values ('mmmm.rar',?);", -1, &stat, 0);

    

     FILE *fp;

     long filesize = 0;

     char * ffile;

 

     fp = fopen("mmmm.rar", "rb");

 

     if(fp != NULL)

     {

         //计算文件的大小

         fseek(fp, 0, SEEK_END);

         filesize = ftell(fp);

         fseek(fp, 0, SEEK_SET);

 

         //读取文件

         ffile = new char[filesize+1];

         size_t sz = fread(ffile, sizeof(char), filesize+1, fp);

             

         fclose(fp);

     }

 

     //将文件数据绑定到insert语句中,替换“?”部分

     sqlite3_bind_blob(stat, 1, ffile, filesize, NULL);

    

//执行绑定之后的SQL语句

     sqlite3_step(stat);

这时数据库当中已经有了一条包含BLOB字段的数据。接下来我们要读取这条数据:

    

     //选取该条数据

     sqlite3_prepare(db, "select * from list;", -1, &stat, 0);

     sqlite3_step(stat);

//得到纪录中的BLOB字段

const void * test = sqlite3_column_blob(stat, 1);

//得到字段中数据的长度

     int size = sqlite3_column_bytes(stat, 1);

    

     //拷贝该字段

     sprintf(buffer2, "%s", test);

此时可以将buffer2写入到文件当中,至此BLOB数据处理完毕。

 

实例三:sqlite 中用blob存储图片和取出图片

 

#include<iostream>
#include<string>
#include<sqlite3.h>

using namespace std;

int main()
{
        sqlite3 *db;
 sqlite3_stmt *stat;
 char *zErrMsg = 0;

 char buffer2[1024]="0";


 sqlite3_open("./MetaInfo.db", &db);
    int result;

     if(result)
      {
         cout<<"Open the database sqlite.db failed"<<endl;
       }
  
     else
          cout<<"Open the database sqlite.db sucessfully"<<endl;

 sqlite3_exec(db, "CREATE TABLE list (fliename varchar(128) UNIQUE, fzip blob);", 0, 0, &zErrMsg);
 sqlite3_prepare(db, "insert into list values ('./data/2.bmp',?);", -1, &stat, 0);
 
 FILE *fp;
 long filesize = 0;
 char * ffile;

 fp = fopen("./data/2.bmp", "rb");

 if(fp != NULL)
 {
  fseek(fp, 0, SEEK_END);
  filesize = ftell(fp);
  fseek(fp, 0, SEEK_SET);

  ffile = new char[filesize+1];
  size_t sz = fread(ffile, sizeof(char), filesize+1, fp);
  
  fclose(fp);
 }

 sqlite3_bind_blob(stat, 1, ffile, filesize, NULL);
 sqlite3_step(stat);

 sqlite3_prepare(db, "select * from list;", -1, &stat, 0);
 sqlite3_step(stat);

 const void * test = sqlite3_column_blob(stat, 1);
 int size = sqlite3_column_bytes(stat, 1);

 sprintf(buffer2, "%s", test);

 FILE *fp2;

 fp2 = fopen("outfile.png", "wb");

 if(fp2 != NULL)
 {
  size_t ret = fwrite(test, sizeof(char), size, fp2);
  fclose(fp2);
 }

 delete(ffile);
 sqlite3_finalize(stat);
 sqlite3_close(db);
   return 0;
}

- 作者: 小浪 2009年07月7日, 星期二 11:25  回复(0) |  引用(0) 加入博采

windows下的makefile教程

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/mirror_hc/archive/2008/03/26/2221117.aspx

先说几句废话
  以前看书时经常遇到makefile,nmake这几个名词,然后随之而来的就是一大段莫名其妙的代码,把我看得云里雾里的。在图书馆和google上搜了半天,也只能找到一些零零星星的资料,把我一直郁闷得不行。最近因缘巧合,被我搞到了一份传说中的MASM6手册,终于揭开了NMAKE的庐山真面目。想到那些可能正遭受着同样苦难的同志以及那些看到E文就头晕的兄弟,所以就写了这篇文章。假如大家觉得有帮助的话,记得回复一下,当作鼓励!如果觉得很白痴,也请扔几个鸡蛋.本文是总结加翻译,对于一些关键词以及一些不是很确定的句子,保留了英文原版,然后再在括号里给出自己的理解以作参考。由于水平有限,加上使用NMAKE的经验尚浅,有不对的地方大家记得要指正唷。MASM6手册在AOGO(好像是)可以download,在我的BLOG上有到那的链接。
  关于NMAKE
  Microsoft Program Maintenance Utility,外号NMAKE,顾名思义,是用来管理程序的工具。其实说白了,就是一个解释程序。它处理一种叫做makefile的文件(以mak为后缀),解释里面的语句并执行相应的指令。我们编写makefile文件,按照规定的语法描述文件之间的依赖关系,以及与该依赖关系相关联的一系列操作。然后在调用NMAKE时,它会检查所有相关的文件,如果目标文件(target file,下文简称target,即依赖于其它文件的文件)的time stamp(就是文件最后一次被修改的时间,一个32位数,表示距离1980年以来经过的时间,以2秒为单位)小于依赖文件(dependent file,下文简称dependent,即被依赖的文件)的time stamp,NMAKE就执行与该依赖关系相关联的操作。请看下面这个例子:
  foo.exe : first.obj second.obj
  link first.obj,second.obj
  第一行定义了依赖关系,称为dependency line;第二行给出了与该依赖关系相关联的操作,称为command line。因为foo.exe由first.obj和second.obj连接而成,所以说foo.exe依赖于first.ogj和second.obj,即foo.exe为target,first.obj和second.obj为dependent。如果first.obj和second.obj中的任何一个被修改了(其time stamp更大),则调用link.exe,重新连接生成foo.exe。这就是NMAKE的执行逻辑。
  综上,NMAKE的核心就是这3个家伙——依赖关系,操作和判定逻辑(target.timestamp < dependent.timestamp,如果为true,就执行相应操作)。
  MAKEFILE的语法
  现在详细讨论一下makefile的语法。makefile就像一个玩具型的程序语言,麻雀虽小,但五脏具全。makefile的组成部分包括:描述语句(description block),inference rules(推导规则),宏和指令(directive)。描述语句就是dependent lines和command lines的组合;inference rules就是预先定义好的或用户自己定义的依赖关系和关联命令;宏就不用说了吧;指令就是内定的一些可以被NMAKE识别的控制命令,提供了很多有用的功能。
  另外,makefile中使用以下几个具有特殊意义的符号:
  ^ # \ ( ) { } ! @ - : ; $
  ^(caret):用于关闭某些字符所具有的特殊意义,使其只表示字面上的意义。例如:^#abc表示#abc这个字符串,而#abc则用于在makefile中加入注释,#在这里为注释标志,就像C++中的//。另外,在一行的末尾加上^,可以使行尾的回车换行符成为字串的一部分。
  #(number sign):为注释标志,NMAKE会忽略所有从#开始到下一个换行符之间的所有文本。这里要注意的是:在command lines中不能存在注释。因为对于command lines,NMAKE是将其整行传递给OS的。通常对于command lines的注释都是放在行与行之间。
  \(backslash):用于将两行合并为一行。将其放在行尾,NMAKE就会将行尾的回车换行符解释为空格(space)。
  %(percent symbol):表示其后的字符串为一文件名。用法较复杂,在讲dependent lines的时候再详细讨论。
  !(exclamation symbol):命令修饰符,在下面会有详细的讨论。
  @(at sign):命令修饰符,在下面会有详细的讨论。
  :(colon):用于dependent lines和inference rules中,用于分隔target和dependent。
  ;(semicolon):如果对于一个dependent line只有一条命令,则可以将该命令放在dependent line的后面,二者之间用“;”分隔。
  $(dolor sign):用于调用宏,在下面讲宏的时候再详细讨论。
  在makefile中还可以使用DOS通配符(wildcard)来描述文件:*和?。作用相信大家都很熟悉了,在此就不再浪费口水了。
  如果要将中间有空格或制表符的字符串作为整体对待,则应该用双引号将之括起来,例如,在指定一个中间有空格的长文件名的时候:
  “My Document”
  或在定义一个宏的时候:
   MYMACRO=”copy a:\foo.exe c:\”
  描述语句块(Description Blocks)
   描述语句块为makefile主体的基本组成单元,其典型结构如下:
   target : dependents
   commands block
  Dependent Line
   每一个描述语句块中只有一个dependent line,其定义了一个依赖关系。该行的开头不能有任何空白(空格或制表符)。冒号两边的target和dependent都可以有多个,之间以空格分隔。NMAKE在分析makefile时首先会从头到尾扫描每一个dependent line,然后根据依赖关系建立起一棵依赖关系树(dependent tree)。例如对于依赖关系:
   foo.exe : first.obj second.obj
   first.obj : first.cpp
   second.obj : second.cpp
  则在其依赖关系树中,foo.exe为first.obj和second.obj的父亲,而first.obj则是first.cpp的父亲,second.obj是second.cpp的父亲。如果second.cpp被更新了,则second.obj会被重新构造,从而导致foo.exe被重新构造。NMAKE就是这样由下而上地对整棵树中的结点进行评估的。
   虽然makefile中可以有很多的dependent lines,但NMAKE只会构造出现在它的命令行中的targets,或者,如果命令行中没有给出targets,就构造第一个dependent line中的第一个target。其他所有无关的targets都不会被构造。例如:
   foo1.exe foo2.exe : first.obj
   first.obj : first.cpp
   second.obj : second.cpp
  假设上面的第一行语句为makefile中出现的第一个dependent line,且命令行中没有给出target。当first.cpp被更新后,first.obj和foo1.exe都会被重新构造,而foo2.exe和second.obj则不会。
   当在一个dependent line中出现多个target时,例如:
   boy.exe girl.exe : first.obj
   echo Hello
   该语句相当于:
   boy.exe : first.obj
   echo Hello
   girl.exe : first.obj
   echo Hello
  (注:echo是一条控制台命令,用于在STDOUT上显示一行信息)
   同一个target也可以出现在多个dependent lines中。在这种情况下,如果只有一个dependent line后跟有command line,则它们会被合并为一个描述语句块,例如:
   foo.exe : first.obj
   echo Building foo.exe…
   …
   foo.exe : second.obj
  NMAKE会将其处理为:
   foo.exe : first.obj second.obj
   echo Building foo.exe…
   如果每一个dependent line后都有command line,则它们会被作为两个描述语句块处理。
  如果在dependent line中使用双冒号(::)来分隔target和dependent,并且同一个target出现在多个描述语句块中,此时,NMAKE将会匹配最合适的语句块,以构造该target。
  例如:
   target.lib :: one.asm two.asm three.asm
  ML one.asm two.asm three.asm
  LIB target -+one.obj -+two.obj -+three.obj;
  target.lib :: four.c five.c
  CL /c four.c five.c
  LIB target -+four.obj -+five.obj;
  Target.lib同时出现在两个描述语句块中,此时,NMAKE在处理该makefile时,将会选择其中一个描述语句块中的命令来执行。如果任何asm文件被更新了,NMAKE就调用ML重新编译之,然后再调用LIB(但CL以及之后的命令都不会被调用);类似地,如果任何C文件被更新了,NMAKE就会调用CL。
   在通常情况下,target和dependent都是文件名。NMAKE会首先在当前目录下搜索dependent,如果没有找到,就到用户指定的目录下搜索。指定搜索路径的语法如下:
   {directory1;directory2;…}dependent
  搜索路径放在{}之中,如果有多个,就用“;”分开。注意,在各个语法成分之间是不能有空白的。
   Target和dependent也可以不是一个文件,而是一个标号(label)。这时,就称之为pseudotarget(伪文件)。Pseudotarget的名字不能与当前目录下的任何文件名相同。一个pseudotarget如果要作为dependent,那么它必须要作为target出现在某个dependent line中。当使用pseudotarget作为target时,与之关联的commands block一定会被执行,同时NMAKE会赋予它一个假想的time stamp。该time stamp等于它的dependents中最大的time stamp,或者,如果它没有dependent,就等于当前时间。该假想的time stamp在pseudotarget作为dependent时会被用来进行有效性评估。这个特性最大的好处就是,你可以让NMAKE构造多个target,而不用将每个target都在NMAKE的命令行中列出来,例如:
   all : setenv project1.exe project2.exe
  project1.exe : project1.obj
  LINK project1;
  project2.exe : project2.obj
  LINK project2;
  setenv :
  set LIB=\project\lib
  上例中有两个pseudotarget,一个是all,另一个是setenv。首先是setenv被评估,其作用是设置环境变量LIB,然后project1.exe和project2.exe依次被更新.
  Commands Block
   第二行开始到下一个dependent line之间为commands block,其给出了当dependents中的任何一个的time stamp大于target时,需要执行的指令序列(commadns block也可以为空,此时,NMAKE什么也不干)。command line必须以空白开头(刚好与dependent line相反,NMAKE就是通过该特征来分辨二者的),并且在dependent line和commands block中的第一条语句之间不能有空白行(就是除了一个换行符,什么也没有的行。所以只有一个空格或制表符的行是合法的,此时NMAKE将其解释为一个null command),但在command lines之间可以有空白行。Commands block中的每一条命令可以是在控制台中合法的任何命令。事实上大可将commands block当成一个由控制台命令序列组成的批处理文件。
   此外,对commands block中的命令,还可以在其前面添加一个或多个所谓的命令修饰符(command modifier),以实现对命令的一些额外的控制。命令修饰符有以下3种:
  1) @command
  消除该命令的所有到STDOUT的输出。
  2) –[number]command
  关掉对该命令返回值的检测。在默认的情况下,如果一条命令返回非0值,则NMAKE将会停止执行。但如果在命令前加上一“-”,则NMAKE将会忽略该命令的返回值。如果“-”紧接着一个整数,则NMAKE会忽略掉任何大于该整数的返回值。
  3) !command
  如果该命令的执行对象为$**或$?(这两个都是预定义的宏,前者表示相应的dependent line中所有的dependent,后者表示所有比target具有更大的time stamp的dependent),则该“!”修饰符将会使该命令施行于这两个宏所描述的每一个独立的文件上。
   NMAKE还提供了一些语法可以在commands block中表示相应的dependent line中第一个dependent的文件名组成。例如:
   foo.exe : c:\sample\first.obj c:\sample\second.obj
   link %s
  NMAKE将“link %s”解释为:
   link c:\sample\first.obj
  如果将命令改为“link %|pfF.exe”,则NMAKE将之解释为:
   link c:\sample\first.exe
  %s表示全文件名,%|[part]F表示文件名中的某个部分,part可以是下列字符中的一个或多个,如果part为空,%|F与%s的意思相同:
  1) d:盘符;
  2) p:路径;
  3) f:文件基本名;
  4) e:文件扩展名;
Inference Rules(推导规则)
   Inference rules(下文简称IR)是一个模板,它用于决定如何从一个具有某种扩展名的文件构造出一个具有另一种扩展名的文件。NMAKE通过IR来确定用来更新target的命令以及推导target的dependents。IR的好处在于它满足了像我这样的懒人的需要。只要提供了正确的IR,则描述语句块就可以极大地化简。请看下面的例子:
   foo.obj :
  上面的语句将会运作得很好。是不是觉得很吃惊呢?事实上,NMAKE在处理该语句的时候,它首先在当前目录下搜索基本名为foo的文件(假设当前目录下有一个foo.c文件)。然后它查找一个后缀列表(suffix list),里面的每一项包含了从一种类型的文件构造另一种类型的文件需要调用的命令和参数的相关信息。在NMAKE预定义的列表中,foo.c到foo.obj的构造命令为CL。最后NMAKE调用CL,编译foo.c。呵呵,这么一长串的操作一条简单的语句就搞定了,是不是很方便呢!
   当出现下列情况之一时,NMAKE就会尝试使用IR:
  l NMAKE遇到一个没有任何命令的描述语句块。此时NMAKE就会搜索后缀列表,试图找到一个匹配的命令来构造target。
  l 无法找到某个dependent,并且该dependent没有作为target出现在其它dependent line中(即它不是一个pseudotarget)。此时NMAKE就会搜索给定的目录以及后缀列表,试图找到一个IR来构造出该dependent。
  l 一个target没有dependent,并且描述语句块中没有给出指令。此时NMAKE就会试图找出一个IR来构造出该target。
  l 一个target在NMAKE的命令行中给出,但在makefile里没有该target的相关信息(或根本就没有makefile)。此时NMAKE就会试图找出一个IR来构造出该target。
  定义一个IR的语法如下:
   [{frompath}].fromext[{topath}].toext;
   commands
  注意,各语法元素之间不能有任何空格。Dependent的后缀名在fromext中给出,target的后缀名在toext中给出。Frompath和topath是可选的,分别给出了搜索的路径。在每个IR的定义中只能分别为每一个后缀名给出一个搜索路径。如果想要指定多个搜索路径,就必须定义多个IR。并且,如果你为一个后缀指定了搜索路径,那么你也必须为另一个后缀指定搜索路径。即是说,fromext和topath只要有一个存在,则另一个也必须存在。你可以使用{.}或{}来表示当前目录。
  另外,要注意的是,如果你在IR中指定了搜索路径,则在dependent lien中也必须指定同样的路径,否则IR将不会应用于dependent line上,例如:
   {..\proj}.exe{..\proj}.obj:
  该IR不会用于下列语句上:
  project1.exe : project1.obj
  但会用于下列语句上:
  {..\proj}project1.exe : {..\proj}project1.obj
  NMAKE本身提供了一个预定义的后缀列表,内容如下:
   Rule Command Default Action
   .asm.exe $(AS)$(AFLAGS) $*.asm ML $*.ASM
  .asm.obj $(AS)$(AFLAGS) /c $*.asm ML /c $*.ASM
  .c.exe $(CC)$(CFLAGS) $*.c CL $*.C
  .c.obj $(CC)$(CFLAGS) /c $*.c CL /c $*.C
  .cpp.exe $(CPP)$(CPPFLAGS) $*.cpp CL $*.CPP
   .cpp.obj $(CPP)$(CPPFLAGS) /c $*.cpp CL /c $*.CPP
  .cxx.exe $(CXX) $(CXXFLAGS) $*.cxx CL $*.CXX
  .cxx.obj $(CXX) $(CXXFLAGS) /c $*.cxx CL /c $*.CXX
  .bas.obj $(BC) $(BFLAGS) $*.bas; BC $*.BAS;
  .cbl.exe $(COBOL) $(COBFLAGS) $*.cbl, $*.exe; COBOL $*.CBL, $*.EXE;
  .cbl.obj $(COBOL) $(COBFLAGS) $*.cbl; COBOL $*.CBL;
  .for.exe $(FOR) $(FFLAGS) $*.for FL $*.FOR
  .for.obj $(FOR) /c $(FFLAGS) $*.for FL /c $*.FOR
  .pas.exe $(PASCAL) $(PFLAGS) $*.pas PL $*.PAS
  .pas.obj $(PASCAL) /c $(PFLAGS) $*.pas PL /c $*.PAS
  .rc.res $(RC) $(RFLAGS) /r $* RC /r $*
   在上表中,类似AFLAG和CFLAG这种被包含在括号里面的是未定义的宏,通过在makefile中对这些宏给出定义,可以为这些命令指定编译器和参数。例如:
   $(AS)$(AFLAGS) $*.asm
  AS宏用于指定编译器,NMAKE中默认为ML;AFLAGS宏用于给出编译器参数,NMAKE将之留给用户定义,默认为空。所以默认的操作为:
  ML $*.asm
  这里可以看到将宏展开的语法,就是将宏的名字用圆括号括起来,然后在前面加上一个美元符号。另外需要说明的是,”$*”是NMAKE预定义的一个特殊的宏,其等于target的路径加上target的基本名。
  宏(MARCRO)
   这个相信大家都十分熟悉了。在makefile中通过使用宏将可以获得很大的灵活性。下面就是在makefile中定义宏的语法:
   macroname=string
  在makefile中,macroname是宏的名字,其可以是任何字母,数字和下划线的组合,最多可以有1024个字符。另外要注意的是,macroname是大小写敏感的。string是宏的定义体,可以有高达65510个字符。任何包含0个字符或只包含空白的字符串都被视为空字串(null string),此时,该宏也被视为NULL,任何其出现的地方,都会被替换为空白。
   在使用宏时,还应知道以下几个具有特殊意义的符号:
  l # 用于注释,例如:
  command=ML # compile asm file
  l \ 将宏定义分作多行来写,例如:
  LINKCMD = link myapp
  another, , NUL, mylib, myapp
  “\”后面的回车换行符会被空格替换,上面两行相当于:
  LINKCMD = link myapp another, , NUL, mylib, myapp
  l $ 将宏展开,用法在后面介绍。
  l ^ 如果要在宏中包含以上符号,但又不使用它们的特殊语义,则可以这样:
  dir=c:\windows^
  此时,dir相当于字符串”c:\windows\”。
   以下是一些语法上的细节:
  1) 在定义宏时,宏名字的第一个字符必须是该行的第一个字符;
  2) 每行只能定义一个宏;
  3) 在”=”两边可以有空格,但它们都会被忽略;
  4) 在宏定义体中可以有空格,它们都会被视为宏的一部分;
  除了可以在makefile中定义宏之外,宏定义也可以出现在NMAKE命令行中。此时,如果在宏定义中有任何空白,则必须用双引号将之括起来,例如:
  NMAKE "LINKCMD = LINK /MAP"
  NMAKE LINKCMD="LINK /MAP"
  而像下面这样则是不允许的(等号两边有空格):
   NMAKE LINKCMD = "LINK /MAP"
   使用宏的语法如下(注意,整个语句中不能有任何空格):
   $(macroname)
   NMAKE会将整个语句用宏替换掉。如果宏未定义,NMAKE会用空白替换之,不会产生任何错误。如果宏的名字只有一个字符,则括号可以省略,例如:$L和$(L)是等价的。
   NMAKE为宏的使用还提供了一个很有用的特性,那就是substitution(子替换)。即是在展开宏的时候,你还可以指明将展开的宏中的某部分文本用另外的文本替换掉。例如:
   SOURCE=one.c two.c
  foo.exe : $(SOURCE:.c=.obj)
   LINK $**;
  展开来就是这样:
  SOURCE=one.c two.c
  foo.exe : one.obj two.obj
   LINK one.obj two.obj;
  语句$(SOURCE:.c=.obj)表示将SOURCE中出现的所有”.c”替换为”.obj”。
  由以上的例子可以看出,substitution的语法如下(注意,没有空格):
  $(macroname:str1=str2)
   此外,NMAKE还提供了4组预定义的宏,它们分别是文件名宏,递归宏,命令宏和参数宏。它们都可以被重新定义,但可能会引起一些不必要的麻烦,因为它们被广泛使用。正所谓“动一发而牵全身”,一个小小的改动,甚至有可能会影响到太阳黑子的运动(蝴蝶效应),这就是使用宏的最大的弊端。
  文件名宏
  在commands block中使用,以表示特定的文件名,包括:
  1) $@ 用来表示相关联的dependent line中第一个target的全名(包括路径)。
  2) $$@ 同上,但只能用在dependent line中。
  3) $* target的路径加基本名。
  4) $** 相应的dependent line中的所有dependent。
  5) $? 相应的dependent line中的所有time stamp大于target的dependent。
  6) $< 同上,但只能用在IR中。
  下面是一个例子:
  DIR = c:\objects
  $(DIR)\a.obj : a.obj
  COPY a.obj $@
   最后一句展开来就相当于:copy a.obj c:\objects\a.obj
   另外,在使用以上这些宏的时候,还可以通过以下的字符来提取文件名中的某一个部分:
   D 路径
   B 基本名
   F 基本名加扩展名
   R 路径加基本名
   例如:如果$@表示c:\objects\a.object,则
   $(@D) c:\objects
   $(@B) a
   $(@F) a.obj
   $(@R) c:\objects\a
  递归宏
   有3个,它们都是用来在makefile中方便地进行NMAKE的递归调用,它们分别是:
  1) MAKE
  表示运行当前makefile的NMAKE程序的名字。例如,如果你在控制台用以下语句运行makefile:
  NMAKE her.mak
  则MAKE就等于NMAKE。
  但如果你将NMAKE.EXE改名为FUCK.EXE,那么你运行makefile的命令就应该改为:
  FUCK her.mak
  此时,MAKE就等于FUCK。
  2) MAKEDIR
  表示你调用NMAKE时所在的目录。
  3) MAKEFLAGS
  表示你运行当前makefile时使用的NMAKE参数。
   这几个宏在build程序的不同版本时特别有用,例如:
   all : vers1 vers2
  vers1 :
  cd \vers1
  $(MAKE)
  cd ..
  vers2 :
  cd \vers2
  $(MAKE) /F vers2.mak
  cd ..
   NMAKE会分别在.\vers1和.\vers2目录下运行vers1.mak和vers2.mak。
  命令宏和参数宏
   命令宏表示Microsoft的编译程序(真的很会做生意,任何时候都不忘自己的产品),而参数宏则是表示传递给这些编译器的参数,在默认情况下,参数宏都是未定义的。当然,你可以重新定义它们,让它们表示Boland的编译程序和参数。
   命令宏 对应的参数宏
  1) AS ml,M的汇编编译器。 AFLAGS
  2) BC bc,M的BASIC编译器。 BFLAGS
  3) CC cl,M的C编译器。 CFLAGS
  4) COBOL cobol,M的COBOL编译器。 COBFLAGS
  5) CPP cl,M的C++编译器。 CPPFLAGS
  6) CXX cl,M的C++编译器。 CXXFLAGS
  7) FOR fl,M的FORTRAN编译器。 FFLAGS
  8) PASCAL pl,M的PASCAL编译器。 PFLAGS
  9) RC rc,M的资源编译器。 RFLAGS 

- 作者: 小浪 2009年06月17日, 星期三 11:27  回复(1) |  引用(0) 加入博采

linux下svn命令大全
1、将文件checkout到本地目录
svn checkout path(path是服务器上的目录)
例如:svn checkout svn://192.168.1.1/pro/domain
简写:svn co

2、往版本库中添加新的文件
svn add file
例如:svn add test.php(添加test.php)
svn add *.php(添加当前目录下所有的php文件)

3、将改动的文件提交到版本库
svn commit -m “LogMessage“ [-N] [--no-unlock] PATH(如果选择了保持锁,就使用–no-unlock开关)
例如:svn commit -m “add test file for my test“ test.php
简写:svn ci

4、加锁/解锁
svn lock -m “LockMessage“ [--force] PATH
例如:svn lock -m “lock test file“ test.php
svn unlock PATH

5、更新到某个版本
svn update -r m path
例如:
svn update如果后面没有目录,默认将当前目录以及子目录下的所有文件都更新到最新版本
svn update -r 200 test.php(将版本库中的文件test.php还原到版本200)
svn update test.php(更新,于版本库同步。如果在提交的时候提示过期的话,是因为冲突,需要先update,修改文件,然后清除svn resolved,最后再提交commit)
简写:svn up

6、查看文件或者目录状态
1)svn status path(目录下的文件和子目录的状态,正常状态不显示)
【?:不在svn的控制中;M:内容被修改;C:发生冲突;A:预定加入到版本库;K:被锁定】
2)svn status -v path(显示文件和子目录状态)
第一列保持相同,第二列显示工作版本号,第三和第四列显示最后一次修改的版本号和修改人。
注:svn status、svn diff和 svn revert这三条命令在没有网络的情况下也可以执行的,原因是svn在本地的.svn中保留了本地版本的原始拷贝。
简写:svn st

7、删除文件
svn delete path -m “delete test fle“
例如:svn delete svn://192.168.1.1/pro/domain/test.php -m “delete test file”
或者直接svn delete test.php 然后再svn ci -m ‘delete test file‘,推荐使用这种
简写:svn (del, remove, rm)

8、查看日志
svn log path
例如:svn log test.php 显示这个文件的所有修改记录,及其版本号的变化

9、查看文件详细信息
svn info path
例如:svn info test.php

10、比较差异
svn diff path(将修改的文件与基础版本比较)
例如:svn diff test.php
svn diff -r m:n path(对版本m和版本n比较差异)
例如:svn diff -r 200:201 test.php
简写:svn di

11、将两个版本之间的差异合并到当前文件
svn merge -r m:n path
例如:svn merge -r 200:205 test.php(将版本200与205之间的差异合并到当前文件,但是一般都会产生冲突,需要处理一下)

12、SVN 帮助
svn help
svn help ci
——————————————————————————

以上是常用命令,下面写几个不经常用的

——————————————————————————

13、版本库下的文件和目录列表
svn list path
显示path目录下的所有属于版本库的文件和目录
简写:svn ls

14、创建纳入版本控制下的新目录
svn mkdir: 创建纳入版本控制下的新目录。
用法: 1、mkdir PATH…
2、mkdir URL…
创建版本控制的目录。
1、每一个以工作副本 PATH 指定的目录,都会创建在本地端,并且加入新增
调度,以待下一次的提交。
2、每个以URL指定的目录,都会透过立即提交于仓库中创建。
在这两个情况下,所有的中间目录都必须事先存在。

15、恢复本地修改
svn revert: 恢复原始未改变的工作副本文件 (恢复大部份的本地修改)。revert:
用法: revert PATH…
注意: 本子命令不会存取网络,并且会解除冲突的状况。但是它不会恢复
被删除的目录


16、代码库URL变更
svn switch (sw): 更新工作副本至不同的URL。
用法: 1、switch URL [PATH]
2、switch –relocate FROM TO [PATH...]

1、更新你的工作副本,映射到一个新的URL,其行为跟“svn update”很像,也会将
服务器上文件与本地文件合并。这是将工作副本对应到同一仓库中某个分支或者标记的
方法。
2、改写工作副本的URL元数据,以反映单纯的URL上的改变。当仓库的根URL变动
(比如方案名或是主机名称变动),但是工作副本仍旧对映到同一仓库的同一目录时使用
这个命令更新工作副本与仓库的对应关系。
我的例子:svn switch --relocate http://59.41.99.254/mytt http://www.mysvn.com/mytt

17、解决冲突
svn resolved: 移除工作副本的目录或文件的“冲突”状态。
用法: resolved PATH…
注意: 本子命令不会依语法来解决冲突或是移除冲突标记;它只是移除冲突的
相关文件,然后让 PATH 可以再次提交。

18、输出指定文件或URL的内容。
svn cat 目标[@版本]…如果指定了版本,将从指定的版本开始查找。
svn cat -r PREV filename > filename (PREV 是上一版本,也可以写具体版本号,这样输出结果是可以提交的)

 

19、查找工作拷贝中的所有遗留的日志文件,删除进程中的锁

当Subversion改变你的工作拷贝(或是.svn中的任何信息),它会尽可能的小心,在修改任何事情之前,它把意图写到日志文件中去,然后执行log文件中的命令,然后删掉日志文件,这与分类帐的文件系统架构类似。如果Subversion的操作中断了(举个例子:进程被杀死了,机器死掉了),日志文件会保存在硬盘上,通过重新执行日志文件,Subversion可以完成上一次开始的操作,你的工作拷贝可以回到一致的状态。

这就是svn cleanup所作的:它查找工作拷贝中的所有遗留的日志文件,删除进程中的锁。如果Subversion告诉你工作拷贝中的一部分已经“锁定”了,你就需要运行这个命令了。同样,svn status将会使用L 显示锁定的项目:

$ svn status
L somedir
M somedir/foo.c

$ svn cleanup
$ svn status
M somedir/foo.c

20、
拷贝用户的一个未被版本化的目录树到版本库。

svn import命令是拷贝用户的一个未被版本化的目录树到版本库最快的方法,如果需要,它也要建立一些中介文件。

$ svnadmin create /usr/local/svn/newrepos $ svn import mytree file:///usr/local/svn/newrepos/some/project Adding mytree/foo.c Adding mytree/bar.c Adding mytree/subdir Adding mytree/subdir/quux.h Committed revision 1.

在上一个例子里,将会拷贝目录mytree到版本库的some/project下:

$ svn list file:///usr/local/svn/newrepos/some/project bar.c foo.c subdir/

注意,在导入之后,原来的目录树并没有转化成工作拷贝,为了开始工作,你还是需要运行svn checkout导出一个工作拷贝。

- 作者: 小浪 2009年04月24日, 星期五 14:35  回复(0) |  引用(0) 加入博采

如何使用API函数GetFileVersionInfo,获得版本信息

使用GetFileVersionInfoSize(),GetFileVersionInfo()和VerQueryValue()三个API可以获得.exe和.dll文件的版本信息


1.获得自身的版本信息

//////////////////////////////////////////////////////////////
//
//         File: version.cpp
//  Description: Sample code for getting version info
//      Created: 2008-1-4
//       Author: Ken Zhang
//       E-Mail: cpp.china@hotmail.com
//
//////////////////////////////////////////////////////////////

/*
    The following code shows how to get FILEINFO value from resource file.

    These WIN32 functions will be used:

        * GetFileVersionInfo
        * GetFileVersionInfoSize
        * VerQueryValue
        * GetModuleFileName
 */


#include <windows.h>
#include <tchar.h>
#include <string>
#include <iostream>

#pragma comment(lib, "version.lib")
using namespace std;

bool GetFileVersion(HMODULE hModule, WORD *pBuffer)
{
    TCHAR fname
[MAX_PATH];
    VS_FIXEDFILEINFO
*pVi;
    DWORD dwHandle
;
    string str
;

   
if (::GetModuleFileName(hModule, fname, MAX_PATH))
   
{
       
int size = GetFileVersionInfoSize(fname, &dwHandle);

       
if (size > 0) {
            BYTE
*buffer = new BYTE[size];
           
           
if (GetFileVersionInfo(fname, dwHandle, size, buffer)) {
               
if (VerQueryValue(buffer, _T("\\"), (LPVOID *)&pVi, (PUINT)&size)) {
                    pBuffer
[0] = HIWORD(pVi->dwFileVersionMS);
                    pBuffer
[1] = LOWORD(pVi->dwFileVersionMS);
                    pBuffer
[2] = HIWORD(pVi->dwFileVersionLS);
                    pBuffer
[3] = LOWORD(pVi->dwFileVersionLS);

                   
delete buffer;
                   
return true;
               
}
           
}

           
delete buffer;
       
}
   
}

   
return false;
}

string
GetFileVersion(HMODULE hModule)
{
    string str
;
    WORD buffer
[4];

   
if (GetFileVersion(hModule, buffer))
   
{
       
char str2[32];

       
for (int i = 0; i < sizeof(buffer)/sizeof(WORD); i++)
       
{
            itoa
(buffer[i], str2, 10);
            str
+= str2;

           
if (i != sizeof(buffer)/sizeof(WORD) - 1)
           
{
                str
+= ".";
           
}
       
}
   
}

   
return str;
}

void main()
{
    cout
<< "Current version is: " << GetFileVersion(::GetModuleHandle(NULL)) << endl;
}

2.获得其他exe或dll的版本信息

std::string GetFileVersion(const std::string &strFilePath)
{
 DWORD dwSize;
 DWORD dwRtn;
 std::string szVersion;  

 //获取版本信息大小
 dwSize = GetFileVersionInfoSize(_T(strFilePath.c_str()),NULL);
 if (dwSize == 0)
 {
  return "";
 }

 char *pBuf;
 pBuf = new char[dwSize + 1];
 if(pBuf == NULL)
  return "";
 memset(pBuf, 0, dwSize + 1);
 //获取版本信息
 dwRtn = GetFileVersionInfo(_T(strFilePath.c_str()),NULL, dwSize, pBuf);
 if(dwRtn == 0)
 {
  return "";
 }

 LPVOID lpBuffer = NULL;
 UINT uLen = 0;
 //版本资源中获取信息
 dwRtn = VerQueryValue(pBuf,
  TEXT("\\StringFileInfo\\080404b0\\FileVersion"), //0804中文
  //04b0即1252,ANSI
  //可以从ResourceView中的Version中BlockHeader中看到
  //可以测试的属性
  /*
  CompanyName
  FileDescription
  FileVersion
  InternalName
  LegalCopyright
  OriginalFilename
  ProductName
  ProductVersion
  Comments
  LegalTrademarks
  PrivateBuild
  SpecialBuild
  */        
  &lpBuffer,
  &uLen);
 if(dwRtn == 0)
 {
  return "";
 }

 szVersion = (char*)lpBuffer;

 delete pBuf;
 return szVersion;
}

void main()
{
    std::string strFilePath = "abc.exe";
    cout << strFilePath << " version is: " << GetFileVersion(strFilePath) << endl;
}

- 作者: 小浪 2009年02月25日, 星期三 13:02  回复(0) |  引用(0) 加入博采

Boost库的编译

Boost库本身不用多介绍,每个用C++的人都对它有或多或少的概念。尽管它存在着是否过度设计、是否学院派这类的争论,不过作为C++标准库的后备它的优秀是谁也否认不了的。下面是网上摘录的一段:

  • 这世上总会有一些智慧让你吃惊。Boost就是这样的东西。
  • Boost没有修改C++的任何规则。它是一个完全符合C++规范的代码库。

对于大部分Boost应用来说,它是不用编译的,直接包含头文件就可使用,如:

any
array
asio
conversion
crc
bind/mem_fn
enable_if
function
lambda
mpl
smart_ptr
...

只有少部分需要编译成库文件,需要编译的库如下:

date_time
filesystem
function_types
graph
iostreams
math
mpi
program_options
python
regex
serialization
signals
system
test
thread
wave

再次啰嗦一句,如果代码中用不到这部分需要编译的Boost库,完全可以不用花时间编译,直接包含头文件即可。

开始编译

第一步:得到Boost(少说费话,写再多字也没稿费!)

本文对应的版本是Boost.1.37.0。
下载地址:http://www.boost.org/users/download/
解压,本文假设解压到D:\Boost_1_37_0

第二步:得到bjam程序

Boost库由一系列库组成,为了简化编译,就搞了个bjam这个工具出来。
想偷懒就直接下载可执行版本,下载地址。Windows版的是有ntx86后缀的那个。
想自己动手先做编译Boost前热身的就接着往下看:

  1. 进入控制台(如果是VC的建议从”Visual Studio 命令提示“进入)
  2. 用cd命令进入boost目录下的tools\jam\src目录,如文本是:D:\Boost_1_37_0\tools\jam\src
  3. 使用build命令编译bjam
    • BCC5.5/BCB6/BCB2006/CB2009用户输入build borland
    • VC用户依据其版本输入build vc7vc8vc9
    • Mingw用户输入build mingw
    • 注意,由于build对含有空格的路径名支持不好,所以有时会编译失败,这时可以通过set path=命令修改编译器的路径为8.3格式(可以用dir /x来查看对应的8.3格式是什么名字)
  4. 把生成的bjam.exe(bin.ntx86目录下)拷贝到Boost根目录下,如文本是:D:\Boost_1_37_0

使用bjam编译Boost

输入命令:

bjam --toolset=borland(对应BCB)或msvc(对应VC)或gcc(对应Mingw) stage

就开始编译了,编译时间比较长(大概半小时左右,依编译器以及选项不同而不同),编译好的文件会放在.\stage\lib--stagedir=命令决定里。

 bjam还有几个很有用的选项:
 

bjam参数
--build-dir=编译的临时文件会放在builddir里(这样比较好管理,编译完就可以把它删除了)
--stagedir=存放编译后库文件的路径,默认是stage
--build-type=complete编译所有版本,不然只会编译一小部分版本(确切地说是相当于:variant=release, threading=multi;link=shared|static;runtime-link=shared)
variant=debug|release决定编译什么版本(Debug or Release?)
link=static|shared决定使用静态库还是动态库。
threading=single|multi决定使用单线程还是多线程库。
runtime-link=static|shared决定是静态还是动态链接C/C++标准库。
--with-只编译指定的库,如输入--with-regex就只编译regex库了。
--show-libraries显示需要编译的库名称


- 作者: 小浪 2009年02月25日, 星期三 12:53  回复(0) |  引用(0) 加入博采

正则表达式

正则表达式英文Regular Expression),在计算机科学中,是指一个用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串。在很多文本编辑器或其他工具里,正则表达式通常被用来检索和/或替换那些符合某个模式的文本内容。许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sedgrep)普及开的。“正则表达式”通常缩写成“regex”,单数有regexp、regex,复数有regexps、regexes、regexen。

目录

[隐藏]

[编辑] 基本概念

一个正则表达式通常被称为一个模式 (pattern),为用来描述或者匹配一系列符合某个句法规则的字符串。例如:HandelHändelHaendel 这三个字符串,都可以由 "H(a|ä|ae)ndel" 这个模式来描述。大部分正则表达式的形式都有如下的结构:

替换
|
竖直分隔符代表替换。例如"gray|grey"可以匹配grey或gray。
数量限定
某个字符后的数量限定符用来限定前面这个字符允许出现的个数。最常见的数量限定符包括“+”,“?”和“*”(不加数量限定则代表出现一次且仅出现一次):
+
加号代表前面的字符必须至少出现一次。(1次,或多次)。例如,"goo+gle"可以匹配googlegoooglegoooogle等;
 ?
问号代表前面的字符最多只可以出现一次。(0次,或1次)。例如,"colou?r"可以匹配colour或者color;
*
星号代表前面的字符可以不出现,也可以出现一次或者多次。(0次,或1次,或多次)。例如,"0*42"可以匹配42042004200042等。
匹配
圆括号可以用来定义操作符的范围和优先度。例如,"gr(a|e)y"等价于"gray|grey","(grand)?father"匹配fathergrandfather

上述这些构造子都可以自由组合,因此,"H(ae?|ä)ndel"和"H(a|ae|ä)ndel"是相同的。

精确的语法可能因不同的工具或程序而异。

[编辑] 历史

最初的正则表达式出现于理论计算机科学自动控制理论和形式语言理论中。在这些领域中有对计算(自动控制)的模型和对形式语言描述与分类的研究。1940年代,Warren McCulloch与Walter Pitts将神经系统中的神经元描述成小而简单的自动控制元。在1950年代,数学家斯蒂芬·科尔·克莱尼利用称之为正则集合的数学符号来描述此模型。肯·汤普逊将此符号系统引入编辑器QED,然后是Unix上的编辑器ed,并最终引入grep。自此,正则表达式被广泛地使用于各种Unix或者类似Unix的工具,例如Perl

Perl正则表达式源自于Henry Spencer写的regex,它已经演化成了pcre(Perl兼容正则表达式Perl Compatible Regular Expressions),一个由Philip Hazel开发的,为很多现代工具所使用的库。

各计算机语言之间的正则表达式的整合目前开展的很差。未来的Perl6的子项目Apocalypse的设计中已考虑到了这点。

[编辑] 形式语言理论

正则表达式可以用形式语言理论的方式来表达。正则表达式由常量和算子组成,它们分别指示字符串的集合和在这些集合上的运算。给定有限字母表 Σ 定义了下列常量:

  • (“空集”) 指示集合
  • (“空串”) ε 指示集合 {ε}
  • (“文字字符”) 在 Σ 中的 a 指示集合 {a}

定义了下列运算:

  • (“串接”) RS 指示集合 { αβ | α ∈ R ∧ β ∈ S }。例如 {"ab"|"c"}{"d"|"ef"} = {"abd", "abef", "cd", "cef"}。
  • (“选择”) R|S 指示 RS 的并集。
  • (“Kleene星号”) R* 指示包含 ε 并且闭合在字符串串接下的 R 的最小超集。这是可以通过 R 中的零或多个字符串的串接得到所有字符串的集合。例如,{"ab", "c"}* = {ε, "ab", "c", "abab", "abc", "cab", "cc", "ababab", ... }。

上述常量和算子形成了克莱尼代数

很多课本使用对选择使用符号 , + 替代竖杠。

为了避免括号,假定 Kleene 星号有最高优先级,接着是串接,接着是并集。如果没有歧义则可以省略括号。例如,(ab)c 可以写为 abca|(b(c*)) 可以写为 a|bc*

例子:

  • a|b* 指示 {ε, a, b, bb, bbb, ...}。
  • (a|b)* 指示由包括空串、任意数目个 ab 字符组成的所有字符串的集合。
  • ab*(c|ε) 指示开始于一个 a 接着零或多个 b 和最终可选的一个 c 的字符串的集合。

正则表达式的形式定义故意非常精简,避免定义多余的量词 ?+,它们可以被表达为: a+ = aa*a? = (a|ε)。有时增加补算子 ~ ;~R 指示在 Σ* 上的不在 R 中的所有字符串的集合。补算子是多余的,因为它使用其他算子来表达(尽管计算这种表示的过程是复杂的,而结果可能指数性的增大)。

这种意义上的正则表达式可以表达正则语言,精确的是可被有限状态自动机接受的语言类。但是在简洁性上有重要区别。某类正则语言只能用大小指数增长的自动机来描述,而要求的正则表达式的长度只线性的增长。正则表达式对应于乔姆斯基层级的类型-3文法。在另一方面,在正则表达式和不导致这种大小上的爆炸的非确定有限状态自动机(NFA)之间有简单的映射;为此 NFA 经常被用作正则表达式的替代表示。

我们还要在这种形式化中研究表达力。如下面例子所展示的,不同的正则表达式可以表达同样的语言: 这种形式化中存在着冗余。

有可能对两个给定正则表达式写一个算法来判定它们所描述的语言是否本质上相等,简约每个表达式到极小确定有限自动机,确定它们是否同构(等价)。

这种冗余可以消减到什么程度? 我们可以找到仍有完全表达力的正则表达式的有趣的子集吗? Kleene 星号和并集明显是需要的,但是我们或许可以限制它们的使用。这提出了一个令人惊奇的困难问题。因为正则表达式如此简单,没有办法在语法上把它重写成某种规范形式。过去公理化的缺乏导致了星号高度问题。最近 Dexter Kozen 用克莱尼代数公理化了正则表达式。

很多现实世界的“正则表达式”引擎实现了不能用正则表达式代数表达的特征。

[编辑] 表达式全集

正则表达式有多种不同的风格。下表是在PCRE中元字符及其在正则表达式上下文中的行为的一个完整列表:

字符描述
\将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“n”匹配字符“n”。“\n”匹配一个换行符。序列“\\”匹配“\”而“\(”则匹配“(”。
^匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。
$匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。
*匹配前面的子表达式零次或多次。例如,zo*能匹配“z”以及“zoo”。*等价于{0,}。
+匹配前面的子表达式一次或多次。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。
?匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“do”或“does”中的“do”。?等价于{0,1}。
{n}n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。
{n,}n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。
{n,m}mn均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。
?当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+?”将匹配单个“o”,而“o+”将匹配所有“o”。
.匹配除“\n”之外的任何单个字符。要匹配包括“\n”在内的任何字符,请使用像“[.\n]”的模式。
(pattern)匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“\(”或“\)”。
(?:pattern)匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用“或”字符(|)来组合一个模式的各个部分是很有用。例如,“industr(?:y|ies)就是一个比”industry|industries'更简略的表达式。
(?=pattern)正向预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern)负向预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始
x|y匹配x或y。例如,“z|food”能匹配“z”或“food”。“(z|f)ood”则匹配“zood”或“food”。
[xyz]字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。
[^xyz]负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“p”。
[a-z]字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。
[^a-z]负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。
\b匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。
\B匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
\cx匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。
\d匹配一个数字字符。等价于[0-9]。
\D匹配一个非数字字符。等价于[^0-9]。
\f匹配一个换页符。等价于\x0c和\cL。
\n匹配一个换行符。等价于\x0a和\cJ。
\r匹配一个回车符。等价于\x0d和\cM。
\s匹配任何空白字符,包括空格、制表符、换页符等等。等价于[\f\n\r\t\v]。
\S匹配任何非空白字符。等价于[^\f\n\r\t\v]。
\t匹配一个制表符。等价于\x09和\cI。
\v匹配一个垂直制表符。等价于\x0b和\cK。
\w匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。
\W匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。
\xn匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41”匹配“A”。“\x041”则等价于“\x04”&“1”。正则表达式中可以使用ASCII编码。.
\num匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符。
\n标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。
\nm标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若nm均为八进制数字(0-7),则\nm将匹配八进制转义值nm
\nml如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。
\un匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。

[编辑] 范例

以下以PHP的语法所写的范例

  • 验证字串是否只含数字与英文,字串长度并在4~16个字符之间

$str = 'a1234';
if (preg_match("^[a-zA-Z0-9]{4,16}$", $str)) {
    echo "驗證成功";
} else {
    echo "驗證失敗";
}
?>
  • 简易的台湾身份证字号验证

$str = 'a1234';
if (preg_match("^[A-Z]{1}[1-2]{1}[0-9]{8}$", $str)) {
    echo "驗證成功";
} else {
    echo "驗證失敗";
}
?>

[编辑] 相关条目

[编辑] 外部链接

- 作者: 小浪 2009年02月23日, 星期一 13:05  回复(0) |  引用(0) 加入博采

[转]电子书大放送(汇编 c c++ windows)
发布的均为计算机类的图书,由于图书较多,所以均存放于纳米盘,如果给你的下载带来不便(请使用纳米机器人下载),请见谅....全部用RAR压缩,解压请用3.7以上版本,解压密码为jumby     

汇编语言:

汇编语言个人认为计算机专业必学的一门课,推荐王爽的汇编语言教程
汇编语言(王爽版)
下载地址:http://www.namipan.com/d/62837a6eb845dfd6efff489aeee8ef924bd1755c69958b02
王爽有一个专业的汇编网(www.asmedu.net),如果有关于汇编的问题可以去上面看看

还有一本也不错的
80x86汇编语言程序设计教程(马季文版)
下载地址:http://www.namipan.com/d/171bc9819f07d7f4479baa87058708fb822b3d554ab32d01

看过以上教程后汇编基本是掌握了,不过以上教程讲解的都是16位的汇编,如果想要更深一步了解汇编的话,可以看罗云斌的Windows下32位汇编语言程序设计(第二版)



C语言:
C语言个人认为也是必须掌握的
The C Programming Language(不说了,经典的K&R,此为中文版第二版)
下载地址:http://www.namipan.com/d/e61704d5390064e28cba7f4a1724182ef550ffc9f0f21f01

C.Primer.Plus第五版中文版(很好的入门书)
下载地址:http://www.namipan.com/d/726b549d208be2b6b4a887dd61995db4619bfad5b5d49803

C语言大全第4版(详细讲解了C99标准)
下载地址:http://www.namipan.com/d/568b2a52baf1981b47702a93aad491ae7edad89561c6fb00



C++语言:
伟大的编程语言,向Bjarne Stroustrup致敬
C++.Primer.Plus中文第五版(不错的入门书)
下载地址:http://www.namipan.com/d/3cce89d63a3f825712dc92c4ad796ca01e5a38bb15eb3903

C++.Primer中文第四版(C++权威之作,是倚天剑还是屠龙刀?)
下载地址:http://www.namipan.com/d/1082c7ac304d670c8128a7a02d5a43d1dc10230637f99905

The C++ Programming Language(C++权威之作,不多说了,到底哪个是倚天哪个是屠龙啊?)
下载地址:http://www.namipan.com/d/20d7b614555e3bce0dbf07e83066ec576ae4c068506f7401



Windows编程:
Windows程序设计(第五版)学习Windows编程的入门经典
下载地址:http://www.namipan.com/d/f20cefbb5ffbb9fa31396c456d24434217ef5f76b8d0cc03

Windows核心编程(第四版)第五版已经出版,有条件的买书吧
下载地址:http://www.namipan.com/d/a62efff880ac5aa779a071b80e2efb6538a9d6b29b65da02

Windows高级编程指南(第三版)
下载地址:http://www.namipan.com/d/ce48e330a077a490b59baf22b51ebfea54a16838d3b30101

Win32 多线程程序设计(侯捷老师的书)
下载地址:http://www.namipan.com/d/72b23a6c11bc466e2b40e81a120acb9a2136922443a76801



MFC编程类:
MFC Windows程序设计(第2版)
下载地址:http://www.namipan.com/d/38312dc41d62a14486aae4e442531c95497f38a682961d02

深入浅出MFC(第二版)
下载地址:http://www.namipan.com/d/7067bab594019ca2a877631fcb43c8bc098ad04167734401

VC深入详解(孙鑫的书)
下载地址:http://www.namipan.com/d/a5abee00d384ebd50ba1905cc76906b8ebe6beb20cdac205

关于Windows编程的学习请参考文章(VC++学习方法及书籍推荐)

以上图书最好按照顺序去学习

其实本文更多的是提供一种学习方法,因为笔者是自学的,所以吃了很多苦,走了很多弯路,总是看到很多朋友在盲目的学习编程,为了给大家提供一个学习的捷径(不走弯路就是捷径),所以才有此文,当然,学完这些你也仅仅是进入了编程的世界,需要走的路依然很多,还有大量的东西需要你去了解,不过我想,如果你真的能把这些学完的话,那么你自己肯定也知道接下来的路该如何去走了,师傅领进门,修行就靠你自己了,由于笔者也是初学者,所以我的观点也许不是很对,本文只是一种思路,更多的还是靠你自己,最后,祝愿大家新年快乐

- 作者: 小浪 2008年12月21日, 星期日 18:57  回复(1) |  引用(0) 加入博采

文档/视图结构中的各个部分是如何联系到一起的

文档/视图结构中的各个部分是如何联系到一起的
来源:http://www.programfan.com/article/showarticle.asp?id=2620

文档/视图结构是MFC中最有特色而又有难度的部分,在这当中涉及了应用、文档模板、文档、视图、MDI框架窗口、MDI子窗口等不同的对象,如果不了解这些部分之间如何关联的话,就可能犯错误,也就很难编出有水平的文档/视图程序。比如我在初学VC编程的时候,为应用程序添加了两个文档模板,两个模板公用一个文档类,只是视图不一样,期望当一个模板的文档的视图改变了文档后,调用UpdateAllViews后也能更新另一个文档模板的视图,结果当然是不行的,原因就是对MFC的文档/视图结构没有深入的了解,了解的最好方法就是阅读一下MFC的源代码。下面就是我的笔记:

(一)应用程序对象与文档模板之间的联系:

        首先,在应用程序对象中有一个CDocManager指针类型的共有数据成员m_pDocManager,在CDocManager中维护一个CPtrList类型的链表:m_tempateList,它是一个保护成员。InitInstance函数中调用CWinApp::AddDocTemplate函数,实际上是调用m_pDocManager的AddDocTemplate函数向链表m_templateList添加模板指针。CWinApp提供了GetFirstDocTemplatePosition和GetNextDocTemplate函数实现对m_templateList链表的访问(实际上是调用了CDocManager的相关函数)。

         在文件操作方面CWinApp提供的最常用的功能是文件的新建(OnFileNew)和打开(OnFileOpen),它也是调用CDocManager类的同名函数。对于新建,一般的时候在只有一个文档模板的时候,它新建一个空白的文件;如果有多个文档模板的时候,它会出现一个对话框提示选择文档类型。它的源代码如下:

void CDocManager::OnFileNew()

{

       if (m_templateList.IsEmpty())

       {

                                .......

              return;

       }

                //取第一个文档模板的指针

       CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();

       if (m_templateList.GetCount() > 1)

       {

              // 如果多于一个文档模板,出现对话框提示用户去选择

              CNewTypeDlg dlg(&m_templateList);

              int nID = dlg.DoModal();

              if (nID == IDOK)

                     pTemplate = dlg.m_pSelectedTemplate;

              else

                     return;     // none - cancel operation

       }

                ......

                //参数为NULL的时候OpenDocument File会新建一个文件

       pTemplate->OpenDocumentFile(NULL);

}

打开文件:

void CDocManager::OnFileOpen()

{

       // 出现打开文件对话框

       CString newName;

       if (!DoPromptFileName(newName, AFX_IDS_OPENFILE,

         OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, TRUE, NULL))

              return; // open cancelled

       AfxGetApp()->OpenDocumentFile(newName);          //实际也是调用文档模板的同名函数

}

(二)文档模板与文档之间的联系:

        从上面看出应用程序对象对文件的新建和打开是依靠文档模板的OpenDocumentFile函数实现的。MFC的模板类是用来联系文档类、视类和框架类的,在它的构造函数就需要这三者的信息:

CDocTemplate ( UINT nIDResource, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass );

构造函数利用后三个参数为它的三个CruntimeClass*类型的保护成员赋值:

       m_pDocClass = pDocClass;

       m_pFrameClass = pFrameClass;

       m_pViewClass = pViewClass;

    文档模板分为单文档模板和多文档模板两种,这两个模板的实现是不同的,除了上面的三个成员,内部有彼此不相同的但是很重要的成员变量。对于多文档模板:CPtrList m_docList;,单文档模板:CDocument* m_pOnlyDoc;。它们都有一个成员函数AddDocument,分别各自的成员进行赋值操作,而在它们的父类的CDocTemplate中则是为它所添加的文档的m_pDocTemplate变量赋值为模板自己的地址:

void CDocTemplate::AddDocument(CDocument* pDoc)

{

       ASSERT_VALID(pDoc);

       ASSERT(pDoc->m_pDocTemplate == NULL);  

       pDoc->m_pDocTemplate = this;

}

由于单文档模板只能拥有一个文档,所以它只是维护一个指向自己所拥有的模板的指针:m_pOnlyDoc,AddDocument函数就是要为这个成员赋值:

void CSingleDocTemplate::AddDocument(CDocument* pDoc)

{

                ......

       CDocTemplate::AddDocument(pDoc);

       m_pOnlyDoc = pDoc;

}
由于多文档模板可以拥有多个文档,所以它要维护的是包含它所打开的所有文档的指针的链表,所以它的AddDocument的实现为:

void CMultiDocTemplate::AddDocument(CDocument* pDoc)

{

                ......

       CDocTemplate::AddDocument(pDoc);

       m_docList..AddTail(pDoc);

}
    模板通过m_pOnlyDoc(单文档)或记住了自己所拥有的所有的模板的指针,并通过GetFirstDocPosition和GetNextDoc函数可以实现对它所拥有的文档的访问,同时使文档记住了自己所属文档模板的指针,同时文档提供了GetDocTemplate()函数可以取得它所属的模板。

对AddDocument函数的调用主要是发生在另一个成员函数CreateNewDocument里,它的作用是创建一个新的文档:

CDocument* CDocTemplate::CreateNewDocument()

{

       if (m_pDocClass == NULL)

       {

         ……

       }

       CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();

    ……

       AddDocument(pDocument);

       return pDocument;

}

CreateNewDocument函数主要利用文档类的运行时指针的函数CreateObject创建一个新文档对象,并利用AddDocument将其指针赋給相关的成员,留做以后使用。

    在应用程序的OnFileNew和OnFileOpen函数都使用了模板的OpenDocumentFile函数,而且在实际编程的时候也大都使用这个函数。在MSDN的文档说这个函数当参数不为NULL的时候打开文件,否则就用上面所说的CreateNewDocument函数创建一个新文档,那么它是如何实现的呢?

CDocument* CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,

       BOOL bMakeVisible)

{

       CDocument* pDocument = NULL;

       CFrameWnd* pFrame = NULL;

       BOOL bCreated = FALSE;      // => doc and frame created

       BOOL bWasModified = FALSE;

    //如果已经有打开的文档,就会询问否保存文件

       if (m_pOnlyDoc != NULL)

       {

              pDocument = m_pOnlyDoc;

              if (!pDocument->SaveModified())

                     return NULL;     

              pFrame = (CFrameWnd*)AfxGetMainWnd();

                                ......

       }

    //创建新文件

       else

       {

              pDocument = CreateNewDocument();

              ASSERT(pFrame == NULL);    

              bCreated = TRUE;

       }

                ......

    //如果第一次创建文档则也要创建框架窗口。

       if (pFrame == NULL)

       {

              ASSERT(bCreated);

              // create frame - set as main document frame

              BOOL bAutoDelete = pDocument->m_bAutoDelete;

              pDocument->m_bAutoDelete = FALSE;

              pFrame = CreateNewFrame(pDocument, NULL);

              pDocument->m_bAutoDelete = bAutoDelete;

                                ......

       }

       if (lpszPathName == NULL)

       {

              // 为新文档设置默认标题

              SetDefaultTitle(pDocument);

        ……

      //一般的时候重载OnNewDocument初始化一些数据,如果返回FALSE,表示初始化失//败,销毁窗口。

              if (!pDocument->OnNewDocument())

              {

                                                ......

                     if (bCreated)

                            pFrame->DestroyWindow();    // will destroy document

                     return NULL;

              }

       }

       else

       {

              CWaitCursor wait;

              // open an existing document

              bWasModified = pDocument->IsModified();

              pDocument->SetModifiedFlag(FALSE);

              //OnOpenDocument函数重新初始化文档对象

              if (!pDocument->OnOpenDocument(lpszPathName))

              {

                     if (bCreated)

                     {

                //新建文档的情况

                            pFrame->DestroyWindow();   

                     }

                     else if (!pDocument->IsModified())

                     {

                            // 文档没有被修改,恢复原来文档的修改标志

                            pDocument->SetModifiedFlag(bWasModified);

                     }

                     else

                     {

                            // 修改了原始的文档

                            SetDefaultTitle(pDocument);

                            if (!pDocument->OnNewDocument())

                            {

                                   TRACE0("Error: OnNewDocument failed after trying to open a document - trying to continue.\n");

                            }

                     }

                     return NULL;        // open failed

              }

              pDocument->SetPathName(lpszPathName);

       }

       CWinThread* pThread = AfxGetThread();

       if (bCreated && pThread->m_pMainWnd == NULL)

       {

              pThread->m_pMainWnd = pFrame;

       }

       InitialUpdateFrame(pFrame, pDocument, bMakeVisible);

       return pDocument;

}

以下是多文档模板的OpenDocumentFile的实现

CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,

       BOOL bMakeVisible)

{

       //新建一个文档对象

       CDocument* pDocument = CreateNewDocument();

……

       BOOL bAutoDelete = pDocument->m_bAutoDelete;

       pDocument->m_bAutoDelete = FALSE;  

       CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL);

       pDocument->m_bAutoDelete = bAutoDelete;

……

       if (lpszPathName == NULL)

    //当是新建的时候

       {

              SetDefaultTitle(pDocument);

              // avoid creating temporary compound file when starting up invisible

              if (!bMakeVisible)

                     pDocument->m_bEmbedded = TRUE;

              if (!pDocument->OnNewDocument())

              {

                     pFrame->DestroyWindow();

                     return NULL;

              }

              m_nUntitledCount++;

       }

       else

       {

              // 打开一个已经存在的文件

              CWaitCursor wait;

              if (!pDocument->OnOpenDocument(lpszPathName))

              {

                     // user has be alerted to what failed in OnOpenDocument

                     TRACE0("CDocument::OnOpenDocument returned FALSE.\n");

                     pFrame->DestroyWindow();

                     return NULL;

              }

              pDocument->SetPathName(lpszPathName);

       }

       InitialUpdateFrame(pFrame, pDocument, bMakeVisible);

       return pDocument;

}

    从上面看出模板类的OpenDocumentFile函数里,利用CreateNewDocument对象使文档对象与模板对象建立了联系,利用了CreateNewFrame函数使框架窗口与文档、视图、模板发生了联系:

CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther)

{

       if (pDoc != NULL)

              ASSERT_VALID(pDoc);

       ASSERT(m_nIDResource != 0); // 必须有资源ID

       CCreateContext context;

       context.m_pCurrentFrame = pOther;

       context.m_pCurrentDoc = pDoc;

       context.m_pNewViewClass = m_pViewClass;

       context.m_pNewDocTemplate = this;

       if (m_pFrameClass == NULL)

       {

           ……

       }

       CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();

       if (pFrame == NULL)

       {

        ……

              return NULL;

       }

       ASSERT_KINDOF(CFrameWnd, pFrame);

       if (context.m_pNewViewClass == NULL)

              TRACE0("Warning: creating frame with no default view.\n");

       if (!pFrame->LoadFrame(m_nIDResource,

                     WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE,   // default frame styles

                     NULL, &context))

       {

……

              return NULL;

       }

       return pFrame;

}

总结:在模板里使用自己的数据结构维护着自己拥有的文档对象,并提供了GetFirstDocPosition和GetNextDoc函数实现对这些文档的对象的访问。所以,在一个拥有多个文档模板的应用程序中,即使每个模板使用了相同类型的文档类,每个新建或打开的文档在这些文档模板之间也不是共享的。

(三)文档与视图之间的联系

在视图类有一个保护数据成员:CDocument* m_pDocument;,这个文档指针指向视图对象所属的文档,视图里常用的函数GetDocument()就是返回的这个指针;在文档类有一个保护数据成员:CDocument* m_viewList;,它保存的是所有正在显示该文档的视图的指针,通过CDocument的成员函数GetFirstViewPosition和GetNextView函数可以实现对这些视图的访问。

在视图被创建的时候,在OnCreate函数里视图和文档发生了关联:

int CView::OnCreate(LPCREATESTRUCT lpcs)

{

       if (CWnd::OnCreate(lpcs) == -1)

              return -1;

       CCreateContext* pContext = (CCreateContext*)lpcs->lpCreateParams;

       if (pContext != NULL && pContext->m_pCurrentDoc != NULL)

       {

              pContext->m_pCurrentDoc->AddView(this);

              ASSERT(m_pDocument != NULL);

       }

       else

       {

              TRACE0("Warning: Creating a pane with no CDocument.\n");

       }

       return 0;  

}

这个关联是通过文档类的AddView函数实现的:

void CDocument::AddView(CView* pView)

{

    ……

       m_viewList.AddTail(pView);

       pView->m_pDocument = this;

       OnChangedViewList();  

}

在这个函数里,首先文档对象先把所添加的视图指针加到自己的视图链表里,然后指向自己的指针赋給了所添加的视图的m_pDocument成员。

众所周知,文档与视图进行通信的方式先调用文档的UpdateAllViews函数,从而调用视图的OnUpdate函数:

void CDocument::UpdateAllViews(CView* pSender, LPARAM lHint, CObject* pHint)

       // walk through all views

{

    //视图链表不能为空且发送者不能为空

       ASSERT(pSender == NULL || !m_viewList.IsEmpty());

       POSITION pos = GetFirstViewPosition();

       while (pos != NULL)

       {

              CView* pView = GetNextView(pos);

              ASSERT_VALID(pView);

      //不调用发送者的OnUpdate函数

              if (pView != pSender)

                     pView->OnUpdate(pSender, lHint, pHint);

       }

}

在视图的OnUpdate函数里默认的实现仅是通知视图进行重画:

Invalidate(TRUE);

我们一般重载这个更新视图的某些数据或进行其他操作,比如更新视图滚动条的滚动范围。

(四)框架窗口与文档、视图之间的联系

在框架窗口被创建的时候,创建了视图,相关的函数如下:

int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs)

{

       CCreateContext* pContext = (CCreateContext*)lpcs->lpCreateParams;

       return OnCreateHelper(lpcs, pContext);

}

int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext)

{

       if (CWnd::OnCreate(lpcs) == -1)

              return -1;

       // create special children first

       if (!OnCreateClient(lpcs, pContext))

       {

              TRACE0("Failed to create client pane/view for frame.\n");

              return -1;

       }

       // post message for initial message string

       PostMessage(WM_SETMESSAGESTRING, AFX_IDS_IDLEMESSAGE);

       // make sure the child windows have been properly sized

       RecalcLayout();

       return 0;   // create ok

}

BOOL CFrameWnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext)

{

       // default create client will create a view if asked for it

       if (pContext != NULL && pContext->m_pNewViewClass != NULL)

       {

              if (CreateView(pContext, AFX_IDW_PANE_FIRST) == NULL)

                     return FALSE;

       }

       return TRUE;

}

CWnd* CFrameWnd::CreateView(CCreateContext* pContext, UINT nID)

{

       CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();

       if (pView == NULL)

       {

              return NULL;

       }

       ASSERT_KINDOF(CWnd, pView);

       if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,

              CRect(0,0,0,0), this, nID, pContext))

       {

              return NULL;        // can't continue without a view

       }

       if (afxData.bWin4 && (pView->GetExStyle() & WS_EX_CLIENTEDGE))

       {

              ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_FRAMECHANGED);

       }

       return pView;

}

在文档模板的OpenDocumentFile函数发生了如下的调用:

InitialUpdateFrame(pFrame, pDocument, bMakeVisible);

文档模板的这个函数的实现为:

pFrame->InitialUpdateFrame(pDoc, bMakeVisible);

实际是调用了框架窗口的同名函数:

void CFrameWnd::InitialUpdateFrame(CDocument* pDoc, BOOL bMakeVisible)

{

       CView* pView = NULL;

       if (GetActiveView() == NULL)

       {

              //取主视图

              CWnd* pWnd = GetDescendantWindow(AFX_IDW_PANE_FIRST, TRUE);

              if (pWnd != NULL && pWnd->IsKindOf(RUNTIME_CLASS(CView)))

              {

        //主视图存在且合法,把当前的主视图设置为活动视图

                     pView = (CView*)pWnd;

                     SetActiveView(pView, FALSE);

              }

       }

       if (bMakeVisible)

       {

              SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);

              if (pView != NULL)

                     pView->OnActivateFrame(WA_INACTIVE, this);

        ……

              ActivateFrame(nCmdShow);

              if (pView != NULL)

                     pView->OnActivateView(TRUE, pView, pView);

       }

       // update frame counts and frame title (may already have been visible)

       if (pDoc != NULL)

              pDoc->UpdateFrameCounts();

       OnUpdateFrameTitle(TRUE);

}

上面的函数中对视图的操作主要是用SetActiveView设置了活动视图,并且调用了视图的OnActivateFrame函数。在CFrameWnd类中维护着一个保护成员:CView* m_pViewActive;,SetAcitveView函数主要就是对它进行操作:

void CFrameWnd::SetActiveView(CView* pViewNew, BOOL bNotify)

{

       CView* pViewOld = m_pViewActive;

       if (pViewNew == pViewOld)

              return;     // do not re-activate if SetActiveView called more than once

       m_pViewActive = NULL;   // no active for the following processing

       // deactivate the old one

       if (pViewOld != NULL)

              pViewOld->OnActivateView(FALSE, pViewNew, pViewOld);

       if (m_pViewActive != NULL)

              return;     // already set

       m_pViewActive = pViewNew;

       // activate

       if (pViewNew != NULL && bNotify)

              pViewNew->OnActivateView(TRUE, pViewNew, pViewOld);

}

CFrameWnd还有另一个函数返回这个成员:

CView* CFrameWnd::GetActiveView() const

{

       ASSERT(m_pViewActive == NULL ||

              m_pViewActive->IsKindOf(RUNTIME_CLASS(CView)));

       return m_pViewActive;

}

CframeWnd还有一个函数能取得当前活动的文档,它是通过活动视图间接得到的:

CDocument* CFrameWnd::GetActiveDocument()

{

       ASSERT_VALID(this);

       CView* pView = GetActiveView();

       if (pView != NULL)

              return pView->GetDocument();

       return NULL;

}

(五)MDI主窗口和子窗口之间的关联:

在MDI子窗口创建的时候,指定了它与MDI之间的关系:

BOOL CMDIChildWnd::Create(LPCTSTR lpszClassName,

       LPCTSTR lpszWindowName, DWORD dwStyle,

       const RECT& rect, CMDIFrameWnd* pParentWnd,

       CCreateContext* pContext)

{

       if (pParentWnd == NULL)

       {

              CWnd* pMainWnd = AfxGetThread()->m_pMainWnd;

              ASSERT(pMainWnd != NULL);

              ASSERT_KINDOF(CMDIFrameWnd, pMainWnd);

              pParentWnd = (CMDIFrameWnd*)pMainWnd;

       }

    ……

       pParentWnd->RecalcLayout();

       CREATESTRUCT cs;

……

//指定了所属的MDI子窗口

       cs.hwndParent = pParentWnd->m_hWnd;

    ……

       cs.lpCreateParams = (LPVOID)pContext;

       if (!PreCreateWindow(cs))

       {

              PostNcDestroy();

              return FALSE;

       }

       MDICREATESTRUCT mcs;

    ……

       mcs.style = cs.style & ~(WS_MAXIMIZE | WS_VISIBLE);

       mcs.lParam = (LONG)cs.lpCreateParams;

       AfxHookWindowCreate(this);

    //发送WM_MDICREATE消息,创建了MDI子窗口

       HWND hWnd = (HWND)::SendMessage(pParentWnd->m_hWndMDIClient,

              WM_MDICREATE, 0, (LPARAM)&mcs);

       if (!AfxUnhookWindowCreate())

              PostNcDestroy();        // cleanup if MDICREATE fails too soon

    ……

       return TRUE;

}

当MDI子窗口创建了以后,MDI主窗口就可以用自己的函数实现对子窗口的管理,例如取得当前的活动子窗口是通过发送WM_MDIGETACTIVE消息实现的

- 作者: 小浪 2008年11月13日, 星期四 20:31  回复(0) |  引用(0) 加入博采

Bjarne Stroustrup的FAQ:C++的风格与技巧
翻译:左轻侯

(译注:本文的翻译相当艰苦。Bjarne Stroustrup不愧是创立C++语言的一代大师,不但思想博大精
深,而且在遣词造句上,也非常精微深奥。有很多地方,译者反复斟酌,都不能取得理想的效果,只能尽
力而为。
Html格式的文档见译者主页:
http://www.wushuang.net
如果你对这个翻译稿有任何意见和建议,请发信给译者:onekey@163.com
原文的地址为:
http://www.research.att.com/~bs/bs_faq2.html

(Bjarne Stroustrup博士,1950年出生于丹麦,先后毕业于丹麦阿鲁斯大学和英国剑挢大学,AT&T大规模程序设计研究部门负责人,AT&T 贝尔实验室和ACM成员。1979年,B. S开始开发一种语言,当时称为"C with Class",后来演化为C++。1998年,ANSI/ISO C++标准建立,同年,B. S推出其经典著作The C++ Programming Language的第三版。)

这是一些人们经常向我问起的有关C++的风格与技巧的问题。如果你能提出更好的问题,或者对这些答案有所建议,请务必发Email给我(bs@research.att.com)。请记住,我不能把全部的时间都花在更新我的主页上面。

更多的问题请参见我的general FAQ。

关于术语和概念,请参见我的C++术语表(C++ glossary.)。

请注意,这仅仅是一个常见问题与解答的列表。它不能代替一本优秀教科书中那些经过精心挑选的范例与解释。它也不能象一本参考手册或语言标准那样,提供详细和准确的说明。有关C++的设计的问题,请参见《C++语言的设计和演变》(The Design and Evolution of C++)。关于C++语言与标准库的使用,请参见《C++程序设计语言》(The C++ Programming Language)。

目录:
我如何写这个非常简单的程序?
为什么编译要花这么长的时间?
为什么一个空类的大小不为0?
我必须在类声明处赋予数据吗?
为什么成员函数默认不是virtual的?
为什么析构函数默认不是virtual的?
为什么不能有虚拟构造函数?
为什么重载在继承类中不工作?
我能够在构造函数中调用一个虚拟函数吗?
有没有“指定位置删除”(placement delete)?
我能防止别人继承我自己的类吗?
为什么不能为模板参数定义约束(constraints)?
既然已经有了优秀的qsort()函数,为什么还需要一个sort()?
什么是函数对象(function object)?
我应该如何对付内存泄漏?
我为什么在捕获一个异常之后就不能继续?
为什么C++中没有相当于realloc()的函数?
如何使用异常?
怎样从输入中读取一个字符串?
为什么C++不提供“finally”的构造?
什么是自动指针(auto_ptr),为什么没有自动数组(auto_array)?
可以混合使用C风格与C++风格的内存分派与重新分配吗?
我为什么必须使用一个造型来转换*void?

我如何写这个非常简单的程序?

特别是在一个学期的开始,我常常收到许多关于编写一个非常简单的程序的询问。这个问题最典型的解决办法是,将它反复读上几遍,做某些事情,然后写出答案。下面是一个这样做的例子:

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

 int main()
 {
  vector<double> v;

  double d;
  while(cin>>d) v.push_back(d); // 读入元素
  if (!cin.eof()) {  // 检查输入是否出错
   cerr << "format error\n";
   return 1; // 返回一个错误
  }

  cout << "read " << v.size() << " elements\n";

  reverse(v.begin(),v.end());
  cout << "elements in reverse order:\n";
  for (int i = 0; i<v.size(); ++i) cout << v[i] << '\n';

  return 0; // 成功返回
 }

对这段程序的观察:

这是一段标准的ISO C++程序,使用了标准库(standard library)。标准库工具在命名空间std中声明,封装在没有.h后缀的头文件中。

如果你要在Windows下编译它,你需要将它编译成一个“控制台程序”(console application)。记得将源文件加上.cpp后缀,否则编译器可能会以为它是一段C代码而不是C++。

是的,main()函数返回一个int值。

读到一个标准的向量(vector)中,可以避免在随意确定大小的缓冲中溢出的错误。读到一个数组(array)中,而不产生“简单错误”(silly error),这已经超出了一个新手的能力——如果你做到了,那你已经不是一个新手了。如果你对此表示怀疑,我建议你阅读我的文章“将标准C++作为一种新的语言来学习”("Learning Standard C++ as a New Language"),你可以在本人著作列表(my publications
list)中下载到它。

!cin.eof()是对流的格式的检查。事实上,它检查循环是否终结于发现一个end-of-file(如果不是这样,那么意味着输入没有按照给定的格式)。更多的说明,请参见你的C++教科书中的“流状态”(stream
state)部分。

vector知道它自己的大小,因此我不需要计算元素的数量。

这段程序没有包含显式的内存管理。Vector维护一个内存中的栈,以存放它的元素。当一个vector需要更多的内存时,它会分配一些;当它不再生存时,它会释放内存。于是,使用者不需要再关心vector中元素的内存分配和释放问题。

程序在遇到输入一个“end-of-file”时结束。如果你在UNIX平台下运行它,“end-of-file”等于键盘上的Ctrl+D。如果你在Windows平台下,那么由于一个BUG它无法辨别“end-of-file”字符,你可能倾向于使用下面这个稍稍复杂些的版本,它使用一个词“end”来表示输入已经结束。

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

 int main()
 {
  vector<double> v;

  double d;
  while(cin>>d) v.push_back(d); // 读入一个元素
  if (!cin.eof()) {  // 检查输入是否失败
   cin.clear();  // 清除错误状态
   string s;
   cin >> s;  // 查找结束字符
   if (s != "end") {
    cerr << "format error\n";
    return 1; // 返回错误
   }
  }

  cout << "read " << v.size() << " elements\n";

  reverse(v.begin(),v.end());
  cout << "elements in reverse order:\n";
  for (int i = 0; i<v.size(); ++i) cout << v[i] << '\n';

  return 0; // 成功返回
 }

更多的关于使用标准库将事情简化的例子,请参见《C++程序设计语言》中的“漫游标准库”("Tour of the Standard Library")一章。

为什么编译要花这么长的时间?

你的编译器可能有问题。也许它太老了,也许你安装它的时候出了错,也许你用的计算机已经是个古董。
在诸如此类的问题上,我无法帮助你。

但是,这也是很可能的:你要编译的程序设计得非常糟糕,以至于编译器不得不检查数以百计的头文件和数万行代码。理论上来说,这是可以避免的。如果这是你购买的库的设计问题,你对它无计可施(除了换一个更好的库),但你可以将你自己的代码组织得更好一些,以求得将修改代码后的重新编译工作降到最少。
这样的设计会更好,更有可维护性,因为它们展示了更好的概念上的分离。

看看这个典型的面向对象的程序例子:

 class Shape {
 public:  // interface to users of Shapes
  virtual void draw() const;
  virtual void rotate(int degrees);
  // ...
 protected: // common data (for implementers of Shapes)
  Point center;
  Color col;
  // ...
 };

 class Circle : public Shape {
 public: 
  void draw() const;
  void rotate(int) { }
  // ...
 protected:
  int radius;
  // ...
 };

 class Triangle : public Shape {
 public: 
  void draw() const;
  void rotate(int);
  // ...
 protected:
  Point a, b, c;
  // ...
 }; 

设计思想是,用户通过Shape的public接口来操纵它们,而派生类(例如Circle和Triangle)的实现部分则共享由protected成员表现的那部分实现(implementation)。

这不是一件容易的事情:确定哪些实现部分是对所有的派生类都有用的,并将之共享出来。因此,与public接口相比,protected成员往往要做多得多的改动。举例来说,虽然理论上“中心”(center)对所有的
图形都是一个有效的概念,但当你要维护一个三角形的“中心”的时候,是一件非常麻烦的事情——对于三角形,当且仅当它确实被需要的时候,计算这个中心才是有意义的。

protected成员很可能要依赖于实现部分的细节,而Shape的用户(译注:user此处译为用户,指使用Shape类的代码,下同)却不见得必须依赖它们。举例来说,很多(大多数?)使用Shape的代码在逻辑上是与“颜色”无关的,但是由于Shape中“颜色”这个定义的存在,却可能需要一堆复杂的头文件,来结合操作系统的颜色概念。
当protected部分发生了改变时,使用Shape的代码必须重新编译——即使只有派生类的实现部分才能够访问protected成员。

于是,基类中的“实现相关的信息”(information helpful to implementers)对用户来说变成了象接口一样敏感的东西,它的存在导致了实现部分的不稳定,用户代码的无谓的重编译(当实现部分发生
改变时),以及将头文件无节制地包含进用户代码中(因为“实现相关的信息”需要它们)。有时这被称为“脆弱的基类问题”(brittle base class problem)。

一个很明显的解决方案就是,忽略基类中那些象接口一样被使用的“实现相关的信息”。换句话说,使用接口,纯粹的接口。也就是说,用抽象基类的方式来表示接口:

 class Shape {
 public:  // interface to users of Shapes
  virtual void draw() const = 0;
  virtual void rotate(int degrees) = 0;
  virtual Point center() const = 0;
  // ...

  // no data
 };

 class Circle : public Shape {
 public: 
  void draw() const;
  void rotate(int) { }
  Point center() const { return center; }
  // ...
 protected:
  Point cent;
  Color col;
  int radius;
  // ...
 };

 class Triangle : public Shape {
 public: 
  void draw() const;
  void rotate(int);
  Point center() const;
  // ...
 protected:
  Color col;
  Point a, b, c;
  // ...
 }; 

现在,用户代码与派生类的实现部分的变化之间的关系被隔离了。我曾经见过这种技术使得编译的时间减少了几个数量级。

但是,如果确实存在着对所有派生类(或仅仅对某些派生类)都有用的公共信息时怎么办呢?可以简单把这些信息封装成类,然后从它派生出实现部分的类:

 class Shape {
 public:  // interface to users of Shapes
  virtual void draw() const = 0;
  virtual void rotate(int degrees) = 0;
  virtual Point center() const = 0;
  // ...

  // no data
 };

 struct Common {
  Color col;
  // ...
 };
  
 class Circle : public Shape, protected Common {
 public: 
  void draw() const;
  void rotate(int) { }
  Point center() const { return center; }
  // ...
 protected:
  Point cent;
  int radius;
 };

 class Triangle : public Shape, protected Common {
 public: 
  void draw() const;
  void rotate(int);
  Point center() const;
  // ...
 protected:
  Point a, b, c;
 }; 

为什么一个空类的大小不为0?

要清楚,两个不同的对象的地址也是不同的。基于同样的理由,new总是返回指向不同对象的指针。
看看:

 class Empty { };

 void f()
 {
  Empty a, b;
  if (&a == &b) cout << "impossible: report error to compiler supplier";

  Empty* p1 = new Empty;
  Empty* p2 = new Empty;
  if (p1 == p2) cout << "impossible: report error to compiler supplier";
 } 

有一条有趣的规则:一个空的基类并不一定有分隔字节。
 struct X : Empty {
  int a;
  // ...
 };

 void f(X* p)
 {
  void* p1 = p;
  void* p2 = &p->a;
  if (p1 == p2) cout << "nice: good optimizer";
 }

这种优化是允许的,可以被广泛使用。它允许程序员使用空类以表现一些简单的概念。现在有些编译器提供这种“空基类优化”(empty base class optimization)。

我必须在类声明处赋予数据吗?

不必须。如果一个接口不需要数据时,无须在作为接口定义的类中赋予数据。代之以在派生类中给出它们。
参见“为什么编译要花这么长的时间?”。

有时候,你必须在一个类中赋予数据。考虑一下复合类(class complex)的情况:

 template<class Scalar> class complex {
 public:
  complex() : re(0), im(0) { }
  complex(Scalar r) : re(r), im(0) { }
  complex(Scalar r, Scalar i) : re(r), im(i) { }
  // ...

  complex& operator+=(const complex& a)
   { re+=a.re; im+=a.im; return *this; }
  // ...
 private:
  Scalar re, im;
 };

设计这种类型的目的是将它当做一个内建(built-in)类型一样被使用。在声明处赋值是必须的,以保证如下可能:建立真正的本地对象(genuinely local objects)(比如那些在栈中而不是在堆中分配
的对象),或者使某些简单操作被适当地inline化。对于那些支持内建的复合类型的语言来说,要获得它们提供的效率,真正的本地对象和inline化都是必要的。

为什么成员函数默认不是virtual的?

因为很多类并不是被设计作为基类的。例如复合类。

而且,一个包含虚拟函数的类的对象,要占用更多的空间以实现虚拟函数调用机制——往往是每个对象占
用一个字(word)。这个额外的字是非常可观的,而且在涉及和其它语言的数据的兼容性时,可能导致麻烦
(例如C或Fortran语言)。

要了解更多的设计原理,请参见《C++语言的设计和演变》(The Design and Evolution of C++)。

为什么析构函数默认不是virtual的?

因为很多类并不是被设计作为基类的。只有类在行为上是它的派生类的接口时(这些派生类往往在堆中分配,通过指针或引用来访问),虚拟函数才有意义。

那么什么时候才应该将析构函数定义为虚拟呢?当类至少拥有一个虚拟函数时。拥有虚拟函数意味着一个
类是派生类的接口,在这种情况下,一个派生类的对象可能通过一个基类指针来销毁。例如:

 class Base {
  // ...
  virtual ~Base();
 };

 class Derived : public Base {
  // ...
  ~Derived();
 };

 void f()
 {
  Base* p = new Derived;
  delete p; // 虚拟析构函数保证~Derived函数被调用
 }

如果基类的析构函数不是虚拟的,那么派生类的析构函数将不会被调用——这可能产生糟糕的结果,例如派生类的资源不会被释放。

为什么不能有虚拟构造函数?

虚拟调用是一种能够在给定信息不完全(given partial information)的情况下工作的机制。特别地,虚拟允许我们调用某个函数,对于这个函数,仅仅知道它的接口,而不知道具体的对象类型。但是要建立一个对象,你必须拥有完全的信息。特别地,你需要知道要建立的对象的具体类型。因此,对构造函数的调用不可能是虚拟的。

当要求建立一个对象时,一种间接的技术常常被当作“虚拟构造函数”来使用。有关例子,请参见《C++程序设计语言》第三版15.6.2.节。

下面这个例子展示一种机制:如何使用一个抽象类来建立一个适当类型的对象。

struct F { // 对象建立函数的接口
 virtual A* make_an_A() const = 0;
 virtual B* make_a_B() const = 0;
};

void user(const F& fac)
{
 A* p = fac.make_an_A(); // 将A作为合适的类型
 B* q = fac.make_a_B(); // 将B作为合适的类型
 // ...
}

struct FX : F {
 A* make_an_A() const { return new AX(); } // AX是A的派生
 B* make_a_B() const { return new BX(); } // AX是B的派生
};

struct FY : F {
 A* make_an_A() const { return new AY(); } // AY是A的派生
 B* make_a_B() const { return new BY(); } // BY是B的派生

};

int main()
{
 user(FX()); // 此用户建立AX与BX
 user(FY()); // 此用户建立AY与BY
 // ...
}

这是所谓的“工厂模式”(the factory pattern)的一个变形。关键在于,user函数与AX或AY这样的类的信息被完全分离开来了。

为什么重载在继承类中不工作?

这个问题(非常常见)往往出现于这样的例子中:

 #include<iostream>
 using namespace std;

 class B {
 public:
  int f(int i) { cout << "f(int): "; return i+1; }
  // ...
 };

 class D : public B {
 public:
  double f(double d) { cout << "f(double): "; return d+1.3; }
  // ...
 };

 int main()
 {
  D* pd = new D;

  cout << pd->f(2) << '\n';
  cout << pd->f(2.3) << '\n';
 }

它输出的结果是:

 f(double): 3.3
 f(double): 3.6

而不是象有些人猜想的那样:

 f(int): 3
 f(double): 3.6

换句话说,在B和D之间并没有发生重载的解析。编译器在D的区域内寻找,找到了一个函数double f(double),并执行了它。它永远不会涉及(被封装的)B的区域。在C++中,没有跨越区域的重载——
对于这条规则,继承类也不例外。更多的细节,参见《C++语言的设计和演变》和《C++程序设计语言》。

但是,如果我需要在基类和继承类之间建立一组重载的f()函数呢?很简单,使用using声明:

 class D : public B {
 public:
  using B::f; // make every f from B available
  double f(double d) { cout << "f(double): "; return d+1.3; }
  // ...
 };

进行这个修改之后,输出结果将是:

 f(int): 3
 f(double): 3.6

这样,在B的f()和D的f()之间,重载确实实现了,并且选择了一个最合适的f()进行调用。

我能够在构造函数中调用一个虚拟函数吗?

可以,但是要小心。它可能不象你期望的那样工作。在构造函数中,虚拟调用机制不起作用,因为继承类的重载还没有发生。对象先从基类被创建,“基类先于继承类(base before derived)”。

看看这个:

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

 class B {
 public:
  B(const string& ss) { cout << "B constructor\n"; f(ss); }
  virtual void f(const string&) { cout << "B::f\n";}
 };

 class D : public B {
 public:
  D(const string & ss) :B(ss) { cout << "D constructor\n";}
  void f(const string& ss) { cout << "D::f\n"; s = ss; }
 private:
  string s;
 };

 int main()
 {
  D d("Hello");
 }

程序编译以后会输出:

 B constructor
 B::f
 D constructor

注意不是D::f。设想一下,如果出于不同的规则,B::B()可以调用D::f()的话,会产生什么样的后果:
因为构造函数D::D()还没有运行,D::f()将会试图将一个还没有初始化的字符串s赋予它的参数。结果很可能是导致立即崩溃。

析构函数在“继承类先于基类”的机制下运行,因此虚拟机制的行为和构造函数一样:只有本地定义(local definitions)被使用——不会调用虚拟函数,以免触及对象中的(现在已经被销毁的)继承类的部分。

更多的细节,参见《C++语言的设计和演变》13.2.4.2和《C++程序设计语言》15.4.3。

有人暗示,这只是一条实现时的人为制造的规则。不是这样的。事实上,要实现这种不安全的方法倒是非常容易的:在构造函数中直接调用虚拟函数,就象调用其它函数一样。但是,这样就意味着,任何虚拟函数都无法编写了,因为它们需要依靠基类的固定的创建(invariants established by base classes)。这将会导致一片混乱。

有没有“指定位置删除”(placement delete)?

没有,不过如果你需要的话,可以自己写一个。

看看这个指定位置创建(placement new),它将对象放进了一系列Arena中;

       class Arena {
        public:
                void* allocate(size_t);
                void deallocate(void*);
                // ...
        };

        void* operator new(size_t sz, Arena& a)
        {
                return a.allocate(sz);
        }

        Arena a1(some arguments);
        Arena a2(some arguments);

这样实现了之后,我们就可以这么写:

        X* p1 = new(a1) X;
        Y* p2 = new(a1) Y;
        Z* p3 = new(a2) Z;
        // ...

但是,以后怎样正确地销毁这些对象呢?没有对应于这种“placement new”的内建的“placement delete”,原因是,没有一种通用的方法可以保证它被正确地使用。在C++的类型系统中,没有什么东西可以让我们确认,p1一定指向一个由Arena类型的a1分派的对象。p1可能指向任何东西分派的任何一块地方。

然而,有时候程序员是知道的,所以这是一种方法:

        template<class T> void destroy(T* p, Arena& a)
        {
                if (p) {
                        p->~T();  // explicit destructor call
                        a.deallocate(p);
                }
        }

现在我们可以这么写:

        destroy(p1,a1);
        destroy(p2,a2);
        destroy(p3,a3);

如果Arena维护了它保存着的对象的线索,你甚至可以自己写一个析构函数,以避免它发生错误。

这也是可能的:定义一对相互匹配的操作符new()和delete(),以维护《C++程序设计语言》15.6中
的类继承体系。参见《C++语言的设计和演变》10.4和《C++程序设计语言》19.4.5。

我能防止别人继承我自己的类吗?

可以,但你为什么要那么做呢?这是两个常见的回答:

效率:避免我的函数被虚拟调用
安全:保证我的类不被用作一个基类(例如,保证我能够复制对象而不用担心出事)

根据我的经验,效率原因往往是不必要的担心。在C++中,虚拟函数调用是如此之快,以致于它们在一个包含虚拟函数的类中被实际使用时,相比普通的函数调用,根本不会产生值得考虑的运行期开支。注意,
仅仅通过指针或引用时,才会使用虚拟调用机制。当直接通过对象名字调用一个函数时,虚拟函数调用的开支可以被很容易地优化掉。

如果确实有真正的需要,要将一个类封闭起来以防止虚拟调用,那么可能首先应该问问为什么它们是虚拟的。我看见过一些例子,那些性能表现不佳的函数被设置为虚拟,没有其他原因,仅仅是因为“我们习惯这么干”。

这个问题的另一个部分,由于逻辑上的原因如何防止类被继承,有一个解决方案。不幸的是,这个方案并不完美。它建立在这样一个事实的基础之上,那就是:大多数的继承类必须建立一个虚拟的基类。这是一个例子:

 class Usable;

 class Usable_lock {
  friend class Usable;
 private:
  Usable_lock() {}
  Usable_lock(const Usable_lock&) {}
 };

 class Usable : public virtual Usable_lock {
  // ...
 public:
  Usable();
  Usable(char*);
  // ...
 };

 Usable a;

 class DD : public Usable { };

 DD dd;  // 错误: DD::DD() 不能访问
         // Usable_lock::Usable_lock()是一个私有成员

(来自《C++语言的设计和演变》11.4.3)

为什么不能为模板参数定义约束(constraints)?

可以的,而且方法非常简单和通用。

看看这个:

        template<class Container>
        void draw_all(Container& c)
        {
                for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
        }

如果出现类型错误,可能是发生在相当复杂的for_each()调用时。例如,如果容器的元素类型是int,我们将得到一个和for_each()相关的含义模糊的错误(因为不能够对对一个int值调用Shape::draw
的方法)。

为了提前捕捉这个错误,我这样写:

        template<class Container>
        void draw_all(Container& c)
        {
                Shape* p = c.front(); // accept only containers of Shape*s

                for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
        }

对于现在的大多数编译器,中间变量p的初始化将会触发一个易于了解的错误。这个窍门在很多语言中都是通用的,而且在所有的标准创建中都必须这样做。在成品的代码中,我也许可以这样写:

 template<class Container>
        void draw_all(Container& c)
        {
                typedef typename Container::value_type T;
                Can_copy<T,Shape*>(); // accept containers of only Shape*s

                for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
        }

这样就很清楚了,我在建立一个断言(assertion)。Can_copy模板可以这样定义:

 template<class T1, class T2> struct Can_copy {
  static void constraints(T1 a, T2 b) { T2 c = a; b = a; }
  Can_copy() { void(*p)(T1,T2) = constraints; }
 };

Can_copy(在运行时)检查T1是否可以被赋值给T2。Can_copy<T,Shape*>检查T是否是Shape*类
型,或者是一个指向由Shape类公共继承而来的类的对象的指针,或者是被用户转换到Shape*类型的某个类型。注意这个定义被精简到了最小:

一行命名要检查的约束,和要检查的类型
一行列出指定的要检查的约束(constraints()函数)
一行提供触发检查的方法(通过构造函数)

注意这个定义有相当合理的性质:

你可以表达一个约束,而不用声明或复制变量,因此约束的编写者可以用不着去设想变量如何被初始化,对象是否能够被复制,被销毁,以及诸如此类的事情。(当然,约束要检查这些属性的情况时例外。)
使用现在的编译器,不需要为约束产生代码
定义和使用约束,不需要使用宏
当约束失败时,编译器会给出可接受的错误信息,包括“constraints”这个词(给用户一个线索),约
束的名字,以及导致约束失败的详细错误(例如“无法用double*初始化Shape*”)。

那么,在C++语言中,有没有类似于Can_copy——或者更好——的东西呢?在《C++语言的设计和演变》中,对于在C++中实现这种通用约束的困难进行了分析。从那以来,出现了很多方法,来让约束类变得更加容易编写,同时仍然能触发良好的错误信息。例如,我信任我在Can_copy中使用的函数指针的方式,它源自Alex Stepanov和Jeremy Siek。我并不认为Can_copy()已经可以标准化了——它需要更多的使用。同样,在C++社区中,各种不同的约束方式被使用;到底是哪一种约束模板在广泛的使用中被证明是最有效的,还没有达成一致的意见。

但是,这种方式非常普遍,比语言提供的专门用于约束检查的机制更加普遍。无论如何,当我们编写一个模板时,我们拥有了C++提供的最丰富的表达力量。看看这个:

template<class T, class B> struct Derived_from {
 static void constraints(T* p) { B* pb = p; }
 Derived_from() { void(*p)(T*) = constraints; }
};

template<class T1, class T2> struct Can_copy {
 static void constraints(T1 a, T2 b) { T2 c = a; b = a; }
 Can_copy() { void(*p)(T1,T2) = constraints; }
};

template<class T1, class T2 = T1> struct Can_compare {
 static void constraints(T1 a, T2 b) { a==b; a!=b; a<b; }
 Can_compare() { void(*p)(T1,T2) = constraints; }
};

template<class T1, class T2, class T3 = T1> struct Can_multiply {
 static void constraints(T1 a, T2 b, T3 c) { c = a*b; }
 Can_multiply() { void(*p)(T1,T2,T3) = constraints; }
};

struct B { };
struct D : B { };
struct DD : D { };
struct X { };

int main()
{
 Derived_from<D,B>();
 Derived_from<DD,B>();
 Derived_from<X,B>();
 Derived_from<int,B>();
 Derived_from<X,int>();

 Can_compare<int,float>();
 Can_compare<X,B>();
 Can_multiply<int,float>();
 Can_multiply<int,float,double>();
 Can_multiply<B,X>();
 
 Can_copy<D*,B*>();
 Can_copy<D,B*>();
 Can_copy<int,B*>();
}

// 典型的“元素必须继承自Mybase*”约束:

template<class T> class Container : Derived_from<T,Mybase> {
 // ...
};

事实上,Derived_from并不检查来源(derivation),而仅仅检查转换(conversion),不过这往往是一个更好的约束。为约束想一个好名字是很难的。

- 作者: 小浪 2008年09月9日, 星期二 17:07  回复(0) |  引用(0) 加入博采

完全激活窗口的实现
ShowWindow(   SW_SHOW   );  
  SetWindowPos(   &wndTopMost   ,   0   ,   0   ,   0   ,   0   ,   SWP_NOSIZE|SWP_NOMOVE);  
 SetWindowPos(   &wndNoTopMost   ,   0   ,   0   ,   0   ,   0   ,   SWP_NOSIZE|SWP_NOMOVE   );  
  SetForegroundWindow();  
  HWND   hCurWnd   =   NULL;  
  DWORD   lMyID;  
  DWORD   lCurID;  
  hCurWnd   =   ::GetForegroundWindow();  
  lMyID   =   ::GetCurrentThreadId();  
  lCurID   =   ::GetWindowThreadProcessId(hCurWnd,   NULL);  
  ::AttachThreadInput(   lMyID,   lCurID,   TRUE);  
  SetForegroundWindow();  
  ::AttachThreadInput(   lMyID,   lCurID,   FALSE);  

- 作者: 小浪 2008年09月1日, 星期一 22:12  回复(0) |  引用(0) 加入博采

字节对齐详解

一.什么是字节对齐,为什么要对齐?

    现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
    对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。

二.字节对齐对程序的影响:

    先让我们看几个例子吧(32bit,x86环境,gcc编译器):
设结构体如下定义:
struct A
{
    int a;
    char b;
    short c;
};
struct B
{
    char b;
    int a;
    short c;
};
现在已知32位机器上各种数据类型的长度如下:
char:1(有符号无符号同)    
short:2(有符号无符号同)    
int:4(有符号无符号同)    
long:4(有符号无符号同)    
float:4    double:8
那么上面两个结构大小如何呢?
结果是:
sizeof(strcut A)值为8
sizeof(struct B)的值却是12

结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7字节。
之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct C)值是8。
修改对齐值为1:
#pragma pack (1) /*指定按1字节对齐*/
struct D
{
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct D)值为7。
后面我们再讲解#pragma pack()的作用.

三.编译器是按照什么样的原则进行对齐的?

    先让我们看四个重要的基本概念:
1.数据类型自身的对齐值:
  对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
3.指定对齐值:#pragma pack (value)时的指定对齐值value。
4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
有 了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是 表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数 据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数 倍,结合下面例子理解)。这样就不能理解上面的几个例子的值了。
例子分析:
分析例子B;
struct B
{
    char b;
    int a;
    short c;
};
假 设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指定 对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4, 所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为 2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的 都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求, 0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B 共有12个字节,sizeof(struct B)=12;
其实如果就这一个就来说它已将满足字节对齐了, 因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那 么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一 个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据,其 自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只 是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.
同理,分析上面例子C:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
第 一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1= 0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续 字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放
在0x0006、0x0007中,符合 0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0,C 只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.

四.如何修改编译器的默认对齐值?

1.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改,默认是8字节。
2.在编码时,可以这样动态修改:#pragma pack .注意:是pragma而不是progma.

五.针对字节对齐,我们在编程中如何考虑?


    如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照 类型大小从小到大声明,尽量减少中间的填补空间.还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做 法是显式的插入reserved成员:
         struct A{
           char a;
           char reserved[3];//使用空间换时间
           int b;
}

reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用.

六.字节对齐可能带来的隐患:

    代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:
unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;

p=&i;
*p=0x00;
p1=(unsigned short *)(p+1);
*p1=0x0000;
最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。
在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.

七.如何查找与字节对齐方面的问题:

如果出现对齐或者赋值问题首先查看
1. 编译器的big little端设置
2. 看这种体系本身是否支持非对齐访问
3. 如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作。

八.相关文章:转自http://blog.csdn.net/goodluckyxl/archive/2005/10/17/506827.aspx

 ARM下的对齐处理
from DUI0067D_ADS1_2_CompLib

3.13 type  qulifiers

有部分摘自ARM编译器文档对齐部分

对齐的使用:
1.__align(num)
   这个用于修改最高级别对象的字节边界。在汇编中使用LDRD或者STRD时
   就要用到此命令__align(8)进行修饰限制。来保证数据对象是相应对齐。
   这个修饰对象的命令最大是8个字节限制,可以让2字节的对象进行4字节
   对齐,但是不能让4字节的对象2字节对齐。
   __align是存储类修改,他只修饰最高级类型对象不能用于结构或者函数对象。
  
2.__packed
  __packed是进行一字节对齐
  1.不能对packed的对象进行对齐
  2.所有对象的读写访问都进行非对齐访问
  3.float及包含float的结构联合及未用__packed的对象将不能字节对齐
  4.__packed对局部整形变量无影响
  5.强制由unpacked对象向packed对象转化是未定义,整形指针可以合法定
  义为packed。
     __packed int* p;  //__packed int 则没有意义
  6.对齐或非对齐读写访问带来问题
  __packed struct STRUCT_TEST
 {
  char a;
  int b;
  char c;
 }  ;    //定义如下结构此时b的起始地址一定是不对齐的
         //在栈中访问b可能有问题,因为栈上数据肯定是对齐访问[from CL]
//将下面变量定义成全局静态不在栈上
static char* p;
static struct STRUCT_TEST a;
void Main()
{
 __packed int* q;  //此时定义成__packed来修饰当前q指向为非对齐的数据地址下面的访问则可以

 p = (char*)&a;         
 q = (int*)(p+1);     
 
 *q = 0x87654321;
/*  
得到赋值的汇编指令很清楚
ldr      r5,0x20001590 ; = #0x12345678
[0xe1a00005]   mov      r0,r5
[0xeb0000b0]   bl       __rt_uwrite4  //在此处调用一个写4byte的操作函数
     
[0xe5c10000]   strb     r0,[r1,#0]   //函数进行4次strb操作然后返回保证了数据正确的访问
[0xe1a02420]   mov      r2,r0,lsr #8
[0xe5c12001]   strb     r2,[r1,#1]
[0xe1a02820]   mov      r2,r0,lsr #16
[0xe5c12002]   strb     r2,[r1,#2]
[0xe1a02c20]   mov      r2,r0,lsr #24
[0xe5c12003]   strb     r2,[r1,#3]
[0xe1a0f00e]   mov      pc,r14
*/

/*
如果q没有加__packed修饰则汇编出来指令是这样直接会导致奇地址处访问失败
[0xe59f2018]   ldr      r2,0x20001594 ; = #0x87654321
[0xe5812000]   str      r2,[r1,#0]
*/

//这样可以很清楚的看到非对齐访问是如何产生错误的
//以及如何消除非对齐访问带来问题
//也可以看到非对齐访问和对齐访问的指令差异导致效率问题
}

- 作者: 小浪 2008年08月7日, 星期四 16:06  回复(0) |  引用(0) 加入博采

内存地址对齐及大小端
 我们常常看到“alignment", "endian"之类的字眼, 但很少有C语言教材提到这些概念. 实际上它们是与处理器与内存接口, 编译器类型密切相关的.

考虑这样一个例子: 两个异构的CPU进行通信, 定义了这样一个结果来传递消息:

struct Message
{
short opcode;
char subfield;
long message_length;
char version;
short destination_processor;
}message;

用这样一个结构来传递消息貌似非常方便, 但也引发了这样一个问题: 若这两种不同的CPU对该结构的定义不一样,  两者就会对消息有不同的理解. 有可能导致二义性. 会引发二义性的有这两个方面:

  1. 内存地址对齐
  2. 大小端定义

本文先介绍内存地址对齐和大小端的概念, 再回头来看这个例子就豁然开朗了.

内存地址对齐


洋名叫做" Byte Alignment".

大部分16位和32位的CPU不允许将字或者长字存储到内存中的任意地址. 比如Motorola 68000不允许将16位的字存储到奇数地址中, 将一个16位的字写到奇数地址将引发异常.

实际上, 对于c中的字节组织, 有这样的对齐规则:
  • 单个字节(char)能对齐到任意地址
  • 2字节(short)以2字节边界对齐
  • 4字节(int, long)以4字节边界对齐
不同CPU的对其规则可能不同, 请参考手册.

为什么会有上述的限制呢? 理解了内存组织, 就会清楚了
CPU通过地址总线来存取内存中的数据, 32位的CPU的地址总线宽度既为32位置, 标为A[0:31]. 在一个总线周期内, CPU从内存读/写32位. 但是CPU只能在能够被4整除的地址进行内存访问, 这是因为: 32位CPU不使用地址总线的A1和A2. (比如ARM, 它的A[0:1]用于字节选择, 用于逻辑控制, 而不和存储器相连, 存储器连接到A[2:31].)

访问内存的最小单位是字节(byte), A0和A1不使用, 那么对于地址来说, 最低两位是无效的, 所以它只能识别能被4整除的地址了. 在4字节中, 通过A0和A1确定某一个字节.

再看看刚才的message结构, 你想想它占了多少字节? 别想当然的以为是10个字节. 实际上它占了12个字节. 不信? 用sizeof(message)看吧. 对于结构体, 编译器会针对起中的元素添加"pad"以满足字节对齐规则. message会被编译器改为下面的形式:

struct Message
{
short opcode;
char subfield;
char pad1; // Pad to start the long word at a 4 byte boundary
long message_length;
char version;
char pad2; // Pad to start a short at a 2 byte boundary
short destination_processor;
char pad3[4]; // Pad to align the complete structure to a 16 byte boundary
};
如果不同的编译器采用不同的对齐规则, 对传递message可就麻烦了.

Byte Endian


是指字节在内存中的组织,所以也称它为Byte Ordering.   

        对于数据中跨越多个字节的对象, 我们必须为它建立这样的约定:

(1) 它的地址是多少?

(2) 它的字节在内存中是如何组织的?

        针对第一个问题,有这样的解释:

        对于跨越多个字节的对象,一般它所占的字节都是连续的, 它的地址等于它所占字节最低地址.(链表可能是个例外, 但链表的地址可看作链表头的地址).

比如: int x, 它的地址为0x100. 那么它占据了内存中的Ox100, 0x101, 0x102, 0x103这四个字节.

        上面只是内存字节组织的一种情况: 多字节对象在内存中的组织有一般有两种约定. 考虑一个W位的整数. 它的各位表达如下:

[Xw-1, Xw-2, ... , X1, X0]

        它的MSB (Most Significant Byte, 最高有效字节)为[Xw-1, Xw-2, ... Xw-8]; LSB (Least Significant Byte, 最低有效字节)为 [X7, X6, ..., X0]. 其余的字节位于MSB, LSB之间. 

        LSB和MSB谁位于内存的最低地址, 即谁代表该对象的地址? 这就引出了大端(Big Endian)与小端(Little Endian)的问题。

        如果LSB在MSB前面, 既LSB是低地址, 则该机器是小端; 反之则是大端. DEC (Digital Equipment Corporation, 现在是Compaq公司的一部分)和Intel的机器一般采用小端. IBM, Motorola, Sun的机器一般采用大端. 当然, 这不代表所有情况. 有的CPU即能工作于小端, 又能工作于大端, 比如ARM, PowerPC, Alpha. 具体情形参考处理器手册.

        举个例子来说名大小端:  比如一个int x, 地址为0x100, 它的值为0x1234567. 则它所占据的0x100, 0x101, 0x102, 0x103地址组织如下图:

 

        0x01234567的MSB为0x01, LSB为0x67. 0x01在低地址(或理解为"MSB出现在LSB前面,因为这里讨论的地址都是递增的), 则为大端; 0x67在低地址则为小端.

认清这样一个事实: C中的数据类型都是从内存的低地址向高地址扩展,取址运算"&"都是取低地址.

两个测试Bit Endian的小程序


method_1

#include <stdio.h>

int main(int argc, char *argv[])
{

  int c = 1;
  if ((*(char *)&c) == 1) {
    printf("little endian\n");
  }
  else
    printf("big endian");

  return 0;
}

        int c 在内存中的表达为: 0x00000001. (这里假设int为4字节). 用char可以截取一个字节. LSB为0x01, 若它出现在c的低地址, 则为小端.

method_2

#include <stdio.h>

int main(void)
{
/* Each component to a union type is allocated storage at the beginning of the union */
         
  union {
    short n;
    char c[sizeof(short)];
  }un;
 
  un.n = 0x0102;
 
  if ((un.c[0] == 1 && un.c[1] == 2))
    printf("big endian\n");
  else if ((un.c[0] == 2 && un.c[1] == 1))
    printf("little endian\n");
  else
    printf("error!\n");
  return 0;
}

      
        union中元素的起始地址都是相同的——位于联合的开始. 用char来截取感兴趣的字节.
     

区分大端与小端有什么用呢? 如果两个不同Endian的机器进行通信时, 就有必要区分了


- 作者: 小浪 2008年08月7日, 星期四 16:04  回复(0) |  引用(0) 加入博采

明明白白Inf文件
INF文件全称Information File文件,是Winodws操作系统下用来描述设备或文件等数据信息的文件。INF文件是由标准的ASCII 码组成,您可以用任何一款文字编辑器查看修改其中的内容。一般我们总是认为INF文件是系统设备的驱动程序,其实这是错误的认识,Windows之所以在安装某些硬件的驱动时提示需要INF文件是因为INF文件为该设备提供了一个全面描述硬件参数和相应驱动文件(DLL文件)的信息。就好比我们看着说明书安装电脑硬件一样,我们就是Windows系统,说明书就是INF文件。INF文件功能非常强大,几乎能完成日常操作的所有功能。您可以把它看成是 Windows系统底下的超强批初理。要熟练掌握和理解甚至是编写INF文件需要对其内部结构有相当的认识。下面就让我们来深入到INF文件中的内部一窥其真面貌吧!

INF文件的组成有节(Sections),键(Key)和值(value)三部分。
关键节有
[Version]版本描述信息,主要用于版本控制。
[Strings]字符串信息,用于常量定义。
[DestinationDirs]定义系统路径信息。
[SourceDisksNames]指明源盘信息。
[SourceDisksNames]指明源盘文件名。
[DefaultInstall]开始执行安装。
其它的节可以自定义,下面用一实例来具体讲解。


程序代码
[Version]
Signature=$Chicago$
Provider=%Author%

[Strings]
Product="添加文件关联演示"
Version="1.0"
Author="Xunchi"
Copyright="Copyright 2005"
CustomFile="inf" ;修改您需要的文件名后缀
Program="NOTEPAD.EXE" ;修改您需要关联的应用程序名

[Add.Reg]
HKCR,"."%CustomFile%,"",FLG_ADDREG_TYPE_SZ ,%CustomFile%File
HKCR,%CustomFile%File,"",FLG_ADDREG_TYPE_SZ,安装信息
HKCR,%CustomFile%"File\shell","",FLG_ADDREG_TYPE_SZ,open
HKCR,%CustomFile%"File\shell\open\command","",FLG_ADDREG_TYPE_SZ,%program% %1

[DefaultInstall]
AddReg=Add.Reg

  在[Version]节中"Signature"项定义了该INF文件需要运行在何种操作系统版本中。有$Windows NT$, $ Chicago$, or $Windows 95$三个值供选择,一般选择$Chicago$即可。项Provider中定义了该文件的创作来源,% Author%指引用Author项的值。您也可自定其它项来描述该INF文件的版本信息。该INF文件的作用是关联文件,所以主要是对注册表的操作,我们来看[Add.Reg]节,共四条语句,格式都是一样。HKCR表示根HKEY_CLASSES_ROOT,第二个参数是子键的路径名,第三个参数是表明值的类型,最后是值(具体见附表)。以上都是对操作的定义与过程,在节[DefaultInstall]中是开始执行要安装的流程,AddReg表明是对注册表进行操作,操作对象是Add.Reg节中的定义。如果您把AddReg换成DelReg则是删除注册表中的键值。当鼠标单击该INF文件在弹出的菜单中选择“安装”就开始执行您所定义的操作。该示例在系统的INF文件右键菜单中增加了查看编辑功能并设置了默认动作,因为在安装了不了解的INF文件有可能对系统产生不良的影响,这样双击文件就可打开编辑该文件了。


  再看看INF文件在文件操作方面的能力吧。请看下面的一个例子。

程序代码
[Version]
Signature=$Chicago$
Provider=%Author%
[Strings]
Product="文件复制和安装演示"
Version="1.0"
Author="Xunchi"
Copyright="Copyright 2005"

[FileList]
ProcessList.exe ;此文件已在当前目录下,下同。

[FileList1]
Wordpad.exe
[DestinationDirs]
FileList=11 ;安装到Windows的系统目录
FileList1=10 ;安装到Windows目录
[DefaultInstall]
Copyfiles=FileList,FileList1

  相同的节的作用与上一例类似,请注意新出现的节[FileList],这是我自定义的节名,它表示了一个文件组,[FileList1]也类似。在节[DestinationDirs]中需定义每个文件组复制到的目录(各个常量的意义见附表)。Copyfiles指明了需要进行复制的文件组。
  INF文件的操作还包括服务(NT系统)程序的安装和卸载,INI文件的转换等。由于这些操作都比较的复杂和繁琐,且有一定的危险性故下次有机会再向大家进行深入探讨。
  最后我们来看一下INF文件的执行机制,这时你也许要问不就是简单的执行一下“安装”吗?知其然不知其所以然知识水平是不会提高的。在“文件夹选项”中的“文件类型”找到INF文件的“安装”命令看到一串命令。“rundll32.exe setupapi, InstallHinfSection DefaultInst_all 132 %1”它表示了运行Dll文件setupapi.dll中的命令 InstallHinfSection并传递给它起始节的名字 DefaultInstall。可见起始节是可以自定义的。INF文件的执行也可用在各种支持API调用的编程工具中。至此INF文件的结构和运行机制我们已基本了解,现在就让你的思维开动起来,让它更好的为我们工作吧。


注册表操作的常量定义:
----------------------------------------------------------
常量 根值
HKCR HKEY_CLASSES_ROOT.
HKCU HKEY_CURRENT_USER.
HKLM HKEY_LOCAL_MACHINE.
HKU HKEY_USERS.
-----------------------------------------------------------
FLG_ADDREG_APPEND 在多字符串后添加字符
FLG_ADDREG_TYPE_SZ 字符类型
FLG_ADDREG_TYPE_MULTI_SZ 字符串类型
FLG_ADDREG_TYPE_EXPAND_SZ 扩展字符串类型
FLG_ADDREG_TYPE_BINARY 二进制值
FLG_ADDREG_TYPE_DWORD DWord值
FLG_ADDREG_TYPE_NONE NULL值
----------------------------------------------------------


[DestinationDirs]节中所定义的常量路径
----------------------------------------------------------
01 源目录(后跟路径)
10 Windows目录
11 Windows系统目录
12 驱动目录
17 INF文件目录
18 帮助文件目录
20 字体目录
21 根目录
24 应用程序目录
25 共享目录
30 当前根目录
50 System目录
51 Spool 目录
52 Spool 驱动目录
53 用户配置目录
----------------------------------------------------------

[DefaultInstall]节中定义的操作
----------------------------------------------------------
LogConfig Log日志文件配置
Copyfiles 复制文件
Renfiles 文件改名
Delfiles 删除文件
UpdateInis 更新Inis
UpdateIniFields 更新Ini字段
AddReg 添加注册项
DelReg 删除注册项
Ini2Reg Ini文件转换为Reg文件
-----------------------------------------------------------
INF的功能

1 复制文件、删除文件、或重新命名文件。
2 新增或删除注册表(Registry)中的项目。
3 修改重要的系统设置文件(如 Autoexec.bat、Config.sys、.INI 等)

INF的规则

INF是纯文本文件,它是分节的,这点和INI文件类似,每节以"[]"扩起来,每一个节名最长为255个字符(Windows 2000/XP/2003操作系统中)或28个字符(Windows 98操作系统中)。在节与节之间的内容叫条目,每一个节又是由许多的条目组成的,每一个条目都由=分开,如a="b"。如果每一个条目的等号后有多个值,则每一个值之间用","号分隔开。INF对大小写不敏感,行注释语句命令是";",类似VB里的’。如果一行写不下,使用"\"来换行。

INF的运行

.INF文件是由Windows的SetupAPI解释执行的脚本文件,它的运行过程很简单,是一种线性的执行,线性的意思就是.INF文件的运行过程不存在分支语句,也就是没有条件语句,一旦开始执行,就是沿着固定的路线运行。它的运行是按照节为单位来执行的,从某一个[Install]节开始执行,从上到下执行该节中的条目,如果该条目是一个节,那么就一条条执行子节中的条目,如此递归执行。在WINDOW上运行只要右击这个文件,点击安装即可。

INF的语法结构

;指定版本和签名节
[VERSION]
;系统根据Signuture看是不是适合当前版本,如果适合的话就执行,否则不执行,当然强制安装是可以的
;用于WIN9X
Signature="$CHICAGO$"
;WINNT+
;Signature="$Windows NT$"
;指定安装文件布局,该行是可选的,如果没有提供布局信息文件,则在INF文件内必须包含[SourceDisksNames]和[SourceDisksFiles]节
LayoutFile=filename.inf
[SourceDisksNames]节
[SourceDisksNames]节罗列源文件所在盘符序列码、盘描述符、盘卷标号和盘序列号。
[SourceDisksNames]节内语句的语法为:
disk-ordinal=“disk-description”,disk-label,disk-serial-number
其中disk-ordinal为必选项,是盘符序列码,标识一个源盘,具有惟一性,一般可设置为从1开始递增的整数,0不是一个有效的盘符序列码。当存在多个源盘时,盘符序列码之间不能重复。
disk-description为必选项,是盘描述符,用双引号括起的字符串或字符串宏描述盘的内容或目的。安装引擎将该字符串显示在对话框内以提示用户。
disk-label为源盘的卷标识。
disk-serial-number未使用,但必须被设置为0。
[SourceDisksFiles]节
[SourceDisksFiles]节指定安装时使用的源文件和盘符序列码、盘描述符。 [SourceDisksFiles]节内语句的语法为:
file-name=disk-number[,subdir] [,file-size]
其中file-name为必选项,是源盘上文件的名称。
disk-number是包含file-name指定文件所在源盘的盘符序列码,该盘符序列码需在[SourceDisksNames]节中列出,并大于或等于1。
Subdir为可选项,指定文件所在源盘的子目录,如省略则源盘为缺省安装路径。
file-size为可选项,表明文件的大小,以字节为单位。

[DestinationDirs]
;指定CopyFiles、RenFiles或DelFiles入口的缺省操作目录
;语法file-list-section=LDID,[Subdir]
;LDID列表如下:
;01 ;current directory
;04 ;backup directory
;10 ;windows directory
;11 ;system dir
;12 ;iosubsys
;13 ;command
;14 ;control panel directory
;15 ;printers directory
;16 ;workgrou dir
;17 ;inf dir
;18 ;help dir
;19 ;administration dir
;20 ;fonts
;21 ;viewers
;22 ;vmm32
;23 ;color dir
;25 ;shared dir
;26 ;winboot
;28 ;host winboot
;30 ;root of boot drive
;31 ;root of host drive of a virtual boot drive
;32 ;old windows dir if exists

;以下例子为安装到window\web目录下
;DefaultDestDir=10,"web"
;[Install]节提供了一个INF文件安装过程的总览,它识别文件内其他包含安装信息节的详细动作,是Windows内建安装函数识别安装过程和内容的真正入口
[Install]
;[Install]节分[DefaultInstall]和[OtherInstall]两类
;[DefaultInstall]节节名DefaultInstall如前面表格内容所述被显式地在注册表中指定。
;该节也是系统获取INF文件中安装信息的首要入口,当用户右击INF文件选“安装”时该节内容被执行。
;[OtherInstall]与[DefaultInstall]节遵循相同的语法,但必须被显式地调用,常被用来定义反安装动作

;缺省安装节
[DefaultInstall]
;指明添加注册表的子节,等号后面的为自定义节名,例子见[add]节
ADDREG=add

;指明要删除的注册表子节,等号后面的为自定义节名
DELREG=del

;指明要要复制的文件子节,用于安装,Copyfiles命令可以替换系统正在访问的文件。这些功能通过普通的del和copy命令都无法实现
CopyFiles=cfile

;指明要要删除的文件子节,用于反安装,多个节以逗号隔开,该命令如果发现要删除的文件被锁定,就会把文件放到系统删除队列中排队
;等系统重启动的时候,该文件就自动被删除了
DelFiles=删除文件段
;[删除文件段]
;文件名列表
;例子:
;a.exe
;b.sys
;重命名文件段
;RenFiles=重命名文件段
;[重命名文件段]
;语法:
;[file-list-section]
;new-file-name,old-file-name
;file1,file2 ;修改文件名file1为file2

;更新INI文件段内容子节
UpdateInis = 更新INI文件段
;[更新INI文件段]
;ini-file, ini-section, [old-ini-entry], [new-ini-entry], [flags]
;ini-file 包含要更改条目的 .ini 文件名
;ini-section 包含要更改条目的节名
;old-ini-entry 可选,常用形式为 Key=Value
;new-ini-entry 可选,常用形式为
;Key=Value。flags 是可选操作标记
;例子
;%01%\wincmd.ini, Configuration,,"InstallDir=%01%"
;%01%\wincmd.ini, Configuration,,"Mainmenu=%01%\LANGUAGE\TCExtMenu.mnu"
;更新ini文件值内容
updateinifield =
;ini文件更新注册表
ini2reg=aa.ini
;更新config.sys内容
updatecfgsys=更新autoexec.bat段
[更新autoexec.bat段]
;更新autoexec.bat内容
updateautobat=更新autoexec.bat段
[更新autoexec.bat段]

;定义资源节,像资源文件,调用时使用%REG_SZ%就代表了0x00000000
[Strings]
REG_SZ=0x00000000
REG_BINARY=0x00000001
REG_DWORD=0x00010001
;自定义添加注册表项的节
[add]
;注意格式:HKEY(根键缩写),Subkey(子键),Valuename(键值名),Type(键值类型),Value(键值)
;HKCU -> HKEY_CURRENT_USER
;HKCR -> HKEY_CLASSES_ROOT
;HKLM -> HKEY_LOCAL_MACHINE
;HKU -> HKEY_USERS
;HKU -> HKEY_USERS
;HKCC -> HKEY_CURRENT_CONFIG
;HKDD -> HKEY_DYN_DATA
;解禁注册表编辑器
HKCU,Software\Microsoft\Windows\CurrentVersion\Policies\System,DisableRegistryTools,1,0
;解禁IE的Internet选项
HKCU,Software\Policies\Microsoft\Internet Explorer\Restrictions,NoBrowserOptions,1,0
;解禁IE的Internet选项里面的各个具体选项
HKCU,Software\Policies\Microsoft\Internet Explorer\Restrictions,NoBrowserOptions,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Settings,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,HomePage,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,GeneralTab,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Cache,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,History,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Colors,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Fonts,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Languages,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Accessibility,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,SecurityTab,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,SecChangeSettings,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,SecAddSites,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,ContentTab,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Ratings,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Certificates,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,CertifPers,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,CertifSite,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,CertifPub,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,FormSuggest,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,FormSuggest Passwords,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Wallet,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Profiles,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,ConnectionsTab,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Connection Wizard,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Connwiz Admin Lock,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Connection Settings,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Proxy,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,AutoConfig,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,ProgramsTab,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,ResetWebSettings,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Check_If_Default,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,AdvancedTab,1,0
HKCU,Software\Policies\Microsoft\Internet Explorer\Control Panel,Advanced,1,0
;解禁下载(可单独使用)
HKCU,Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\3,1803,1,0
;自动修改IE标题栏文字、主页、搜索页等等
HKCU,Software\Microsoft\Internet Explorer\Main,Window Title,0,"Internet Explorer"
HKCU,Software\Microsoft\Internet Explorer\Main,Start Page,0,"http://www.20cn.net"
HKCU,Software\Microsoft\Internet Explorer\Main,Search Page,0,"http://www.20cn.net"
HKCU,Software\Microsoft\Internet Explorer\Main,Default_Page_URL,0,"http://www.20cn.net"
HKLM,SOFTWARE\Microsoft\Internet Explorer\Main,Default_Search_URL,0,"http://www.20cn.net"
HKLM,SOFTWARE\Microsoft\Internet Explorer\Main,Search Page,0,"http://www.20cn.net"
HKLM,SOFTWARE\Microsoft\Internet Explorer\Main,Start Page,0,"http://www.20cn.net"
HKLM,SOFTWARE\Microsoft\Internet Explorer\Main,Default_Page_URL,0,"http://www.20cn.net"
HKLM,SOFTWARE\Microsoft\Internet Explorer\Main,Default_Page_URL,0,"http://www.20cn.net"

[dfile]
;该节定义了将要删除的文件列表,filename后面的1是一个标志,指明如果文件当前无法删除,就等到系统重启动后删除。
;格式:filename,,,1
;例子:
a.exe,,,1
关于inf文件的详细结构信息,可参考DDK帮助文档。

一、修改telnet服务,端口改为99,NTLM认证方式为1。
===============================

C:\myinf\Telnet.inf

[Version]
Signature="$WINDOWS NT$"
[DefaultInstall]
AddReg=AddRegName
[My_AddReg_Name]
HKLM,SOFTWARE\Microsoft\TelnetServer\1.0,TelnetPort,0x00010001,99
HKLM,SOFTWARE\Microsoft\TelnetServer\1.0,NTLM,0x00010001,1

安装:rundll32.exe setupapi,InstallHinfSection DefaultInstall 128 c:\myinf\telnet.inf

说明:[Version]和[DefaultInstall]是必须的,0x00010001表示REG_DWORD数据类型,0x00000000或省略该项(保留逗号)表示REG_SZ(字符串)。0x00020000表示REG_EXPAND_SZ。
InstallHinfSection是大小写敏感的。它和setupapi之间只有一个逗号,没有空格。128表示给定路径,该参数其他取值及含义参见MSDN。
特别注意,最后一个参数,必须是inf文件的全路径,不要用相对路径。
inf文件中的项目都是大小写不敏感的。

二、服务
===============

增加一个服务:

[Version]
Signature="$WINDOWS NT$"
[DefaultInstall.Services]
AddService=inetsvr,,My_AddService_Name
[My_AddService_Name]
DisplayName=Windows Internet Service
Description=提供对 Internet 信息服务管理的支持。
ServiceType=0x10
StartType=2
ErrorControl=0
ServiceBinary=%11%\inetsvr.exe

保存为inetsvr.inf,然后:

rundll32.exe setupapi,InstallHinfSection DefaultInstall 128 c:\path\inetsvr.inf

这个例子增加一个名为inetsvr的服务(是不是很像系统自带的服务,呵呵)。

几点说明:
1,最后四项分别是
服务类型:0x10为独立进程服务,0x20为共享进程服务(比如svchost);
启动类型:0 系统引导时加载,1 OS初始化时加载,2 由SCM(服务控制管理器)自动启动,3 手动启动,4 禁用。
(注意,0和1只能用于驱动程序)
错误控制:0 忽略,1 继续并警告,2 切换到LastKnownGood的设置,3 蓝屏。
服务程序位置:%11%表示system32目录,%10%表示系统目录(WINNT或Windows),%12%为驱动目录system32\drivers。其他取值参见DDK。你也可以不用变量,直接使用全路径。
这四项是必须要有的。
2,除例子中的六个项目,还有LoadOrderGroup、Dependencies等。不常用所以不介绍了。
3,inetsvr后面有两个逗号,因为中间省略了一个不常用的参数flags。

删除一个服务:

[Version]
Signature="$WINDOWS NT$"
[DefaultInstall.Services]
DelService=inetsvr

很简单,不是吗?

当然,你也可以通过导入注册表达到目的。但inf自有其优势。
1,导出一个系统自带服务的注册表项,你会发现其执行路径是这样的:
"ImagePath"=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,\
74,00,25,00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,74,\
00,6c,00,6e,00,74,00,73,00,76,00,72,00,2e,00,65,00,78,00,65,00,00,00
可读性太差。其实它就是%SystemRoot%\system32\tlntsvr.exe,但数据类型是REG_EXPAND_SZ。当手动导入注册表以增加服务时,这样定义ImagePath显然很不方便。如果用REG_SZ代替会有些问题——不能用环境变量了。即只能使用完整路径。用 inf文件完全没有这个问题,ServiceBinary(即ImagePath)自动成为REG_EXPAND_SZ。
2,最关键的是,和用SC等工具一样,inf文件的效果是即时起效的,而导入reg后必须重启才有效。
3,inf文件会自动为服务的注册表项添加一个Security子键,使它看起来更像系统自带的服务。

另外,AddService和DelService以及AddReg、DelReg可以同时且重复使用。即可以同时增加和删除多个服务和注册表项。

三、组策略
==========

1、密码最小6位
[version]
signature="$CHICAGO$"
[System Access]
MinimumPasswordLength = 6
PasswordComplexity = 1

保存为gp.inf,然后导入:
secedit /configure /db gp.sdb /cfg gp.inf /quiet


2、关闭所有的“审核策略

echo [version] >1.inf
echo signature="$CHICAGO$" >>1.inf
echo [Event Audit] >>1.inf
echo AuditSystemEvents=0 >>1.inf
echo AuditObjectAccess=0 >>1.inf
echo AuditPrivilegeUse=0 >>1.inf
echo AuditPolicyChange=0 >>1.inf
echo AuditAccountManage=0 >>1.inf
echo AuditProcessTracking=0 >>1.inf
echo AuditDSAccess=0 >>1.inf
echo AuditAccountLogon=0 >>1.inf
echo AuditLogonEvents=0 >>1.inf
secedit /configure /db 1.sdb /cfg 1.inf /log 1.log /quiet
del 1.*


四、解决XP ipc$连接只有Guest权限
====================

echo [version] >1.inf
echo signature="$CHICAGO$" >>1.inf
echo [Registry Values] >>1.inf
echo MACHINE\System\CurrentControlSet\Control\Lsa\ForceGuest=4,0 >>1.inf
secedit /configure /db 1.sdb /cfg 1.inf /log 1.log
del 1.*

- 作者: 小浪 2008年06月6日, 星期五 10:02  回复(0) |  引用(0) 加入博采

stl字符串转换数字,数字转换字符串

#include<iostream>  
#include<string>  
#include<sstream>  

using   namespace   std;  

int   main()  
{  
 ostringstream   os;
 int n = 345;
 os<<n<<endl;  
 string   str   =   os.str();  
 cout<<str<<endl;  
   
 string   str   =   "13.12";  
 istringstream   is(s);  
 float   f;  
 is>>f;  
 cout<<f<<endl;  
 system("pause");
 return 0;
  }

- 作者: 小浪 2008年04月29日, 星期二 15:39  回复(0) |  引用(0) 加入博采

单元测试的基本方法
作者 贺炘 来源 csai

  单元测试的对象是软件设计的最小单位——模块。单元测试的依据是详细设描述,单元测试应对模块内所有重要的控制路径设计测试用例,以便发现模块内部的错误。单元测试多采用白盒测试技术,系统内多个模块可以并行地进行测试。

单元测试任务

  单元测试任务包括:1 模块接口测试;2 模块局部数据结构测试;3 模块边界条件测试;4 模块中所有独立执行通路测试;5 模块的各条错误处理通路测试。

  模块接口测试是单元测试的基础。只有在数据能正确流入、流出模块的前提下,其他测试才有意义。测试接口正确与否应该考虑下列因素:

  1 输入的实际参数与形式参数的个数是否相同;

  2 输入的实际参数与形式参数的属性是否匹配;

  3 输入的实际参数与形式参数的量纲是否一致;

  4 调用其他模块时所给实际参数的个数是否与被调模块的形参个数相同;

  5 调用其他模块时所给实际参数的属性是否与被调模块的形参属性匹配;

  6调用其他模块时所给实际参数的量纲是否与被调模块的形参量纲一致;

  7 调用预定义函数时所用参数的个数、属性和次序是否正确;

  8 是否存在与当前入口点无关的参数引用;

  9 是否修改了只读型参数;

  10 对全程变量的定义各模块是否一致;

  11是否把某些约束作为参数传递。

  如果模块内包括外部输入输出,还应该考虑下列因素:

  1 文件属性是否正确;

  2 OPEN/CLOSE语句是否正确;

  3 格式说明与输入输出语句是否匹配;

  4缓冲区大小与记录长度是否匹配;

  5文件使用前是否已经打开;

  6是否处理了文件尾;

  7是否处理了输入/输出错误;

  8输出信息中是否有文字性错误;

  检查局部数据结构是为了保证临时存储在模块内的数据在程序执行过程中完整、正确。局部数据结构往往是错误的根源,应仔细设计测试用例,力求发现下面几类错误:

  1 不合适或不相容的类型说明;

  2变量无初值;

  3变量初始化或省缺值有错;

  4不正确的变量名(拼错或不正确地截断);

  5出现上溢、下溢和地址异常。

  除了局部数据结构外,如果可能,单元测试时还应该查清全局数据(例如FORTRAN的公用区)对模块的影响。

  在模块中应对每一条独立执行路径进行测试,单元测试的基本任务是保证模块中每条语句至少执行一次。此时设计测试用例是为了发现因错误计算、不正确的比较和不适当的控制流造成的错误。此时基本路径测试和循环测试是最常用且最有效的测试技术。计算中常见的错误包括:

  1 误解或用错了算符优先级;

  2混合类型运算;

  3变量初值错;

  4精度不够;

  5表达式符号错。

  比较判断与控制流常常紧密相关,测试用例还应致力于发现下列错误:

  1不同数据类型的对象之间进行比较;

  2错误地使用逻辑运算符或优先级;

  3因计算机表示的局限性,期望理论上相等而实际上不相等的两个量相等;

  4比较运算或变量出错;

  5循环终止条件或不可能出现;

  6迭代发散时不能退出;

  7错误地修改了循环变量。

  一个好的设计应能预见各种出错条件,并预设各种出错处理通路,出错处理通路同样需要认真测试,测试应着重检查下列问题:

  1输出的出错信息难以理解;

  2记录的错误与实际遇到的错误不相符;

  3在程序自定义的出错处理段运行之前,系统已介入;

  4异常处理不当;

  5错误陈述中未能提供足够的定位出错信息。

  边界条件测试是单元测试中最后,也是最重要的一项任务。众的周知,软件经常在边界上失效,采用边界值分析技术,针对边界值及其左、右设计测试用例,很有可能发现新的错误。

单元测试过程

  一般认为单元测试应紧接在编码之后,当源程序编制完成并通过复审和编译检查,便可开始单元测试。测试用例的设计应与复审工作相结合,根据设计信息选取测试数据,将增大发现上述各类错误的可能性。在确定测试用例的同时,应给出期望结果。

  应为测试模块开发一个驱动模块(driver)和(或)若干个桩模块(stub),下图显示了一般单元测试的环境。驱动模块在大多数场合称为“主程序”,它接收测试数据并将这些数据传递到被测试模块,被测试模块被调用后,“主程序”打印“进入-退出”消息。

  驱动模块和桩模块是测试使用的软件,而不是软件产品的组成部分,但它需要一定的开发费用。若驱动和桩模块比较简单,实际开销相对低些。遗憾的是,仅用简单的驱动模块和桩模块不能完成某些模块的测试任务,这些模块的单元测试只能采用下面讨论的综合测试方法。

  提高模块的内聚度可简化单元测试,如果每个模块只能完成一个,所需测试用例数目将显著减少,模块中的错误也更容易发现。

- 作者: 小浪 2008年04月28日, 星期一 16:32  回复(0) |  引用(0) 加入博采

深入浅出单元测试

一 单元测试概述
  工厂在组装一台电视机之前,会对每个元件都进行测试,这,就是单元测试。
  其实我们每天都在做单元测试。你写了一个函数,除了极简单的外,总是要执行一下,看看功能是否正常,有时还要想办法输出些数据,如弹出信息窗口什么的,这,也是单元测试,老纳把这种单元测试称为临时单元测试。只进行了临时单元测试的软件,针对代码的测试很不完整,代码覆盖率要超过70%都很困难,未覆盖的代码可能遗留大量的细小的错误,这些错误还会互相影响,当BUG暴露出来的时候难于调试,大幅度提高后期测试和维护成本,也降低了开发商的竞争力。可以说,进行充分的单元测试,是提高软件质量,降低开发成本的必由之路。
  对于程序员来说,如果养成了对自己写的代码进行单元测试的习惯,不但可以写出高质量的代码,而且还能提高编程水平。
  要进行充分的单元测试,应专门编写测试代码,并与产品代码隔离。老纳认为,比较简单的办法是为产品工程建立对应的测试工程,为每个类建立对应的测试类,为每个函数(很简单的除外)建立测试函数。首先就几个概念谈谈老纳的看法。
  一般认为,在结构化程序时代,单元测试所说的单元是指函数,在当今的面向对象时代,单元测试所说的单元是指类。以老纳的实践来看,以类作为测试单位,复杂度高,可操作性较差,因此仍然主张以函数作为单元测试的测试单位,但可以用一个测试类来组织某个类的所有测试函数。单元测试不应过分强调面向对象,因为局部代码依然是结构化的。单元测试的工作量较大,简单实用高效才是硬道理。
  有一种看法是,只测试类的接口(公有函数),不测试其他函数,从面向对象角度来看,确实有其道理,但是,测试的目的是找错并最终排错,因此,只要是包含错误的可能性较大的函数都要测试,跟函数是否私有没有关系。对于C+ +来说,可以用一种简单的方法区隔需测试的函数:简单的函数如数据读写函数的实现在头文件中编写(inline函数),所有在源文件编写实现的函数都要进行测试(构造函数和析构函数除外)。
  什么时候测试?单元测试越早越好,早到什么程度?XP开发理论讲究TDD,即测试驱动开发,先编写测试代码,再进行开发。在实际的工作中,可以不必过分强调先什么后什么,重要的是高效和感觉舒适。从老纳的经验来看,先编写产品函数的框架,然后编写测试函数,针对产品函数的功能编写测试用例,然后编写产品函数的代码,每写一个功能点都运行测试,随时补充测试用例。所谓先编写产品函数的框架,是指先编写函数空的实现,有返回值的随便返回一个值,编译通过后再编写测试代码,这时,函数名、参数表、返回类型都应该确定下来了,所编写的测试代码以后需修改的可能性比较小。
  由谁测试?单元测试与其他测试不同,单元测试可看作是编码工作的一部分,应该由程序员完成,也就是说,经过了单元测试的代码才是已完成的代码,提交产品代码时也要同时提交测试代码。测试部门可以作一定程度的审核。
  关于桩代码,老纳认为,单元测试应避免编写桩代码。桩代码就是用来代替某些代码的代码,例如,产品函数或测试函数调用了一个未编写的函数,可以编写桩函数来代替该被调用的函数,桩代码也用于实现测试隔离。采用由底向上的方式进行开发,底层的代码先开发并先测试,可以避免编写桩代码,这样做的好处有:减少了工作量;测试上层函数时,也是对下层函数的间接测试;当下层函数修改时,通过回归测试可以确认修改是否导致上层函数产生错误。

二 测试代码编写
  多数讲述单元测试的文章都是以Java为例,本文以C++为例,后半部分所介绍的单元测试工具也只介绍C++单元测试工具。下面的示例代码的开发环境是VC6.0。

产品类:
class CMyClass
{
public:
int Add(int i, int j);
CMyClass();
virtual ~CMyClass();

private:
int mAge; //年龄
CString mPhase; //年龄阶段,如"少年","青年"
};

建立对应的测试类CMyClassTester,为了节约编幅,只列出源文件的代码:
void CMyClassTester::CaseBegin()
{
//pObj是CMyClassTester类的成员变量,是被测试类的对象的指针,
//为求简单,所有的测试类都可以用pObj命名被测试对象的指针。
pObj = new CMyClass();
}

void CMyClassTester::CaseEnd()
{
delete pObj;
}
测试类的函数CaseBegin()和CaseEnd()建立和销毁被测试对象,每个测试用例的开头都要调用CaseBegin(),结尾都要调用CaseEnd()。

接下来,我们建立示例的产品函数:
int CMyClass::Add(int i, int j)
{
return i+j;
}
和对应的测试函数:
void CMyClassTester::Add_int_int()
{
}
把参数表作为函数名的一部分,这样当出现重载的被测试函数时,测试函数不会产生命名冲突。下面添加测试用例:
void CMyClassTester::Add_int_int()
{
//第一个测试用例
CaseBegin();{ //1
int i = 0; //2
int j = 0; //3
int ret = pObj->Add(i, j); //4
ASSERT(ret == 0); //5
}CaseEnd(); //6
}
第1和第6行建立和销毁被测试对象,所加的{}是为了让每个测试用例的代码有一个独立的域,以便多个测试用例使用相同的变量名。
第2 和第3行是定义输入数据,第4行是调用被测试函数,这些容易理解,不作进一步解释。第5行是预期输出,它的特点是当实际输出与预期输出不同时自动报错, ASSERT是VC的断言宏,也可以使用其他类似功能的宏,使用测试工具进行单元测试时,可以使用该工具定义的断言宏。

  示例中的格式显得很不简洁,2、3、4、5行可以合写为一行:ASSERT(pObj->Add(0, 0) == 0);但这种不简洁的格式却是老纳极力推荐的,因为它一目了然,易于建立多个测试用例,并且具有很好的适应性,同时,也是极佳的代码文档,总之,老纳建议:输入数据和预期输出要自成一块。
  建立了第一个测试用例后,应编译并运行测试,以排除语法错误,然后,使用拷贝/修改的办法建立其他测试用例。由于各个测试用例之间的差别往往很小,通常只需修改一两个数据,拷贝/修改是建立多个测试用例的最快捷办法。

三 测试用例
  下面说说测试用例、输入数据及预期输出。输入数据是测试用例的核心,老纳对输入数据的定义是:被测试函数所读取的外部数据及这些数据的初始值。外部数据是对于被测试函数来说的,实际上就是除了局部变量以外的其他数据,老纳把这些数据分为几类:参数、成员变量、全局变量、IO媒体。IO媒体是指文件、数据库或其他储存或传输数据的媒体,例如,被测试函数要从文件或数据库读取数据,那么,文件或数据库中的原始数据也属于输入数据。一个函数无论多复杂,都无非是对这几类数据的读取、计算和写入。预期输出是指:返回值及被测试函数所写入的外部数据的结果值。返回值就不用说了,被测试函数进行了写操作的参数(输出参数)、成员变量、全局变量、IO媒体,它们的预期的结果值都是预期输出。一个测试用例,就是设定输入数据,运行被测试函数,然后判断实际输出是否符合预期。下面举一个与成员变量有关的例子:
产品函数:
void CMyClass::Grow(int years)
{
mAge += years;

if(mAge < mphase = "儿童" mphase = "少年" mphase = "青年" mphase = "中年" mphase = "老年" years =" 1;">mAge = 8;
pObj->Grow(years);
ASSERT( pObj->mAge == 9 );
ASSERT( pObj->mPhase == "儿童" );
}CaseEnd();
在输入数据中对被测试类的成员变量mAge进行赋值,在预期输出中断言成员变量的值。现在可以看到老纳所推荐的格式的好处了吧,这种格式可以适应很复杂的测试。在输入数据部分还可以调用其他成员函数,例如:执行被测试函数前可能需要读取文件中的数据保存到成员变量,或需要连接数据库,老纳把这些操作称为初始化操作。例如,上例中 ASSERT( ...)之前可以加pObj->OpenFile();。为了访问私有成员,可以将测试类定义为产品类的友元类。例如,定义一个宏:
#define UNIT_TEST(cls) friend class cls##Tester;
然后在产品类声明中加一行代码:UNIT_TEST(ClassName)。

  下面谈谈测试用例设计。前面已经说了,测试用例的核心是输入数据。预期输出是依据输入数据和程序功能来确定的,也就是说,对于某一程序,输入数据确定了,预期输出也就可以确定了,至于生成/销毁被测试对象和运行测试的语句,是所有测试用例都大同小异的,因此,我们讨论测试用例时,只讨论输入数据。
  前面说过,输入数据包括四类:参数、成员变量、全局变量、IO媒体,这四类数据中,只要所测试的程序需要执行读操作的,就要设定其初始值,其中,前两类比较常用,后两类较少用。显然,把输入数据的所有可能取值都进行测试,是不可能也是无意义的,我们应该用一定的规则选择有代表性的数据作为输入数据,主要有三种:正常输入,边界输入,非法输入,每种输入还可以分类,也就是平常说的等价类法,每类取一个数据作为输入数据,如果测试通过,可以肯定同类的其他输入也是可以通过的。下面举例说明:
  正常输入
  例如字符串的Trim函数,功能是将字符串前后的空格去除,那么正常的输入可以有四类:前面有空格;后面有空格;前后均有空格;前后均无空格。
  边界输入
  上例中空字符串可以看作是边界输入。
  再如一个表示年龄的参数,它的有效范围是0-100,那么边界输入有两个:0和100。
  非法输入
  非法输入是正常取值范围以外的数据,或使代码不能完成正常功能的输入,如上例中表示年龄的参数,小于0或大于100都是非法输入,再如一个进行文件操作的函数,非法输入有这么几类:文件不存在;目录不存在;文件正在被其他程序打开;权限错误。
  如果函数使用了外部数据,则正常输入是肯定会有的,而边界输入和非法输入不是所有函数都有。一般情况下,即使没有设计文档,考虑以上三种输入也可以找出函数的基本功能点。实际上,单元测试与代码编写是“一体两面”的关系,编码时对上述三种输入都是必须考虑的,否则代码的健壮性就会成问题。

四 白盒覆盖
  上面所说的测试数据都是针对程序的功能来设计的,就是所谓的黑盒测试。单元测试还需要从另一个角度来设计测试数据,即针对程序的逻辑结构来设计测试用例,就是所谓的白盒测试。在老纳看来,如果黑盒测试是足够充分的,那么白盒测试就没有必要,可惜“足够充分”只是一种理想状态,例如:真的是所有功能点都测试了吗?程序的功能点是人为的定义,常常是不全面的;各个输入数据之间,有些组合可能会产生问题,怎样保证这些组合都经过了测试?难于衡量测试的完整性是黑盒测试的主要缺陷,而白盒测试恰恰具有易于衡量测试完整性的优点,两者之间具有极好的互补性,例如:完成功能测试后统计语句覆盖率,如果语句覆盖未完成,很可能是未覆盖的语句所对应的功能点未测试。
  白盒测试针对程序的逻辑结构设计测试用例,用逻辑覆盖率来衡量测试的完整性。逻辑单位主要有:语句、分支、条件、条件值、条件值组合,路径。语句覆盖就是覆盖所有的语句,其他类推。另外还有一种判定条件覆盖,其实是分支覆盖与条件覆盖的组合,在此不作讨论。跟条件有关的覆盖就有三种,解释一下:条件覆盖是指覆盖所有的条件表达式,即所有的条件表达式都至少计算一次,不考虑计算结果;条件值覆盖是指覆盖条件的所有可能取值,即每个条件的取真值和取假值都要至少计算一次;条件值组合覆盖是指覆盖所有条件取值的所有可能组合。老纳做过一些粗浅的研究,发现与条件直接有关的错误主要是逻辑操作符错误,例如:||写成&&,漏了写!什么的,采用分支覆盖与条件覆盖的组合,基本上可以发现这些错误,另一方面,条件值覆盖与条件值组合覆盖往往需要大量的测试用例,因此,在老纳看来,条件值覆盖和条件值组合覆盖的效费比偏低。老纳认为效费比较高且完整性也足够的测试要求是这样的:完成功能测试,完成语句覆盖、条件覆盖、分支覆盖、路径覆盖。做过单元测试的朋友恐怕会对老纳提出的测试要求给予一个字的评价:晕!或者两个字的评价:狂晕!因为这似乎是不可能的要求,要达到这种测试完整性,其测试成本是不可想象的,不过,出家人不打逛语,老纳之所以提出这种测试要求,是因为利用一些工具,可以在较低的成本下达到这种测试要求,后面将会作进一步介绍。
  关于白盒测试用例的设计,程序测试领域的书籍一般都有讲述,普通方法是画出程序的逻辑结构图如程序流程图或控制流图,根据逻辑结构图设计测试用例,这些是纯粹的白盒测试,不是老纳想推荐的方式。老纳所推荐的方法是:先完成黑盒测试,然后统计白盒覆盖率,针对未覆盖的逻辑单位设计测试用例覆盖它,例如,先检查是否有语句未覆盖,有的话设计测试用例覆盖它,然后用同样方法完成条件覆盖、分支覆盖和路径覆盖,这样的话,既检验了黑盒测试的完整性,又避免了重复的工作,用较少的时间成本达到非常高的测试完整性。不过,这些工作可不是手工能完成的,必须借助于工具,后面会介绍可以完成这些工作的测试工具。

五 单元测试工具
  现在开始介绍单元测试工具,老纳只介绍三种,都是用于C++语言的。
  首先是CppUnit,这是C++单元测试工具的鼻祖,免费的开源的单元测试框架。由于已有一众高人写了不少关于CppUnit的很好的文章,老纳就不现丑了,想了解CppUnit的朋友,建议读一下Cpluser 所作的《CppUnit测试框架入门》,网址是:http://blog.csdn.net/cpluser/archive/2004/09/21/111522.aspx。该文也提供了CppUnit的下载地址。
  然后介绍C++Test,这是Parasoft公司的产品。[C++Test是一个功能强大的自动化C/C++单元级测试工具,可以自动测试任何C/C++函数、类,自动生成测试用例、测试驱动函数或桩函数,在自动化的环境下极其容易快速的将单元级的测试覆盖率达到100%]。[]内的文字引自http://www.superst.com.cn/softwares_testing_c_cpptest.htm,这是华唐公司的网页。老纳想写些介绍C++Test的文字,但发现无法超越华唐公司的网页上的介绍,所以也就省点事了,想了解C++Test的朋友,建议访问该公司的网站。华唐公司代理C++Test,想要购买或索取报价、试用版都可以找他们。老纳帮华唐公司做广告,不知道会不会得点什么好处?
  最后介绍Visual Unit,简称VU,这是国产的单元测试工具,据说申请了多项专利,拥有一批创新的技术,不过老纳只关心是不是有用和好用。[自动生成测试代码 快速建立功能测试用例 程序行为一目了然 极高的测试完整性 高效完成白盒覆盖 快速排错 高效调试 详尽的测试报告]。[]内的文字是VU开发商的网页上摘录的,网址是:http://www.unitware.cn。前面所述测试要求:完成功能测试,完成语句覆盖、条件覆盖、分支覆盖、路径覆盖,用VU可以轻松实现,还有一点值得一提:使用VU还能提高编码的效率,总体来说,在完成单元测试的同时,编码调试的时间可能还会缩短。算了,不想再讲了,老纳显摆理论、介绍经验还是有兴趣的,因为可以满足老纳好为人师的虚荣心,但介绍工具就觉得索然无味了,毕竟工具好不好用,合不合用,要试过才知道,还是自己去开发商的网站看吧,可以下载演示版,还有演示课件,老纳念经去也,阿弥陀佛。

- 作者: 小浪 2008年04月28日, 星期一 16:31  回复(0) |  引用(0) 加入博采

一个简短的c++单元测试框架

摘自:http://blog.163.com/phiqy@126/blog/static/50170421200772101316518/

介绍
 
这个测试框架只有125行的单个头文件。它的目的是提供一种简单的方式来对c++进行单元测试。因为简单,它还能狗容易定制。一些测试框架都需要链接分离的库文件或者经过多次跳转才能工作,这就使得写case是一件非常难得事情了。
其中一种情况是开发人员经常碰到当他们需要写单元测试用例的时候他们已经工作于项目上了。如果这个项目不应独立的库而坏掉,这就很难写出独立的单元测试用例。另外,一些程序的一些核心function不能简单的被破坏成一个单独可执行的用例。实际上,一个开发人员应该写一些单元测试用例,在一个程序内部调用自己的function,可以简单的使用#define。
这看起来不像一种很好的软件工程方法,但是,这些都是尽早测试,通常比其它方法更好,用最小的努力来写测试,最早的完成。测试集合随着项目前进而增长,由于时间限制测试就能集合到一个单独的库和可执行的东东。
 
我开始寻找一个现成的解决方案。在互联网上有很多xUnit的测试框架,比如Smalltalk SUnit,JUnit,NUnit,CppUnit等等

但是很多框架都有一个共同的就是能够让它的属性在一个function中run起来,或者让其自动的跑起来。C++程序员没有那么幸运,因为需要做很多手工的工作比如宏,模板之类的,尽管不是大的工作。
设计
 
在评估一些测试框架后,我决定它们都不是能够满足我的简单的而且能够使用修改的要求。我决定写一个测试框架,写到一个头文件中,尽量只包含尽量少的代码。这里列出设计原则:

 能够在一个单独的头文件中(没有模块或库)
 几百行代码
 易于修改扩展
 信息输出
 可选宏
 不要模板
 不要动态内存分配
 用于嵌入式系统中
 用兼容与低级别的C++ compilers (没有超炫的功能)

使用Code
 
建立case有三件事需要做
 一个测试用例test case
 一个测试集合test suite
 测试集合需要添加到runner然后被调用
写一个case
 
这个例子中,我们从TestCase基类派生出我们的测试用例
TestCase,像其他的框架的一样,是一个结构体,能够帮助我们避免一些公共访问。

测试code被加到一个测试方法中,TestSuite类含有一切能够让所有测试用例能够访问的任何数据,能够传递到每个测试用例的函数。当测试失败事件是能够输出有意义的输出。

struct TestAccountWithdrawal : TestCase
{
    const char* name() { return "Account withdrawal test"; }
 
    void test(TestSuite* suite) 
    {
        TestAccountSuite* data = (TestAccountSuite*)suite;
 
        data->account->Deposit(10);
 
        bool succeeded = data->account->Withdraw(11);
 
        T_ASSERT(succeeded == false);
        T_ASSERT(data->account->Balance() == 10);
    }
};

添加一个测试集test suite
 
测试集包含一组相关的测试用例,他的目的像其他的框架中的test suite和test fixture共同的功能。
有两个关键的方法(都是可选的)是setup和teardown,它们总是成对出现。
struct TestAccountSuite : TestSuite
{
    const char* name() { return "Account suite"; }
 
    void setup()
    {
        account = new Account();
    }
 
    void teardown() 
    {
        delete account;
    }
 
    Account* account;
};

把它们组织到一起
一旦一个测试集和至少一个测试用例完成之后我们可以将它们放到一个runner中执行。
#include
#include "shortcut.h"
#include "tests/account.h"
int main(int argc, char* argv[])
{
    TestRunner runner;
    TestAccountSuite accountSuite;
    TestAccountWithdrawal accountWithdrawalTest;
 
    accountSuite.AddTest(&accountWithdrawalTest);
    runner.AddSuite(&accountSuite);
    runner.RunTests();
    
    return 0;
}

这个很小的系统的好处是能够都在一个头文件中。这就防止不同类的声明定义重复。因为之用一个头文件,所以只需要要一个驱动(driver),比如在main函数中。
这个系统可以很容易添加一个测试用例到现有的程序中。比如,一些用例被#ifdef DEBUG宏控制块,在Release版本就不会输出到二进制文件中,不会连接到其他单元测试库上。
显然这不是长效的解决方法,尽管是一个好的方法的开始。开发人员能够在开发时间内分离测试和code才是

基类
所有的测试用例派生与一个基类TestCase.
struct TestCase
{
    TestCase() : next(0) {}
    
    virtual void test(TestSuite* suite) {}
    virtual const char* name() { return "?"; }
    
    TestCase* next;
};

有个名字的方法用来记录错误的。用一个指针指向测试用例的列表。它本身是个虚函数能够派生。

测试集test suite具有相同的结构,除了包含一系列case和两个重载函数setup和teardown

+struct TestSuite
{
    TestSuite() : next(0), tests(0) {}
 
    virtual void setup() {}
    virtual void teardown() {}
    virtual const char* name() { return "?"; }
 
    void AddTest(TestCase* tc)
    {
        tc->next = tests;
        tests = tc;
    }
 
    TestSuite* next;
    TestCase* tests;
};

像开始提及的一样这个test suite类扮演着其他框架中的 test suite和test fixture类的角色. 这在写框架中,当fixture提供setup/teardown机制时,suite常常扮演测试组织的角色,因为 ShortCUT是一个简单的框架,就不需要创建这些复杂的类了。当开发需要一个类似的功能是很容易定制的加上。
执行Runner
 
一个测试的runner是测试的主体,当然也是一个非常直接的他的主线是调用测试的方法,run每个suite。
struct TestRunner
{
    ...
    void RunSuite(TestSuite* suite, int& testCount, int& passCount)
    {
        TestCase* test = suite->tests;
        while (test)
        {
            try
            {
                suite->setup();
                test->test(suite);
                passCount++;
            }
            catch (TestException& te)
            {
                log->write("FAILED '%s': %s\n", test->name(), te.text());
            }
            catch (...)
            {
                log->write("FAILED '%s': unknown exception\n", test->name());
            }
 
            try
            {
                suite->teardown();
            }
            catch (...)
            {
                log->write("FAILED: teardown error in suite '%s'\n", suite->name());
            }
 
            test = test->next;
            testCount++;
        }
    }
    ...
}

这个关键点要注意,首先记录类执行在框架之外。这就容易让结果输出到另外一个目标中,像一个窗体。第二点,烦恼的是测试集合和测试用例像个链子一样,这就像一个LIFO的规则一样,反向与添加时候的顺序。

这就会有一个简单的方式能够修复这个问题,但是为了框架的简单性我删除了它。

这个框架的主要目标是是个尽量简单的解决方案。像TestLog类可以加到上面来帮助满足需求,尽管框架是简单的,但也不会失去基本的机动性。
头文件有200行代码,四分之一是不需要的,希望能能够组建一个能够剪裁的系统、能够很容易使用修改定制的系统。
附录:
/*
 * Copyright (c) 2003-2004  Pau Arum?& David Garc韆
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#ifndef MiniCppUnit_hxx
#define MiniCppUnit_hxx

/**
 * @mainpage
 * miniCppUnit
 * (C) 2003-2006 Pau Arumi & David Garcia
 *
 * @version 2.5 2006-03-14
 *   - MS Visual compatibility: SConstruct ccflags, usage example, #ifdefs
 * @version 2.4 2006-03-14
 *   - exit test case after first failure
 *   - double and float comparison with fu equals (using scalable epsilon)
 *   - have into account not a numbers
 *   - new ASSERT_EQUALS_EPSILON macro
 *   - more colors, and disabled when comiled in MS Visual
 *   - removed catalan location.
 *   - UsageExample.cxx now uses all macros and features
 * @version 2.3 2006-02-13 added usage example and SConstruct
 * @version 2.2 2004-11-28 code in english and tests suites
 * @version 2.1 2004-11-04 char* especialization
 * @version 2.0 2004-10-26 TestsFactory
 * @version 1.0 2003-10-28 initial
 *
 * Example of use:
 *
 * @code
 * #include "MiniCppUnit.hxx"
 * class MyTests : public TestFixture
 * {
 *  public:
 *   TEST_FIXTURE( MyTests )
 *  {
 *   CAS_DE_TEST( testAddition );
 *   // etc
 *  }
 *  void testAddition()
 *  {
 *   ASSERT_EQUALS( 4, 1+1+2 );
 *  } 
 *  // etc
 * };
 *
 * REGISTER_FIXTURE( MyTests );
 * @endcode
 * @code
 * int main()
 * {
 * return TestFixtureFactory::theInstance().runTests() ? 0 : -1;
 * }
 * @endcode
 * Good things:
 *
 *   - it's a tiny framework made up of two or three src files.
 *     => no need to install as a library
 *   - object oriented and makes use of several GoF patterns
 *   - very simple usage. Just needs to learn very few C macros
 *   - string asserts are simpler to use than cppunit
 *   - string asserts are enhanced with coloured diffs
 *   - concrete test classes are totally decoupled via static factory
 *     => no src file have to include them all.
 *   - it have test suite hierarchies
 *   - compatible with non-standard compliant VisualC6
 *     (though not necessary good ;)
 */

#include
#include
#include
#include

#if _MSC_VER < 1300
/** necesary for Visual 6 which don't define std::min */
namespace std
{
 template
 min(const T& a, const T& b) { return a < b ? a: b; }
}
#endif

/**
 * A singleton class.
 * Receives tests results and stores messages to the test log
 * for later listing.
 * It's a singleton for an easy global access from the 'Asserts'
 * methods but it is probably asking for a refactoring in order to limit
 * access only to TestFixtures
 */
class TestsListener
{
public:
 /** accessor to the global (static) singleton instance */
 static TestsListener& theInstance();
 std::stringstream& errorsLog();
 std::string logString();
 void currentTestName( std::string& name);
 static void testHasRun();
 static void testHasFailed();
 static void testHasThrown();
 /** the human readable summary of run tests*/
 std::string summary();
 /** returns wheather all run tests have passed */
 static bool allTestsPassed();
 
private:
 static const char* errmsgTag_nameOfTest() { return "Test failed: "; }
 
 /** constructor private: force the singleton to be wellbehaved ! */
 TestsListener() : _currentTestName(0)
 {
  _executed=_failed=_exceptions=0;
 }
 
 std::string* _currentTestName;
 std::stringstream _log;
 unsigned _executed;
 unsigned _failed;
 unsigned _exceptions;
};

class TestFailedException
{
};

/**
 * Abstract class with interface that allows run a test. That is runTest
 * and name. It is implemented by TestFixture and TestCase
 *
 * It does the 'Component' role in the 'Composite' patten
 **/
class Test
{
public:
 virtual ~Test(){}
 /** run the test: exercice the code and check results*/
 virtual void runTest() = 0;
 /** the test human-readable name */
 virtual std::string name() const = 0;
};


/**
 * This class is just a placeholder for all assert functions --as static methods.
 * It is meant for being used just by the assert macros
 */
class Assert
{
 static const char * errmsgTag_testFailedIn() { return "Test failed in "; }
 static const char * errmsgTag_inLine() { return ", line: "; };
 static const char * errmsgTag_failedExpression() { return "Failed expression: "; }
 static const char * errmsgTag_expected() { return "Expected: "; }
 static const char * errmsgTag_butWas() { return "But was: "; }

public:
#ifdef _MSC_VER
 static const char * blue() { return ""; }
 static const char * green() { return ""; }
 static const char * red() { return ""; }
 static const char * normal() { return ""; }
 static const char * bold() { return ""; }
 static const char * yellow() { return ""; }
#else
 static const char * blue() { return "\033[36;1m"; }
 static const char * green() { return "\033[32;1m"; }
 static const char * red() { return "\033[31;1m"; }
 static const char * normal() { return "\033[0m"; }
 static const char * bold() { return "\033[" "1m"; }
 static const char * yellow() { return "\033[93;1m"; }
#endif
 template
 static void assertEquals( const AType& expected, const AType& result,
  const char* file="", int linia=0 )
 {
  if(expected != result)
  {
   TestsListener::theInstance().errorsLog()
    << file << ", linia: " << linia << "\n"
    << errmsgTag_expected() << " " << expected << " "
    << errmsgTag_butWas() << " " << result << "\n";
   TestsListener::theInstance().testHasFailed();
  }
 }

 static void assertTrue(char* strExpression, bool expression,
   const char* file="", int linia=0);

 static void assertTrueMissatge(char* strExpression, bool expression,
   const char* missatge, const char* file="", int linia=0);

 static void assertEquals( const char * expected, const char * result,
  const char* file="", int linia=0 );
 
 static void assertEquals( const bool& expected, const bool& result,
  const char* file="", int linia=0 );
 
 static void assertEquals( const double& expected, const double& result,
  const char* file="", int linia=0 );

 static void assertEquals( const float& expected, const float& result,
  const char* file="", int linia=0 );
 
 static void assertEquals( const long double& expected, const long double& result,
  const char* file="", int linia=0 );
 
 static void assertEqualsEpsilon( const double& expected, const double& result, const double& epsilon,
  const char* file="", int linia=0 );

 static int notEqualIndex( const std::string & one, const std::string & other );

 /**
  * we overload the assert with string doing colored diffs
  *
  * MS Visual6 doesn't allow string by reference :-(
  */
 static void assertEquals( const std::string expected, const std::string result,
  const char* file="", int linia=0 );
 
 static void fail(const char* motiu, const char* file="", int linia=0);


};

/**
 * A TestFixture is a class that contain TestCases --which corresponds to
 * ConcreteTestFixture methods-- common objects uder tests, and setUp and
 * tearDown methods which are automatically executed before and after each
 * test case.
 *
 * Is the base class of ConcreteFixtures implemented by the framework user
 *
 * It does the 'Composite' role in the 'Composite' GoF pattern.
 * Its composite children are TestCases, which wrapps the test methods.
 *
 * It is a template class parametrized by ConcreteTestFixture so that it can
 * instantiate TestCase objects templatized with this same parameter: it needs the
 * concrete class type for calling its non-static methods.
 */
template
class TestFixture : public Test
{
protected:

 typedef ConcreteTestFixture ConcreteFixture;
 typedef void(ConcreteTestFixture::*TestCaseMethod)();

 /**
  * Wrapper for the test methods of concrete TestFixtures.
  *
  * Makes the 'Leave' role in the 'Composite' GoF pattern because can't be
  * be a composition of other tests.
  *
  * It's also a case of 'Command' pattern because it encapsules in an object
  * certain functionality whose execution depends on some deferred entity.
  */
 class TestCase : public Test
 {
 public:
  TestCase(ConcreteFixture* parent, TestCaseMethod method, const std::string & name) :
    _parent(parent),
    _testCaseMethod(method),
    _name(name)
  {
  }
  /** calls TestFixture method.  setUp and tearDown methods are called by
   * its parent TestFixture (in its runTest method).
   * it is robust to unexpected exceptions (throw) */
  void runTest()
  {
   TestsListener::theInstance().testHasRun();
   TestsListener::theInstance().currentTestName(_name);
   try
   {
    (_parent->*_testCaseMethod)();
   }
   catch( std::exception& error )
   {
    TestsListener::theInstance().testHasThrown();
    TestsListener::theInstance().errorsLog()
     << "std::exception catched by MiniCppUnit: \n"
     << "what() : "
     << Assert::yellow() << error.what()
     << Assert::normal() << "\n";
   }
   catch ( TestFailedException& failure) //just for skiping current test case
   {
   }
   catch(...)
   {
    TestsListener::theInstance().testHasThrown();
    TestsListener::theInstance().errorsLog()
     << "non standard exception catched by MiniCppUnit.\n";
   }
  }

  /** the TestFixture method hame */
  std::string name() const
  {
   return _name;
  }

 private:
  ConcreteFixture* _parent;
  TestCaseMethod _testCaseMethod;
  std::string _name;
 };
    //------------- end of class TestCase ----------------------------

private:
 
 typedef std::list TestCases;
 TestCases _testCases;
 std::string _name;

 void testsList() const
 {
  std::cout << "\n+ " << name() << "\n";
  for( TestCases::const_iterator it=_testCases.begin();
   it!=_testCases.end(); it++ )
   std::cout << "  - "<< (*it)->name() << "\n";
 }
 

public:
 virtual void setUp() {}
 virtual void tearDown() {}

 std::string name() const
 {
  return _name;
 };

 TestFixture(const std::string& name="A text fixture") : _name(name)
 {
 }

 void afegeixCasDeTest(ConcreteFixture* parent, TestCaseMethod method, const char* name)
 {
  TestCase* casDeTest = new TestCase(parent, method, _name + "::" + name);
  _testCases.push_back( casDeTest );
 }
 /** calls each test after setUp and tearDown TestFixture methods */
 void runTest()
 {
  testsList();
  TestCases::iterator it;
  for( it=_testCases.begin(); it!=_testCases.end(); it++)
  {
   setUp();
   (*it)->runTest();
   tearDown();
  }
 }
 /** TestCase that wrapps TestFixture methods are dynamically created and owned by
  * the TestFixture. So here we clean it up*/
 ~TestFixture()
 { 
  TestCases::iterator it;
  for( it =_testCases.begin(); it!=_testCases.end(); it++)
   delete (*it);
 }
};


/**
 * This class is aimed to hold a creator method for each concrete TestFixture
 */
class TestFixtureFactory
{
private:
 /** Well behaved singleton:
  *  Don't allow instantiation apart from theInstance(), so private ctr.*/
 TestFixtureFactory()
 {
 }
 typedef Test* (*FixtureCreator)();
 std::list _creators;
public:
 /** Accessor to the (static) singleton instance */
 static TestFixtureFactory& theInstance()
 {
  static TestFixtureFactory theFactory;
  return theFactory;
 }
 bool runTests()
 {
  std::list::iterator it;
  for(it=_creators.begin(); it!=_creators.end(); it++)
  { 
   FixtureCreator creator = *it;
   Test* test = creator();
   test->runTest();
   delete test;
  }
  std::string errors =  TestsListener::theInstance().logString();
  if (errors!="") std::cout << "\n\nError Details:\n" << errors;
  std::cout << TestsListener::theInstance().summary();

  return TestsListener::theInstance().allTestsPassed(); 
 }
 void addFixtureCreator(FixtureCreator creator)
 {
  _creators.push_back( creator );
 }
 
};

/**
 * Macro a usar despr閟 de cada classe de test
 */
#define REGISTER_FIXTURE( ConcreteTestFixture ) \
\
Test* Creador##ConcreteTestFixture() { return new ConcreteTestFixture; } \
\
class Registrador##ConcreteTestFixture \
{ \
public: \
 Registrador##ConcreteTestFixture() \
 { \
  TestFixtureFactory::theInstance().addFixtureCreator( \
    Creador##ConcreteTestFixture); \
 } \
}; \
static Registrador##ConcreteTestFixture estatic##ConcreteTestFixture;


/**
 * Assert macros to use in test methods. An assert is a test condition
 * we want to check.
 */
#define ASSERT_EQUALS( expected, result) \
 Assert::assertEquals( expected, result, __FILE__, __LINE__ );

#define ASSERT_EQUALS_EPSILON( expected, result, epsilon) \
 Assert::assertEqualsEpsilon( expected, result, epsilon, __FILE__, __LINE__ );

#define ASSERT( exp ) \
 Assert::assertTrue(#exp, exp, __FILE__, __LINE__);

#define ASSERT_MESSAGE( exp, message ) \
 Assert::assertTrueMissatge(#exp, exp, message, __FILE__, __LINE__);

#define FAIL( why ) \
 Assert::fail(#why, __FILE__, __LINE__);

/**
 * Macros that allows to write the  constructor of the concrete TestFixture.
 * What the constructor does is agregate a wrapper for each test case (method)
 * As easy to write as this:
 *
 * @code
 * class MyTests : public TestFixture
 * {
 *  public:
 *   TEST_FIXTURE( MyTests )
 * {
 *  TEST_CASE( test );
 *  // etc
 * }
 * void test()
 * {
 *  ASSERT_EQUALS( 4, 1+1+2 );
 * }
 * @endcode
 */

#define TEST_FIXTURE( ConcreteFixture ) \
 ConcreteFixture() : TestFixture( #ConcreteFixture )

#define TEST_CASE( methodName ) \
 afegeixCasDeTest( this, &ConcreteFixture::methodName, #methodName );

 


       
#endif  // MiniCppUnit_hxx
/*
 * Copyright (c) 2003-2004  Pau Arum?& David Garc韆
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include "MiniCppUnit.hxx"

#include

#ifdef _MSC_VER
#include
namespace std
{
  template
  inline bool isnan(T x) {
  return _isnan(x) != 0;
 }
 template
 inline bool isinf(T x) {
  return _finite(x) == 0;
 }
}
#endif

TestsListener& TestsListener::theInstance()
{
 static TestsListener instancia;
 return instancia;
}

std::stringstream& TestsListener::errorsLog()
{
 if (_currentTestName)
  _log << "\n" << errmsgTag_nameOfTest() << (*_currentTestName) << "\n";
 return _log;
}

std::string TestsListener::logString()
{
 std::string aRetornar = _log.str();
 _log.str("");
 return aRetornar;
}
void TestsListener::currentTestName( std::string& name)
{
 _currentTestName = &name;
}
void TestsListener::testHasRun()
{
 std::cout << ".";
 theInstance()._executed++;
}
void TestsListener::testHasFailed()
{
 std::cout << "F";
 theInstance()._failed++;
 throw TestFailedException();
}
void TestsListener::testHasThrown()
{
 std::cout << "E";
 theInstance()._exceptions++;
}
std::string TestsListener::summary()
{
 std::ostringstream os;
 os << "\nSummary:\n"
  << Assert::bold() << "\tExecuted Tests:         "
  << _executed << Assert::normal() << std::endl
  << Assert::green() << "\tPassed Tests:           "
  << (_executed-_failed-_exceptions)
  << Assert::normal() << std::endl;
 if (_failed > 0)
 {
  os  << Assert::red() << "\tFailed Tests:           "
   << _failed << Assert::normal() << std::endl;
 }
 if (_exceptions > 0)
 {
  os  << Assert::yellow() << "\tUnexpected exceptions:  "
   << _exceptions << Assert::normal() << std::endl;
 }
 os << std::endl;
 return os.str();
}
bool TestsListener::allTestsPassed()
{
 return !theInstance()._exceptions && !theInstance()._failed;
}

 

void Assert::assertTrue(char* strExpression, bool expression,
  const char* file, int linia)
{
 if (!expression)
 {
  TestsListener::theInstance().errorsLog() << "\n"
   << errmsgTag_testFailedIn() << file
   << errmsgTag_inLine() << linia << "\n"
   << errmsgTag_failedExpression()
   << bold() << strExpression << normal() << "\n";
  TestsListener::theInstance().testHasFailed();
 }
}

void Assert::assertTrueMissatge(char* strExpression, bool expression,
  const char* missatge, const char* file, int linia)
{
 if (!expression)
 {
  TestsListener::theInstance().errorsLog() << "\n"
   << errmsgTag_testFailedIn() << file
   << errmsgTag_inLine() << linia << "\n"
   << errmsgTag_failedExpression()
   << bold() << strExpression << "\n"
   << missatge<< normal() << "\n";
  TestsListener::theInstance().testHasFailed();
 }
}

 

void Assert::assertEquals( const char * expected, const char * result,
 const char* file, int linia )
{
 assertEquals(std::string(expected), std::string(result),
  file, linia);

}
void Assert::assertEquals( const bool& expected, const bool& result,
 const char* file, int linia )
{
 assertEquals(
  (expected?"true":"false"),
  (result?"true":"false"),
  file, linia);
}

// floating point numbers comparisons taken
// from c/c++ users journal. dec 04 pag 10
bool isNaN(double x)
{
 bool b1 = (x < 0.0);
 bool b2 = (x >= 0.0);
 return !(b1 || b2);
}

double scaledEpsilon(const double& expected, const double& fuEpsilon )
{
 const double aa = fabs(expected)+1;
 return (std::isinf(aa))? fuEpsilon: fuEpsilon * aa;
}
bool fuEquals(double expected, double result, double fuEpsilon)
{
 return (expected==result) || ( fabs(expected-result) <= scaledEpsilon(expected, fuEpsilon) );
}
void Assert::assertEquals( const double& expected, const double& result,
  const char* file, int linia )

 const double fuEpsilon = 0.000001;
 assertEqualsEpsilon( expected, result, fuEpsilon, file, linia );
}

void Assert::assertEquals( const float& expected, const float& result,
  const char* file, int linia )
{
 assertEquals((double)expected, (double)result, file, linia);
}
void Assert::assertEquals( const long double& expected, const long double& result,
  const char* file, int linia )
{
 assertEquals((double)expected, (double)result, file, linia);
}
void Assert::assertEqualsEpsilon( const double& expected, const double& result, const double& epsilon,
  const char* file, int linia )
{
 if (isNaN(expected) && isNaN(result) ) return;
 if (!isNaN(expected) && !isNaN(result) && fuEquals(expected, result, epsilon) ) return;

 TestsListener::theInstance().errorsLog()
   << errmsgTag_testFailedIn() << file
   << errmsgTag_inLine() << linia << "\n"
   << errmsgTag_expected()
   << bold() << expected << normal() << " "
   << errmsgTag_butWas()
   << bold() << result << normal() << "\n";
 TestsListener::theInstance().testHasFailed();
}

int Assert::notEqualIndex( const std::string & one, const std::string & other )
{
 int end = std::min(one.length(), other.length());
 for ( int index = 0; index < end; index++ )
  if (one[index] != other[index] )
   return index;
 return end;
}


/**
 * we overload the assert with string doing colored diffs
 *
 * MS Visual6 doesn't allow string by reference :-(
 */
void Assert::assertEquals( const std::string expected, const std::string result,
 const char* file, int linia )
{
 if(expected == result)
  return;
 
 int indexDiferent = notEqualIndex(expected, result);
 TestsListener::theInstance().errorsLog()
  << file << ", linia: " << linia << "\n"
  << errmsgTag_expected() << "\n" << blue()
  << expected.substr(0,indexDiferent)
  << green() << expected.substr(indexDiferent)
  << normal() << "\n"
  << errmsgTag_butWas() << blue() << "\n"
  << result.substr(0,indexDiferent)
  << red() << result.substr(indexDiferent)
  << normal() << std::endl;

 TestsListener::theInstance().testHasFailed();
}
void Assert::fail(const char* motiu, const char* file, int linia)
{
 TestsListener::theInstance().errorsLog() <<
  file << errmsgTag_inLine() << linia << "\n" <<
  "Reason: " << motiu << "\n";

 TestsListener::theInstance().testHasFailed();
}

- 作者: 小浪 2008年04月25日, 星期五 15:55  回复(0) |  引用(0) 加入博采

ACE_Message_Block功能简介

摘自:http://www.cnblogs.com/TianFang/archive/2006/12/30/607960.html

ACE_Message_Block在Ace中用来表示消息的存放空间,可用做网络通信中的消息缓冲区,使用非常频繁,下面将在如下方简单的介绍一下ACE_Message_Block相关功能。

  1. 创建消息块
  2. 释放消息块
  3. 从消息块中读写数据
  4. 数据的拷贝
  5. 其它常用函数

1。创建消息块

创建消息块的方式比较灵活,常用的有以下几种方式 :

1。直接给消息块分配内存空间创建。

    ACE_Message_Block *mb = new ACE_Message_Block (30);

2。共享底层数据块创建。

    char buffer[100];
    ACE_Message_Block *mb = new ACE_Message_Block (buffer,30);

这种方式共享底层的数据块,被创建的消息块并不拷贝该数据,也不假定自己拥有它的所有权。在消息块mb被销毁时,相关联的数据缓冲区data将不会被销毁。这是有意义的:消息块没有拷贝数据,因此内存也不是它分配的,这样它也不应该负责销毁它。

3。通过duplicate()函数从已有的消息块中创建副本。

    ACE_Message_Block *mb = new ACE_Message_Block (30);
    ACE_Message_Block *mb2 = mb->duplicate();

这种方式下,mb2和mb共享同一数据空间,使用的是ACE_Message_Block的引用计数机制。它返回指向要被复制的消息块的指针,并在内部增加内部引用计数

4。通过clone()函数从已有的消息块中复制。

    ACE_Message_Block *mb = new ACE_Message_Block (30);
    ACE_Message_Block *mb2 = mb->clone();

clone()方法实际地创建整个消息块的新副本,包括它的数据块和附加部分;也就是说,这是一次"深拷贝"。

2。释放消息块

一旦使用完消息块,程序员可以调用它的release()方法来释放它。

  1. 如果消息数据内存是由该消息块分配的,调用release()方法就也会释放此内存。
  2. 如果消息块是引用计数的,release()就会减少计数,直到到达0为止;之后消息块和与它相关联的数据块才从内存中被移除。
  3. 如果消息块是通过共享已分配的底层数据块创建的,底层数据块不会被释放。

无论消息块是哪种方式创建的,只要在使用完后及时调用release()函数,就能确保相应的内存能正确的释放。

3。从消息块中读写数据

ACE_Message_Block提供了两个指针函数以供程序员进行读写操作,rd_ptr()指向可读的数据块地址,wr_ptr()指向可写的数据块地址,默认情况下都执行数据块的首地址。下面的例子简单了演示它的使用方法。

#include "ace/Message_Queue.h"
#include "ace/OS.h"

int main(int argc, char *argv[])
{
    ACE_Message_Block *mb = new ACE_Message_Block (30);
    ACE_OS::sprintf(mb->wr_ptr(),"%s","hello");
    ACE_OS::printf("%s\n",mb->rd_ptr ());
    mb->release();
    return 0;
}

注意:这两个指针所指向的位置并不会自动移动,在上面的例子中,函数执行完毕后,执行的位置仍然是最开始的0,而不是最新的可写位置5,程序员需要通过wr_ptr(5)函数手动移动写指针的位置。

4。数据的拷贝

一般的数据的拷贝可以通过函数来实现数据的拷贝,copy()还会保证wr_ptr()的更新,使其指向缓冲区的新末尾处。

下面的例子演示了copy()函数的用法。

    mb->copy("hello");
    mb->copy("123",4);

注意:由于c++是以'\0'作为字符串结束标志的,对于上面的例子,底层数据块中保存的是"hello\0123\0",而用ACE_OS::printf("%s\n",mb->rd_ptr ());打印出来的结果是"hello",使用copy函数进行字符串连接的时候需要注意。

5。其它常用函数

  1. length()    返回当前的数据长度
  2. next()    获取和设置下一个ACE_Message_Block的链接。(用来建立消息队列非常有用)
  3. space()    获取剩余可用空间大小
  4. size()    获取和设置数据存储空间大小。

ACE_Message_Block使用心得(摘自:http://dainel-nj.spaces.live.com/blog/cns!bb182726f4560395!107.entry

1 copy() 不需要让写指针后移.
  ACE_Message_Block* mb = new ACE_Message_Block(BUFSIZ);
  mb->copy(buff); //buff先已经初始化
2 初始化mb后需要后移指针的情况
  2.1
  ACE_Message_Block* mb = new ACE_Message_Block(buff,len);
  mb->wt_ptr(len);  //len是buff的长度 len = strlen(buff) +1
                    // +1 表示后面的\0
  2.2
  ACE_Message_Block* mb = new ACE_Message_Block(BUFSIZ);
  ACE_OS::sprintf(mb->wt_ptr(),buff);
  mb->wt_ptr(len);
  2.3
  ACE_Message_Block* mb = new ACE_Message_Block(len,
            ACE_Message_Block::MB_DATA,
            mb2,   //表示 mb->cont(mb2)
            buff)
  mb->wt_ptr(len);
3.让消息接成串cont()时,千万不要直接或接间的把它接成一个环
   mb->cont(mb2);
   mb2->cont(mb3); //ok
   ***mb3->cont(mb);  //死定了
4.通知其它线程结束时,可以通过ACE_Message_Block::MB_STOP
 
  ACE_Message_Block* lastMsg =ACE_Message_Block ,ACE_Message_Block::MB_STOP)
  otherTask->putq(lastMsg);
 
  otherTask在接收到的时候如下处理
 
   int OtherTask::svc()
   {
          ACE_Message_Block* mb;
           while(1)
           {
             getq(mb);
            if(mb->get_tpye() == ACE_Message_Block::MB_STOP)
           {
              mb->release();
              break; //退出这个永久限环)
           }
           else
           {
              handle_message(mb); //处理这条消息
           }
   return 0;
         
    }

- 作者: 小浪 2008年01月11日, 星期五 15:45  回复(0) |  引用(0) 加入博采

CListCtrl 使用技巧

摘自:http://blog.csdn.net/lixiaosan/archive/2006/04/07/653563.aspx

CListCtrl 使用技巧

作者:lixiaosan
时间:04/06/2006

以下未经说明,listctrl默认view 风格为report

相关类及处理函数

MFC:CListCtrl类

SDK:以 “ListView_”开头的一些宏。如 ListView_InsertColumn


1. CListCtrl 风格

      LVS_ICON: 为每个item显示大图标
      LVS_SMALLICON: 为每个item显示小图标
      LVS_LIST: 显示一列带有小图标的item
      LVS_REPORT: 显示item详细资料

      直观的理解:windows资源管理器,“查看”标签下的“大图标,小图标,列表,详细资料”



2. 设置listctrl 风格及扩展风格

      LONG lStyle;
      lStyle = GetWindowLong(m_list.m_hWnd, GWL_STYLE);//获取当前窗口style
      lStyle &= ~LVS_TYPEMASK; //清除显示方式位
      lStyle |= LVS_REPORT; //设置style
      SetWindowLong(m_list.m_hWnd, GWL_STYLE, lStyle);//设置style
 
      DWORD dwStyle = m_list.GetExtendedStyle();
      dwStyle |= LVS_EX_FULLROWSELECT;//选中某行使整行高亮(只适用与report风格的listctrl)
      dwStyle |= LVS_EX_GRIDLINES;//网格线(只适用与report风格的listctrl)
      dwStyle |= LVS_EX_CHECKBOXES;//item前生成checkbox控件
      m_list.SetExtendedStyle(dwStyle); //设置扩展风格
 
      注:listview的style请查阅msdn
      http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wceshellui5/html/wce50lrflistviewstyles.asp

 


3. 插入数据

      m_list.InsertColumn( 0, "ID", LVCFMT_LEFT, 40 );//插入列
      m_list.InsertColumn( 1, "NAME", LVCFMT_LEFT, 50 );
      int nRow = m_list.InsertItem(0, “11”);//插入行
      m_list.SetItemText(nRow, 1, “jacky”);//设置数据

 


4. 一直选中item

    选中style中的Show selection always,或者在上面第2点中设置LVS_SHOWSELALWAYS



5. 选中和取消选中一行

    int nIndex = 0;
    //选中
    m_list.SetItemState(nIndex, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED);
    //取消选中
    m_list.SetItemState(nIndex, 0, LVIS_SELECTED|LVIS_FOCUSED);
 


6. 得到listctrl中所有行的checkbox的状态

      m_list.SetExtendedStyle(LVS_EX_CHECKBOXES);
      CString str;
      for(int i=0; i      {
           if( m_list.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED || m_list.GetCheck(i))
           {
                str.Format(_T("第%d行的checkbox为选中状态"), i);
                AfxMessageBox(str);
           }
      }



7. 得到listctrl中所有选中行的序号


      方法一:
      CString str;
      for(int i=0; i      {
           if( m_list.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED )
           {
                str.Format(_T("选中了第%d行"), i);
                AfxMessageBox(str);
           }
      }

      方法二:
      POSITION pos = m_list.GetFirstSelectedItemPosition();
      if (pos == NULL)
           TRACE0("No items were selected!\n");
      else
      {
           while (pos)
           {
                int nItem = m_list.GetNextSelectedItem(pos);
                TRACE1("Item %d was selected!\n", nItem);
                // you could do your own processing on nItem here
           }
      }



8. 得到item的信息

      TCHAR szBuf[1024];
      LVITEM lvi;
      lvi.iItem = nItemIndex;
      lvi.iSubItem = 0;
      lvi.mask = LVIF_TEXT;
      lvi.pszText = szBuf;
      lvi.cchTextMax = 1024;
      m_list.GetItem(&lvi);

      关于得到设置item的状态,还可以参考msdn文章
      Q173242: Use Masks to Set/Get Item States in CListCtrl
               http://support.microsoft.com/kb/173242/en-us



9. 得到listctrl的所有列的header字符串内容

      LVCOLUMN lvcol;
      char  str[256];
      int   nColNum;
      CString  strColumnName[4];//假如有4列

      nColNum = 0;
      lvcol.mask = LVCF_TEXT;
      lvcol.pszText = str;
      lvcol.cchTextMax = 256;
      while(m_list.GetColumn(nColNum, &lvcol))
      {
           strColumnName[nColNum] = lvcol.pszText;
           nColNum++;
      }



10. 使listctrl中一项可见,即滚动滚动条

    m_list.EnsureVisible(i, FALSE);


11. 得到listctrl列数

    int nHeadNum = m_list.GetHeaderCtrl()->GetItemCount();


12. 删除所有列

      方法一:
         while ( m_list.DeleteColumn (0))
       因为你删除了第一列后,后面的列会依次向上移动。

      方法二:
      int nColumns = 4;
      for (int i=nColumns-1; i>=0; i--)
          m_list.DeleteColumn (i);



13. 得到单击的listctrl的行列号

      添加listctrl控件的NM_CLICK消息相应函数
      void CTest6Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
      {
           // 方法一:
           /*
           DWORD dwPos = GetMessagePos();
           CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
  
           m_list.ScreenToClient(&point);
  
           LVHITTESTINFO lvinfo;
           lvinfo.pt = point;
           lvinfo.flags = LVHT_ABOVE;
    
           int nItem = m_list.SubItemHitTest(&lvinfo);
           if(nItem != -1)
           {
                CString strtemp;
                strtemp.Format("单击的是第%d行第%d列", lvinfo.iItem, lvinfo.iSubItem);
                AfxMessageBox(strtemp);
           }
          */
  
          // 方法二:
          /*
           NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
           if(pNMListView->iItem != -1)
           {
                CString strtemp;
                strtemp.Format("单击的是第%d行第%d列",
                                pNMListView->iItem, pNMListView->iSubItem);
                AfxMessageBox(strtemp);
           }
          */
           *pResult = 0;
      }

 


14. 判断是否点击在listctrl的checkbox上

      添加listctrl控件的NM_CLICK消息相应函数
      void CTest6Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
      {
           DWORD dwPos = GetMessagePos();
           CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
  
           m_list.ScreenToClient(&point);
  
           LVHITTESTINFO lvinfo;
           lvinfo.pt = point;
           lvinfo.flags = LVHT_ABOVE;
    
           UINT nFlag;
           int nItem = m_list.HitTest(point, &nFlag);
           //判断是否点在checkbox上
           if(nFlag == LVHT_ONITEMSTATEICON)
           {
                AfxMessageBox("点在listctrl的checkbox上");
           }
           *pResult = 0;
      }



15. 右键点击listctrl的item弹出菜单

      添加listctrl控件的NM_RCLICK消息相应函数
      void CTest6Dlg::OnRclickList1(NMHDR* pNMHDR, LRESULT* pResult)
      {
           NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
           if(pNMListView->iItem != -1)
           {
                DWORD dwPos = GetMessagePos();
                CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
   
                CMenu menu;
                VERIFY( menu.LoadMenu( IDR_MENU1 ) );
                CMenu* popup = menu.GetSubMenu(0);
                ASSERT( popup != NULL );
                popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this );
           }
           *pResult = 0;
  }


 


16. item切换焦点时(包括用键盘和鼠标切换item时),状态的一些变化顺序

      添加listctrl控件的LVN_ITEMCHANGED消息相应函数
      void CTest6Dlg::OnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult)
      {
           NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
           // TODO: Add your control notification handler code here
   
           CString sTemp;
 
           if((pNMListView->uOldState & LVIS_FOCUSED) == LVIS_FOCUSED &&
            (pNMListView->uNewState & LVIS_FOCUSED) == 0)
           {
                sTemp.Format("%d losted focus",pNMListView->iItem);
           }
           else if((pNMListView->uOldState & LVIS_FOCUSED) == 0 &&
               (pNMListView->uNewState & LVIS_FOCUSED) == LVIS_FOCUSED)
           {
                sTemp.Format("%d got focus",pNMListView->iItem);
           }
 
           if((pNMListView->uOldState & LVIS_SELECTED) == LVIS_SELECTED &&
            (pNMListView->uNewState & LVIS_SELECTED) == 0)
           {
                sTemp.Format("%d losted selected",pNMListView->iItem);
           }
           else if((pNMListView->uOldState & LVIS_SELECTED) == 0 &&
            (pNMListView->uNewState & LVIS_SELECTED) == LVIS_SELECTED)
           {
                sTemp.Format("%d got selected",pNMListView->iItem);
           }
   
           *pResult = 0;
      }




17. 得到另一个进程里的listctrl控件的item内容

http://www.codeproject.com/threads/int64_memsteal.asp



18. 选中listview中的item

Q131284: How To Select a Listview Item Programmatically
http://support.microsoft.com/kb/131284/en-us



19. 如何在CListView中使用CListCtrl的派生类

http://www.codeguru.com/cpp/controls/listview/introduction/article.php/c919/



20. listctrl的subitem添加图标

      m_list.SetExtendedStyle(LVS_EX_SUBITEMIMAGES);
      m_list.SetItem(..); //具体参数请参考msdn

 


21. 在CListCtrl显示文件,并根据文件类型来显示图标

      网上找到的代码,share
      BOOL CTest6Dlg::OnInitDialog()
      {
           CDialog::OnInitDialog();
  
           HIMAGELIST himlSmall;
           HIMAGELIST himlLarge;
           SHFILEINFO sfi;
           char  cSysDir[MAX_PATH];
           CString  strBuf;
 
           memset(cSysDir, 0, MAX_PATH);
  
           GetWindowsDirectory(cSysDir, MAX_PATH);
           strBuf = cSysDir;
           sprintf(cSysDir, "%s", strBuf.Left(strBuf.Find("\\")+1));
 
           himlSmall = (HIMAGELIST)SHGetFileInfo ((LPCSTR)cSysDir, 
                      0, 
                      &sfi,
                      sizeof(SHFILEINFO), 
                      SHGFI_SYSICONINDEX | SHGFI_SMALLICON );
  
           himlLarge = (HIMAGELIST)SHGetFileInfo((LPCSTR)cSysDir, 
                      0, 
                      &sfi, 
                      sizeof(SHFILEINFO), 
                      SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
  
           if (himlSmall && himlLarge)
           {
                ::SendMessage(m_list.m_hWnd, LVM_SETIMAGELIST,
                             (WPARAM)LVSIL_SMALL, (LPARAM)himlSmall);
                ::SendMessage(m_list.m_hWnd, LVM_SETIMAGELIST,
                             (WPARAM)LVSIL_NORMAL, (LPARAM)himlLarge);
           }
           return TRUE;  // return TRUE  unless you set the focus to a control
      }
 
      void CTest6Dlg::AddFiles(LPCTSTR lpszFileName, BOOL bAddToDocument)
      {
           int nIcon = GetIconIndex(lpszFileName, FALSE, FALSE);
           CString strSize;
           CFileFind filefind;
 
           //  get file size
           if (filefind.FindFile(lpszFileName))
           {
                filefind.FindNextFile();
                strSize.Format("%d", filefind.GetLength());
           }
           else
                strSize = "0";
  
           // split path and filename
           CString strFileName = lpszFileName;
           CString strPath;
 
           int nPos = strFileName.ReverseFind('\\');
           if (nPos != -1)
           {
                strPath = strFileName.Left(nPos);
                strFileName = strFileName.Mid(nPos + 1);
           }
  
           // insert to list
           int nItem = m_list.GetItemCount();
           m_list.InsertItem(nItem, strFileName, nIcon);
           m_list.SetItemText(nItem, 1, strSize);
           m_list.SetItemText(nItem, 2, strFileName.Right(3));
           m_list.SetItemText(nItem, 3, strPath);
      }
 
      int CTest6Dlg::GetIconIndex(LPCTSTR lpszPath, BOOL bIsDir, BOOL bSelected)
      {
           SHFILEINFO sfi;
           memset(&sfi, 0, sizeof(sfi));
  
           if (bIsDir)
           {
            SHGetFileInfo(lpszPath, 
                         FILE_ATTRIBUTE_DIRECTORY, 
                         &sfi, 
                         sizeof(sfi), 
                         SHGFI_SMALLICON | SHGFI_SYSICONINDEX |
                         SHGFI_USEFILEATTRIBUTES |(bSelected ? SHGFI_OPENICON : 0)); 
            return  sfi.iIcon;
           }
           else
           {
            SHGetFileInfo (lpszPath, 
                         FILE_ATTRIBUTE_NORMAL, 
                         &sfi, 
                         sizeof(sfi), 
                         SHGFI_SMALLICON | SHGFI_SYSICONINDEX | 
                         SHGFI_USEFILEATTRIBUTES | (bSelected ? SHGFI_OPENICON : 0));
            return   sfi.iIcon;
           }
           return  -1;
      }



22. listctrl内容进行大数据量更新时,避免闪烁

      m_list.SetRedraw(FALSE);
      //更新内容
      m_list.SetRedraw(TRUE);
      m_list.Invalidate();
      m_list.UpdateWindow();
 
或者参考

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_mfc_cwnd.3a3a.setredraw.asp



23. listctrl排序

Q250614:How To Sort Items in a CListCtrl in Report View
http://support.microsoft.com/kb/250614/en-us



24. 在listctrl中选中某个item时动态改变其icon或bitmap

Q141834: How to change the icon or the bitmap of a CListCtrl item in Visual C++
http://support.microsoft.com/kb/141834/en-us



25. 在添加item后,再InsertColumn()后导致整列数据移动的问题

Q151897: CListCtrl::InsertColumn() Causes Column Data to Shift
http://support.microsoft.com/kb/151897/en-us



26. 关于listctrl第一列始终居左的问题

解决办法:把第一列当一个虚列,从第二列开始插入列及数据,最后删除第一列。
     
具体解释参阅   http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/listview/structures/lvcolumn.asp

 


27. 锁定column header的拖动

http://msdn.microsoft.com/msdnmag/issues/03/06/CQA/



28. 如何隐藏clistctrl的列

    把需隐藏的列的宽度设为0,然后检测当该列为隐藏列时,用上面第27点的锁定column 的拖动来实现


29. listctrl进行大数据量操作时,使用virtual list   

http://www.microsoft.com/msj/archive/S2061.aspx
http://www.codeguru.com/cpp/controls/listview/advanced/article.php/c4151/
http://www.codeproject.com/listctrl/virtuallist.asp



30. 关于item只能显示259个字符的问题

解决办法:需要在item上放一个edit。



31. 响应在listctrl的column header上的鼠标右键单击

Q125694: How To Find Out Which Listview Column Was Right-Clicked
http://support.microsoft.com/kb/125694/en-us



32. 类似于windows资源管理器的listview

Q234310: How to implement a ListView control that is similar to Windows Explorer by using DirLV.exe
http://support.microsoft.com/kb/234310/en-us

 


33. 在ListCtrl中OnTimer只响应两次的问题

Q200054:
PRB: OnTimer() Is Not Called Repeatedly for a List Control
http://support.microsoft.com/kb/200054/en-us


34. 以下为一些为实现各种自定义功能的listctrl派生类

          (1)    拖放       
                   http://www.codeproject.com/listctrl/dragtest.asp

                   在CListCtrl和CTreeCtrl间拖放
                   http://support.microsoft.com/kb/148738/en-us
 
          (2)    多功能listctrl
                   支持subitem可编辑,图标,radiobutton,checkbox,字符串改变颜色的类
                   http://www.codeproject.com/listctrl/quicklist.asp
 
                   支持排序,subitem可编辑,subitem图标,subitem改变颜色的类
                   http://www.codeproject.com/listctrl/ReportControl.asp

          (3)    subitem中显示超链接
                   http://www.codeproject.com/listctrl/CListCtrlLink.asp

          (4)    subitem的tooltip提示
                   http://www.codeproject.com/listctrl/ctooltiplistctrl.asp

          (5)    subitem中显示进度条   
                   http://www.codeproject.com/listctrl/ProgressListControl.asp
                   http://www.codeproject.com/listctrl/napster.asp
                   http://www.codeguru.com/Cpp/controls/listview/article.php/c4187/

          (6)    动态改变subitem的颜色和背景色
                    http://www.codeproject.com/listctrl/highlightlistctrl.asp
                    http://www.codeguru.com/Cpp/controls/listbox/colorlistboxes/article.php/c4757/
 
          (7)    类vb属性对话框
                    http://www.codeproject.com/listctrl/propertylistctrl.asp
                    http://www.codeguru.com/Cpp/controls/listview/propertylists/article.php/c995/
                    http://www.codeguru.com/Cpp/controls/listview/propertylists/article.php/c1041/
 
          (8)    选中subitem(只高亮选中的item)
                    http://www.codeproject.com/listctrl/SubItemSel.asp
                    http://www.codeproject.com/listctrl/ListSubItSel.asp
 
          (9)    改变行高
                    http://www.codeproject.com/listctrl/changerowheight.asp
 
          (10)   改变行颜色
                    http://www.codeproject.com/listctrl/coloredlistctrl.asp
 
          (11)   可编辑subitem的listctrl
                    http://www.codeproject.com/listctrl/nirs2000.asp
                    http://www.codeproject.com/listctrl/editing_subitems_in_listcontrol.asp
 
          (12)   subitem可编辑,插入combobox,改变行颜色,subitem的tooltip提示
                    http://www.codeproject.com/listctrl/reusablelistcontrol.asp
 
          (13)   header 中允许多行字符串
                    http://www.codeproject.com/listctrl/headerctrlex.asp
 
          (14)   插入combobox
                    http://www.codeguru.com/Cpp/controls/listview/editingitemsandsubitem/article.php/c979/
 
          (15)   添加背景图片
                    http://www.codeguru.com/Cpp/controls/listview/backgroundcolorandimage/article.php/c4173/
                    http://www.codeguru.com/Cpp/controls/listview/backgroundcolorandimage/article.php/c983/
                    http://www.vchelp.net/vchelp/archive.asp?type_id=9&class_id=1&cata_id=1&article_id=1088&search_term=
   
          (16)  自适应宽度的listctrl
                    http://www.codeproject.com/useritems/AutosizeListCtrl.asp

          (17)  改变ListCtrl高亮时的颜色(默认为蓝色)
                   处理 NM_CUSTOMDRAW
          
http://www.codeproject.com/listctrl/lvcustomdraw.asp

     (18)  改变header颜色
         
http://www.pocketpcdn.com/articles/hdr_color.html

- 作者: 小浪 2007年12月20日, 星期四 10:15  回复(2) |  引用(0) 加入博采

VC其它- VC常用小技巧

摘自:http://blog.csdn.net/MasterFT/archive/2007/05/15/1609608.aspx

窗口

让窗口一启动就最大化
把应用程序类(CxxxApp)的 InitInstance() 函数中的
m_pMainWnd->ShowWindow(SW_SHOW);
改为
m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);
则窗口一启动就最大化显示。

如何设置窗口的初始尺寸
在将应用程序类(CxxAPP)的 InitInstance() 函数中加入:
m_pMainWnd->SetWindowPos(NULL,x,y,Width,Height,SWP_NOMOVE);
Width
为窗口宽度,Height为窗口高度
SWP_NOMOVE
表示忽略位置(x,y)
如:

让窗口居中显示
以下两种方法可任选其一:
在应用程序类(CxxxApp)的 InitInstance() 函数中加入:
在主框架类(MainFrm.cpp)OnCreate()函数中加入:
CenterWindow( GetDesktopWindow() );
如:如何修改窗口标题
窗口标题一般形式为:文档标题 - 程序标题
1
、设置文档标题:
在文档类(CxxxDoc)OnNewDocument()函数中加入语句:SetTitle("文档名");
如:TextEditorDoc.cpp
可删除Debug文件夹和Release文件夹;
原则上还可删除主文件夹中所有图标为 的文件,包括.aps.ncb.opt.plg等文件,它们都能在编译时重建。但一般.clw不要删除,它可能导致ClassWizard不好用。



控件
如何隐藏和显示控件
CWnd类的函数BOOL ShowWindow(int nCmdShow)可以隐藏或显示一个控件。
1
CWnd *pWnd;
pWnd = GetDlgItem( IDC_EDIT1 ); //
获取控件指针,IDC_EDIT为控件ID
pWnd->ShowWindow( SW_HIDE ); //
隐藏控件
2
CWnd *pWnd;
pWnd = GetDlgItem( IDC_EDIT1 ); //
获取控件指针,IDC_EDIT为控件ID
pWnd->ShowWindow( SW_SHOW ); //
显示控件

按钮的使能与禁止
ClassWizardMember Variables为按钮定义变量,如:m_Button1

m_Button1.EnableWindow(true);
使按钮处于允许状态
m_Button1.EnableWindow(false);
使按钮被禁止,并变灰显示

改变控件的大小和位置
CWnd类的函数MoveWindow()SetWindowPos()可以改变控件的大小和位置。
void MoveWindow(int x,int y,int nWidth,int nHeight);
void MoveWindow(LPCRECT lpRect);
第一种用法需给出控件新的坐标和宽度、高度;
第二种用法给出存放位置的CRect对象;
例:
CWnd *pWnd;
pWnd = GetDlgItem( IDC_EDIT1 ); //
获取控件指针,IDC_EDIT1为控件ID
pWnd->MoveWindow( CRect(0,0,100,100) ); //
在窗口左上角显示一个宽100、高100的编辑控件
SetWindowPos()
函数使用更灵活,多用于只修改控件位置而大小不变或只修改大小而位置不变的情况:
BOOL SetWindowPos(const CWnd* pWndInsertAfter,int x,int y,int cx,int cy,UINT nFlags);
第一个参数一般设为NULL;
x
y控件位置;cxcy控件宽度和高度;
nFlags
常用取值:
SWP_NOZORDER
:忽略第一个参数;
SWP_NOMOVE
:忽略xy,维持位置不变;
SWP_NOSIZE
:忽略cxcy,维持大小不变;
例:
CWnd *pWnd;
pWnd = GetDlgItem( IDC_BUTTON1 ); //
获取控件指针,IDC_BUTTON1为控件ID
pWnd->SetWindowPos( NULL,50,80,0,0,SWP_NOZORDER | SWP_NOSIZE ); //
把按钮移到窗口的(50,80)
pWnd = GetDlgItem( IDC_EDIT1 );
pWnd->SetWindowPos( NULL,0,0,100,80,SWP_NOZORDER | SWP_NOMOVE ); //
把编辑控件的大小设为(100,80),位置不变
pWnd = GetDlgItem( IDC_EDIT1 );
pWnd->SetWindowPos( NULL,0,0,100,80,SWP_NOZORDER ); //
编辑控件的大小和位置都改变
以上方法也适用于各种窗口。

单选按钮控件(Radio Button)的使用
一、对单选按钮进行分组:
每组的第一个单选按钮设置属性:GroupTabstopAuto;其余按钮设置属性TabstopAuto
如:
Radio1
Radio2Radio3为一组,Radio4Radio5为一组
设定Radio1属性:GroupTabstopAuto
设定Radio2属性:TabstopAuto
设定Radio3属性:TabstopAuto
设定Radio4属性:GroupTabstopAuto
设定Radio5属性:TabstopAuto
二、用ClassWizard为单选控件定义变量,每组只能定义一个。如:m_Radio1m_Radio4
三、用ClassWizard生成各单选按钮的单击消息函数,并加入内容:
void CWEditView::OnRadio1()
{
m_Radio1 = 0; //
第一个单选按钮被选中
}
void CWEditView::OnRadio2()
{
m_Radio1 = 1; //
第二个单选按钮被选中
}
void CWEditView::OnRadio3()
{
m_Radio1 = 2; //
第三个单选按钮被选中
}
void CWEditView::OnRadio4()
{
m_Radio4 = 0; //
第四个单选按钮被选中
}
void CWEditView::OnRadio5()
{
m_Radio4 = 1; //
第五个单选按钮被选中
}
当控件变量值为0时,它对应组的第一个单选按钮处于选中状态。

BOOL CDzyApp::InitInstance() 

AfxEnableControlContainer(); 
…… 

// The one and only window has been initialized, so show and update it. 
m_pMainWnd->SetWindowPos(NULL,0,0,750,555,SWP_NOMOVE);//设置窗口的初始大小为750*555 
m_pMainWnd->ShowWindow(SW_SHOW); 
m_pMainWnd
->UpdateWindow(); 

return TRUE; 
}


m_pMainWnd->CenterWindow( GetDesktopWindow() );

 

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) 
if (CFrameWnd::OnCreate(lpCreateStruct) == -1
return -1
…… 

// TODO: Delete these three lines if you don't want the toolbar to 
// be dockable 
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); 
EnableDocking(CBRS_ALIGN_ANY); 
DockControlBar(
&m_wndToolBar); 

CenterWindow( GetDesktopWindow() ); 
//使窗口打开时处于屏幕正中

return 0
}


BOOL CTextEditorDoc::OnNewDocument() 
if (!CDocument::OnNewDocument()) 
return FALSE; 
// TODO: add reinitialization code here 
// (SDI documents will reuse this document) 
SetTitle("未命名.txt"); //设置文档标题 
return TRUE; 
}


2、设置程序标题:
在框架类(CMainFrame)PreCreateWindow()函数中加入语句:m_strTitle = _T("程序标题");
如:MainFrm.cpp

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) 
if!CFrameWnd::PreCreateWindow(cs) ) 
return FALSE; 
// TODO: Modify the Window class or styles here by modifying 
// the CREATESTRUCT cs 
cs.style&=~FWS_ADDTOTITLE;//去除标题栏文字前面的"无标题"
m_strTitle = _T("文本整理器"); //设置程序标题 
return TRUE; 
}

以上两点比较适用于视图-文档结构的程序,在新建文档时,系统会自动运行OnNewDocument()函数,在其中可以设置合适的标题。对于未采用文档的程序可以用下面的方法修改标题:
3
、修改窗口标题:
修改窗口标题一般在打开文件函数OnFileOpen()和另存为函数OnFileSaveAs()中进行,可以使用下面的函数:其中文档标题和程序标题可使用定义过的串变量。



项目
如何干净的删除一个类?
1、先删除项目中对应的.h.cpp文件,(选中后用Delete键删除)
2
、保存后退出项目,到文件夹中删除实际的.h.cpp文件;
3
、删除.clw文件;
4
、重新进入项目,进行全部重建(rebuild all)。

如何建立一个新类?
   
插入”(Insert)菜单中选择新建类”(New Class),在弹出的对话框中选择基类(Base class),在Name中输入新类的名字(一般都以C开头)即可。
如果想要建立一个没有基类的自定义类,则在New Class对话框中把Class type设置为generic,再输入类名即可。

如何把外来文件添加到项目中?
   
先把外来文件复制到当前项目的目录下,从项目”(Project)菜单下选择添加项目”(Add to Project)下的“Files”菜单项,从弹出的打开文件对话框中把外来文件打开即可。

如何在一个工作区中打开多个项目?
   
一般编程者都有这样的经历:做了一个项目,由于不满意,想从头重做,但又想把旧项目的一些可用内容拷到新项目中来,以免做重复工作,这时就需要在新项目中打开旧项目。
   
先打开新项目,从项目”(Project)菜单下选择插入项目到工作区”(Insert Project into Workspace),从弹出的打开文件对话框中打开旧项目的.asp文件即可。
   
之后,可以利用项目”(Project)菜单下的设置活动项目”(Select Active Project)的选项中切换各打开的项目。
注意:在一个工作区中打开的各项目不能同名。

如何把项目中的文件分类存放?
当我们往项目中添加新类时,它会把源文件放在Source Files下,头文件放在Header Files下。当项目中文件很多时,管理不便,最好添加新节点,把文件分类放置。
右击项目节点树的根节点,选择“New Folder...”,在弹出的对话框中填入新节点名,则新节点就建立了,用鼠标节点树中的文件拖入新节点,就可以把文件分类了。
以上分类只是在项目的节点树中分类,它不影响文件在磁盘上的位置,所有.cpp文件和.h文件仍在项目的根目录下,最好文件本身也能分类存放在不同文件夹中。
Windows下,用新建文件夹在项目的根目录下建立子文件夹,如DialogClass,把所有对话框类的.cpp文件和.h文件拖入其中。
回到VC下,右键单击项目树中更改了路径的节点,选择“Properties”,在弹出的对话框中修改文件路径,如:把原路径“.\Dialog1.cpp”改为“.\DialogClass\Dialog1.cpp”
打开Dialog1.cpp文件,修改它包含的文件路径。如:
#include "stdafx.h"
#include "PluckBox.h"
#include "Dialog1.h"
改为:
#include "stdafx.h"
#include "..\\PluckBox.h"
#include "Dialog1.h"
打开ClassWizard,它会提示你文件不存在,单击确定后,从对话框中用“Browse...”选择文件所在路径,则ClassWizard也可正常使用了。



编辑
编辑代码时,跟随提示消失了怎么办?
单 击工具”(Tools)菜单中的设置”(Options)菜单项,在弹出的Options对话框中选择Editor制表页,把它最下方的四个复选框都 选中(Auto list memberAuto type infoCode commentsAuto parameter info),这样,当用户输入“->”“.”时,会自动显示跟随提示,减少了输入负担。



对话框
如何修改对话框的背景色
在对话框的OnPaint()函数中加入下面语句:
CRect rect;
GetClientRect(&rect); //
计算对话框的尺寸
dc.FillSolidRect(&rect,RGB(192,248,202)); //
绘制对话框背景色

如何让弹出式对话框具有统一的背景色
在应用程序类CxxxAppInitInstance()函数中加入下面的语句:
SetDialogBkColor( RGB(192,248,202) );
则所有用户定义的弹出式对话框都以RGB(192,248,202)为背景色,就不需要逐个进行设置了。


如何让打开文件对话框能进行多项选择
在定制打开文件对话框时,增加OFN_ALLOWMULTISELECT属性,就可以使打开文件对话框进行多选了。
如:
CFileDialog m_Dlg( TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT
| OFN_ALLOWMULTISELECT, NULL, NULL );
之后,用GetStartPosition()函数获取选择的起始文件位置,用GetNextPathName()函数获取各位置上的文件名。
如:
if( m_Dlg.DoModal() == IDOK )
{
POSITION pos;
pos = m_Dlg.GetStartPosition();
while( pos )
{
m_Path = m_Dlg.GetNextPathName(pos);
…………
}
}

为什么用打开文件对话框选择多个文件到一定数目时,文件没有打开?
CFileDialog
为文件列表设置有缓冲区,当选择文件过多时,会造成缓冲区溢出,造成一些文件没有被打开。可以采用自定义大缓冲区代替系统缓冲区的方法解决。
如:
CFileDialog m_Dlg( TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT
| OFN_ALLOWMULTISELECT, NULL, NULL );//
定制打开文件对话框
char* pBuf = new char[20480]; //
申请缓冲区
m_Dlg.m_ofn.nMaxFile = 20480; //
pBuf代替CFileDialog缓冲区
m_Dlg.m_ofn.lpstrFile = pBuf;
m_Dlg.m_ofn.lpstrFile[0] = NULL;
…………
delete []pBuf; //
回收缓冲区

提示对话框(MessageBox
在视类和对话框类中可使用MFC函数中用的MessageBox()函数弹出提示对话框。这个函数原型为:
int MessageBox(LPCTSTR lpszText,LPCTSTR lpsCaption=NULL,UINT nType=MB_OK);
参数:lpszText 显示的字符串
lpsCaption
对话框的标题
nType
风格,可为如下值的组合:
指定下列标志中的一个来显示消息框中的按钮,标志的含义如下。
MB_ABORTRETRYIGNORE
:消息框含有三个按钮:AbortRetryIgnore
MB_OK
:消息框含有一个按钮:OK。这是缺省值。
MB_OKCANCEL
:消息框含有两个按钮:OKCancel
MB_RETRYCANCEL
:消息框含有两个按钮:RetryCancel
MB_YESNO
:消息框含有两个按钮:YesNo
MB_YESNOCANCEL
:消息框含有三个按钮:YesNoCancel
指定下列标志中的一个来显示消息框中的图标:标志的含义如下。
MB_ICONEXCLAMATION:
MB_ICONWARNING
:一个惊叹号出现在消息框。
MB_ICONINFORMATION

MB_ICONASTERISK
:一个圆圈中小写字母i组成的图标出现在消息框。
MB_ICONOUESTION:
一个问题标记图标出现在消息框。
MB_ICONSTOP:
MB_ICONERROR

MB_ICONHAND
:一个停止消息图标出现在消息框。
指定下列标志中的一个来指定缺省的按钮:标志的含义如下。
MB_DEFBUTTON1
:第一个按钮为缺省按钮。如果MB_DEFBUTTON2MB_DEFBUTTON3MB_DEFBUTTON4没有被指定,则MB_DEFBUTTON1为缺省值。
MB_DEFBUTTON2
;第二个按钮为缺省按钮。
MB_DEFBUTTON3
:第三个按钮为缺省按钮。
MB_DEFBUTTON4
:第四个按钮为缺省按钮。
例:提示文件是否存盘:
int t;
t=MessageBox(m_PathName+"
的文字已经改变,要存盘吗?",
"
警告",MB_YESNOCANCEL | MB_ICONWARNING);
if(t==0 || t==IDCANCEL)
return;
if(t==IDYES)
OnFileSave();
在文档类等其它类中不能使用MFC中的MessageBox()函数,只能使用API函数中的MessageBox()函数:
int MessageBox(HWND hWnd,LPCTSTR lpszText,LPCTSTR lpCaption,UINT UType);
hWnd:
标识将被创建的消息框的拥有窗口。如果此参数为NULL,则消息框没有拥有窗口。
后三个参数与视类的MessageBox相同,但没有缺省值,必须设置。
例:::MessageBox(NULL,m_PathName+"的文字已经改变,要存盘吗?",
"
警告",MB_YESNOCANCEL | MB_ICONWARNING);



调试
error C2146: syntax error : missing ';' before identifier ……
如果出现这个错误且错误数目很多,通常并不是缺失了分号引起的,而是忘记了添加某头文件引起的。
最常见的是新加入了对话框,然后用它的类定义了一个对象,再编译出现上面的错误。
解决方法是在引用新类的文件中加入#include "类名.h",再编译,错误消失。

fatal error C1010: unexpected end of file while looking for precompiled header directive
在一个项目中,如果用“New”向工程中添加了一个.cpp文件,编译,出错。
解决方法:
                  1)  在新建的.cpp文件的开头加入#include "stdafx.h"
                  2)  可以使用右键点击项目工程中的该cpp文件,选择setting,在c/c++栏,选择PreCompiled headers,然后设置第一选项,选择不使用预编译头,解决这个问题。发布
Debug
模式和Release模式
早就发现用VC编译出来的.exe文件比用Turbo C编译出来的文件大了许多,于是就认为VC编译时一定加了很多没用的东西,记得当时还做过把VC自动生成的项目中自认为没用的函数都删掉的傻事。后来才从网上的文章中了解到还有编译模式一说。
Debug
模式是用来调试用的,它生成的执行文件中含有大量调试信息,所以很大;
Release
模式生成的执行文件消除了这些调试信息,可用来作为成品发布。
默 认情况下是Debug模式,切换方法是在编译”(Build)菜单中选设置项目配置”(Set Active Configure)。从弹出的对话框中选择Win32 Release模式,然后再重新编译。这时在工作目录下会多出一个Release目录,其中的exe文件比Debug目录下的那个要小得多。

动态链接库和静态链接库
VC 做好了一个程序,拿到别人那里却不能运行,这也是很多编程者都经历过的,这样的软件只能在安装有VC的机器上运行,也不应拿出去发布。实际上如果你没有使 用ActiveX控件和自定义的动态DLL技术,只需把MFC的动态链接库打包到你的程序里就可以了,也就是使用静态链接库。
设置方法:从项目”(Project)菜单下选择设置” (Settings),在弹出的对话框中的General选项卡下,把“User MFC in a Shared DLL”改为“User MFC in a Static Library”,关闭对话框后重新编译即可。
在静态链接库下编译的文件比动态链接库的要大很多,不过,如果使用Release模式编译,一般也就几百K,它就可以在没有安装VC的机器上运行了。

发布VC源代码时,哪些文件可以删除?

AfxGetMainWnd()->SetWindowText("文档标题"+" - "+"程序标题");

 


四、设置默认按钮:
在定义控件变量时,ClassWizard在构造函数中会把变量初值设为-1,只需把它改为其它值即可。
如:
//{{AFX_DATA_INIT(CWEditView)
m_Ridio1 = 0; //
初始时第一个单选按钮被选中
m_Ridio4 = 0; //
初始时第四个单选按钮被选中
//}}AFX_DATA_INIT

旋转控件(Spin)的使用
当单击旋转控件上的按钮时,相应的编辑控件值会增大或减小。其设置的一般步骤为:
一、在对话框中放入一个Spin控件和一个编辑控件作为Spin控件的伙伴窗口
设置Spin控件属性:Auto buddySet buddy integerArrow keys
设置文本控件属性:Number
二、用ClassWizardSpin控件定义变量m_Spin,为编辑控件定义变量m_Edit,定义时注意要把m_Edit设置为int型。
三、在对话框的OnInitDialog()函数中加入语句:
BOOL CMyDlg::OnInitDialog()
{
CDialog::OnInitDialog();

m_Spin.SetBuddy( GetDlgItem( IDC_EDIT1 ) ); //
设置编辑控件为Spin控件的伙伴窗口
m_Spin.SetRange( 0, 10 ); //
设置数据范围为0-10
return TRUE;
}
四、用ClassWizard为编辑控件添加EN_CHANGE消息处理函数,再加入语句:
void CMyDlg::OnChangeEdit1()
{
m_Edit = m_Spin.GetPos(); //
获取Spin控件当前值
}

UpdateData()
对于可以接收数据的控件,如编辑控件来说,UpdateData()函数至关重要。当控件内容发生变化时,对应的控件变量的值并没有跟着变化,同样,当控件变量值变化时,控件内容也不会跟着变。
UpdateData()
函数就是解决这个问题的。
UpdateData(true);
把控件内容装入控件变量
UpdateData(false);
用控件变量的值更新控件
如:有编辑控件IDC_EDIT1,对应的变量为字符串m_Edit1
1
、修改变量值并显示在控件中:
m_Edit1 = _T("结果为50");
UpdateData(false);
2
、读取控件的值到变量中:
ClassWizardIDC_EDIT1添加EN_CHANGE消息处理函数,这个函数在编辑控件内容发生变化时执行。
void CEditView::OnChangeEdit1()
{
UpdateData(true); //
更新变量值
}



其它
如何获取程序所在的路径
也就是获取你这个程序本身所在的路径。
在应用程序类CxxApp的头文件中定义一个变量CString m_exePath;用来放置程序的路径名,在应用程序类CxxAppInitInstance()函数中加入如下语句:
TCHAR m_Path[MAX_PATH];
GetModuleFileName( NULL, m_Path, MAX_PATH ); //
获取程序路径(包括程序名)
int i = 0, j;
while( m_Path[i]!=0 )
{
if( m_Path[i]=='\\' )
j = i;
i++;
}
m_Path[j+1] = '\0';
m_exePath.Format( "%s", m_Path ); //
分离路径名(去掉程序名)
这段程序执行后,字符串变量m_exePath中放置的就是程序所在路径,其中不包括程序名。
获取程序的位置有什么用呢?
1
、打开与应用程序在一起放置的数据文件:
如果你运行程序过程中使用过打开文件对话框打开过其它路径下的文件,这时系统的默认路径就发生了改变,有可能使你原定的数据文件打不开了,如果采用以下方法就可以没问题了:
CFile file;
file.Open( m_exePath+"
数据文件名", CFile::modeRead );
2
、放置程序运行中的临时文件:
同样,当系统的默认路径发生改变后,程序中生成的临时文件就会放得到处都是,成了一个个垃圾文件,采用以下方法可使临时文件只放在程序所在路径下:
CFile file;
file.Open( m_exePath+"
临时文件名", CFile::modeCreate | CFile::modeWrite );
……
程序结束时,用下面的方法删除临时文件:
CFile::Remove( m_exePath+"
临时文件名" );

如何在你的程序中执行其它程序
在自己的程序中调用其它程序的方法有好几种,这里我介绍我用过的两种:
一、WinExec()函数:
一般用法:WinExec(m_PathName,SW_SHOWNORMAL);
m_PathName
为执行程序的路径名,必须为可执行文件。
如:WinExec("C:\\Program Files\\Internet Explorer\\iexplore.exe",SW_SHOWNORMAL);为打开IE浏览器


BOOL CEditDoc::CanCloseFrame(CFrameWnd* pFrame) 
{
    CFile file;
    
if(b_Flag) //b_Flag为文档修改标志,在修改文档时将其置为True
    {
        
int t;
        t
=::MessageBox(NULL,"文字已经改变,要存盘吗?","警告",MB_YESNOCANCEL | MB_ICONWARNING); //弹出提示对话框
        if(t==0 || t==IDCANCEL)
            
return false;
        
if(t==IDYES)
        
{
            CString sFilter
="Text File(*.txt)|*.txt||";
            CFileDialog m_Dlg(FALSE,
"txt",NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,(LPCTSTR)sFilter,NULL); //定制文件对话框
            int k=m_Dlg.DoModal(); //弹出文件对话框
            if(k==IDCANCEL || k==0)
                
return false;
            m_PathName
=m_Dlg.GetPathName(); //获取选择的文件路径名            
            file.Open(m_PathName,CFile::modeCreate | CFile::modeWrite);
            file.Write(m_Text,m_TextLen); 
//数据写入文件
            file.Close();
        }

    }

return CDocument::CanCloseFrame(pFrame);
}

 

 





二、ShellExecute()函数:
一般用法:ShellExecute(NULL,NULL,m_PathName,NULL,_T("c:\\temp"),SW_SHOWNORMAL);
m_PathName
为打开的程序路径名;
_T("c:\\temp")
为工作目录;
WinExec()不同的是ShellExecute()函数也可以打开非可执行文件,比如你指定的文件为.txt,结果会打开记事本装入该文件。我用这种方法调用自己制作的帮助文件(.chm)效果很好。

如果不使用串行化,如何在程序结束时保存文件?
在文档-视图结构中,用串行化自动保存文件在各种VC书上都有介绍。现在的问题是我不使用串行化,而是自己动手保存,当点击窗口的关闭按钮时,如何提示并保存文档。
ClassWizard在文档类(CxxDoc)中添加函数CanCloseFrame(),再在其中加入保存文件的语句就可以了。
例:
BOOL CEditDoc::CanCloseFrame(CFrameWnd* pFrame) 
{
    CFile file;
    
if(b_Flag) //b_Flag为文档修改标志,在修改文档时将其置为True
    {
        
int t;
        t
=::MessageBox(NULL,"文字已经改变,要存盘吗?","警告",MB_YESNOCANCEL | MB_ICONWARNING); //弹出提示对话框
        if(t==0 || t==IDCANCEL)
            
return false;
        
if(t==IDYES)
        
{
            CString sFilter
="Text File(*.txt)|*.txt||";
            CFileDialog m_Dlg(FALSE,
"txt",NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,(LPCTSTR)sFilter,NULL); //定制文件对话框
            int k=m_Dlg.DoModal(); //弹出文件对话框
            if(k==IDCANCEL || k==0)
                
return false;
            m_PathName
=m_Dlg.GetPathName(); //获取选择的文件路径名            
            file.Open(m_PathName,CFile::modeCreate | CFile::modeWrite);
            file.Write(m_Text,m_TextLen); 
//数据写入文件
            file.Close();
        }

    }

return CDocument::CanCloseFrame(pFrame);
}


//


退出程序
这样当你单击窗口上的关闭按钮时,如果数据已修改了,就会弹出一个提示保存数据的对话框,提示你保存数据。
程序中的b_Flag是数据修改标志,应该在修改数据时进行设置,m_Text是准备保存的数据,放在文档内。

POSITION
怎么用?
POSITION
类型数据用于表征各种列表中元素的位置,它类似于数组的下标,但又有所不同。主要区别是:
我们不能访问POSITION型数据的值,也不能对POSITION数据型数据进行加减、比较等运算。
POSITION型数据访问列表时,都是采用迭代法,一般格式为:
POSITION pos; //
定义pos型变量
pos = GetHeadPosition(); //
获取列表起始元素位置
while( pos )
{
x = GetNext(pos); //
获取pos处的列表值,同时修改pos为下一个元素位置
}
GetNext()
就是一种迭代,其格式为:
TYPE GetNext(POSITION& rPosition);
首先,它返回当前pos位置处的元素;再就是把pos值修改为下一个元素位置。这样循环时,可依次取得列表中各元素的值;当到达列表尾时,posNULL,循环结束。
所以使用POSITION型数据时,你不要试图用加减等操作去修改它,只能用GetNext()(向后迭代)或GetPrev()(向前迭代)反复迭代来修改它的值。
如果你想直接到达指定值,还可以用Find()函数或FindIndex()函数获得指定值的POSITION值。
POSITION Find(TYPE Value);
用于在列表中查找值为Value的元素的POSITION值;
POSITION FindIndex(int nIndex);
用于获取列表中第nIndex个元素的POSITION值,nIndex0开始。
如:
pos = FindIndex(5); //
求列表中第5个元素的位置
x = GetNext(pos); //
读取元素的值
总之,POSITION类型在多种涉及列表的类中提供,不同的类提供的函数有所不同,但用法都是类似的。

如何从完整的文件路径中分离文件名和路径名?
从路径中分离文件名:
CString GetFileName(CString pathname)
{
    
forint i=pathname.GetLength()-1; i>=0; i-- )
    
{
    
if( pathname[i]=='\' )
        
break;
    }

    
return pathname.Mid( i+1 );
}

从路径中分离路径名(去除文件名):

CString GetPath(CString pathname)
{
    
int i = 0, j;
    
while( i<pathname.GetLength() )
    
{
        
if( pathname[i]=='\' )
        j 
= i;
        i
++;
    }

    
return pathname.Left( j+1 );
}


- 作者: 小浪 2007年12月20日, 星期四 10:04  回复(0) |  引用(0) 加入博采

Dialog & Windows 使用技巧
Dialog & Windows 使用技巧
摘自:http://tb.blog.csdn.net/TrackBack.aspx?PostId=658248
作者:lixiaosan
日期:04/11/2006

文章不断更新中,请访问这里

注:以下代码以一个名为CTest6Dlg的对话框类为例

1. 在任务栏隐藏对话框

      ModifyStyleEx(WS_EX_APPWINDOW, WS_EX_TOOLWINDOW);


2. 使对话框为顶层窗口

        SetWindowPos(&this->wndTopMost, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);


3. 在运行时添加最大化,最小化按钮

    SetWindowLong(this->m_hWnd, GWL_STYLE,
                  GetWindowLong(this->m_hWnd, GWL_STYLE) |
                  WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
     UpdateWindow();



4. 使能对话框右上角关闭按钮


    在OnInitDialog中

    方法一:
       CMenu* menu = GetSystemMenu(FALSE);
       menu->ModifyMenu(SC_CLOSE, MF_BYCOMMAND | MF_GRAYED );

    方法二:
       CMenu* menu = GetSystemMenu(FALSE);
       menu->EnableMenuItem(SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);


5. 当对话框一部分在屏幕外时,显示全部对话框

    SendMessage(DM_REPOSITION);


6. 改变鼠标外形


    添加 WM_SETCURSOR 消息映射函数

    BOOL CTest6Dlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
    {
         SetCursor(AfxGetApp()->LoadStandardCursor(IDC_HELP));

         return 0; 
    }


7. 改变对话框背景色和文本颜色


    在CTest6App的InitInstance中添加

    SetDialogBkColor(RGB(255,0,0), RGB(0,255,0));


8. 改变对话框caption上的图标


    导入自己的图标资源到工程中,把原来ID为 IDR_MAINFRAME 的资源删除,把新的图标的ID命名为IDR_MAINFRAME


9. 在主对话框显示前,显示一个login对话框


     BOOL CTest6App::InitInstance()
     {
          //...
          int nResponse;
          CLoginDlg loginDlg;

          nResponse = loginDlg.DoModal();
          if (nResponse == IDOK)
          {
          }
          if (nResponse == IDCANCEL)
          {
               return FALSE;
          }
 
          CTest6Dlg dlg;
          m_pMainWnd = &dlg;
          int nResponse = dlg.DoModal();
          if (nResponse == IDOK )
          {
          }
          else if (nResponse == IDCANCEL)
          {
          }
          return FALSE;
     }

然后重载CLoginDlg对话框的哦OnOK(),在其中判断条件
void CLoginDlg::OnOK()
{
     if (条件满足)
        CDialog::OnOK();
     else
        AfxMessageBox(_T("invalid password!"));
}



10. 在对话框中添加工具栏


    方法一:添加以下代码到 OnInitDialog 中
 
     if ( !m_wndToolBar.Create(this) || !m_wndToolBar.LoadToolBar(IDR_TOOLBAR1) )
     {
          TRACE0("Failed to Create Dialog Toolbar\n");
          EndDialog(IDCANCEL);
     }

     CRect rcClientOld; // 久客户区RECT
     CRect rcClientNew; // 加入TOOLBAR后的CLIENT RECT
     GetClientRect(rcClientOld); //
     // Called to reposition and resize control bars in the client area of a window
     // The reposQuery FLAG does not really traw the Toolbar.  It only does the calculations.
     // And puts the new ClientRect values in rcClientNew so we can do the rest of the Math.
     //重新计算RECT大小
     RepositionBars(AFX_IDW_CONTROLBAR_FIRST,
                       AFX_IDW_CONTROLBAR_LAST,
                       0,
                       reposQuery,
                       rcClientNew);

    // All of the Child Windows (Controls) now need to be moved so the Tollbar does not cover them up.
     //所有的子窗口将被移动,以免被TOOLBAR覆盖
     // Offest to move all child controls after adding Tollbar
     //计算移动的距离
     CPoint ptOffset(rcClientNew.left-rcClientOld.left,
       rcClientNew.top-rcClientOld.top);

     CRect rcChild;
     CWnd* pwndChild = GetWindow(GW_CHILD);  //得到子窗口
     while(pwndChild) // 处理所有子窗口
     {
          //移动所有子窗口
         pwndChild->GetWindowRect(rcChild);
          ScreenToClient(rcChild);
          rcChild.OffsetRect(ptOffset);
          pwndChild->MoveWindow(rcChild,FALSE);
          pwndChild = pwndChild->GetNextWindow();
     }

     CRect rcWindow;
     GetWindowRect(rcWindow); // 得到对话框RECT
     rcWindow.right += rcClientOld.Width() - rcClientNew.Width(); // 修改对话框尺寸
     rcWindow.bottom += rcClientOld.Height() - rcClientNew.Height();
     MoveWindow(rcWindow,FALSE); // Redraw Window

     RepositionBars(AFX_IDW_CONTROLBAR_FIRST,AFX_IDW_CONTROLBAR_LAST,0);

    方法二:

         http://www.codeproject.com/dialog/dlgtoolstatusbar.asp


11.响应对话框的最大化、最小化、关闭、恢复事件


     方法一:添加 WM_SYSCOMMAND 消息映射函数

     void CTest6Dlg::OnSysCommand(UINT nID, LPARAM lParam)
     {
          if ( (nID & 0xFFF0) == IDM_ABOUTBOX )
          {
               CAboutDlg dlgAbout;
               dlgAbout.DoModal();
      }
     else
     {
          if ( nID == SC_MAXIMIZE )
          {
               AfxMessageBox(_T("最大化"));
          }
          else if ( nID == SC_MINIMIZE ) 
          {
               AfxMessageBox(_T("最小化"));
          }
          else if ( nID == SC_CLOSE )
          {
               AfxMessageBox(_T("关闭"));
          }

          CDialog::OnSysCommand(nID, lParam);
    }

     方法二:添加 WM_SIZE 消息映射函数

     void CTest6Dlg::OnSize(UINT nType, int cx, int cy)
     {
          CDialog::OnSize(nType, cx, cy);

          if ( nType == SIZE_MAXIMIZED )
          {
               AfxMessageBox(_T("最大化"));
          }
          else if ( nType == SIZE_MINIMIZED )
          {
               AfxMessageBox(_T("最小化"));
          } 
          else if ( nType == SIZE_RESTORED )
          {
               AfxMessageBox(_T("恢复"));
          }
     }


12.代码实现窗口最小化,最大化,关闭


PostMessage(WM_SYSCOMMAND,  SC_MINIMIZE);
PostMessage(WM_SYSCOMMAND,  SC_MAXIMIZE);
PostMessage(WM_SYSCOMMAND,  SC_CLOSE);


13.按下ESC和ENTER键时禁止关闭对话框

  
    方法一:

     (1) 重载OnCancel和OnOk,屏蔽其中的CDialog::OnCancel()和CDialog::OnOk();
     (2) 添加以下代码
     void CTest6Dlg::OnSysCommand(UINT nID, LPARAM lParam)
     {
         if ((nID & 0xFFF0) == IDM_ABOUTBOX)
          {
             CAboutDlg dlgAbout;   //if you have an about dialog
              dlgAbout.DoModal();
          }
          else if ((nID & 0xFFF0) == SC_CLOSE)
          {
              //用户点击右上角"X"
              EndDialog(IDOK); 
      
          }
          else
          {
              CDialog::OnSysCommand(nID, lParam);
          }
     }

    方法二:

     BOOL CTest6Dlg::PreTranslateMessage(MSG* pMsg)
     {
          if ( pMsg->message == WM_KEYDOWN )
          {
               switch(pMsg->wParam)
               {
               case VK_ESCAPE:
                return TRUE; //直接返回TRUE
                break;
               case VK_RETURN:
                return TRUE;
                break;
               }
          }
          return CDialog::PreTranslateMessage(pMsg);
     }

     方法三:
         Q122489:
         How to Disable Default Pushbutton Handling for MFC Dialog
         http://support.microsoft.com/kb/122489/en-us


14.在对话框中处理键盘鼠标消息


处理PreTranslateMessage消息

以下代码示例只演示了键盘WM_KEYDOWN消息,你也可以处理鼠标消息,比如WM_LBUTTONDOWN,WM_LBUTTONUP,WM_RBUTTONDOWN等。

BOOL CTest6Dlg::PreTranslateMessage(MSG* pMsg) 
{
    /**********************************************************/
    /*    当焦点在combobox(drop down风格)的edit上,响应回车            */
    /***********************************************************/
    if ( pMsg->message == WM_KEYDOWN )
    {        
        switch( pMsg->wParam )
        {
        case VK_RETURN:
            CEdit *pEdit = (CEdit*)m_combo1.GetWindow(GW_CHILD);
            if(pMsg->hwnd == pEdit->m_hWnd )
            { 
                
AfxMessageBox("在combobox的edit中按下了Enter!");
            }             
            return TRUE;
        }
    }
 
    /****************************************/
    /*  
ALT为WM_SYSKEYDOWN                  */
    /****************************************/

    if( pMsg->message == WM_SYSKEYDOWN )
    {   
        switch( pMsg->wParam )
        {
        case VK_F1:     
            if(::GetKeyState(VK_MENU) < 0)//ALT+F1
            {
                AfxMessageBox("按下了ALT+F1");
                return TRUE;
            }             
        }         
    }
    
    /****************************************/
    /*     在clistctrl中按ctrl+A选中所有项  */
    /****************************************/
    if( pMsg->message == WM_KEYDOWN )
    {   
        if(pMsg->hwnd == GetDlgItem(IDC_LIST1)->m_hWnd)
        {
            switch( pMsg->wParam )
            {
            case 65://A     
              if(::GetKeyState(VK_CONTROL) < 0)//Shift+enter
              {
                    for(int i=0; i
                    {
                        m_list.SetItemState(i, LVIS_SELECTED|LVIS_FOCUSED,
                                            LVIS_SELECTED|LVIS_FOCUSED);
                    }
              }
              return TRUE;
            }
        }
    }  
 
    /****************************************/
    /*    当焦点在combobox,弹出自定义菜单   */
    /****************************************/      
    if(pMsg->message == WM_RBUTTONDOWN)
    {
        CEdit *pEdit = (CEdit*)m_combo1.GetWindow(GW_CHILD);
        if(pMsg->hwnd == pEdit->m_hWnd)
        {
            DWORD dwPos = GetMessagePos();
            CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
            ScreenToClient(&point);
            ClientToScreen(&point);
             
            CMenu menu;
            VERIFY( menu.LoadMenu( IDR_MENU1 ) );
            CMenu* popup = menu.GetSubMenu(0);
            ASSERT( popup != NULL );
            popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this );
        }         
    }
 
    return CDialog::PreTranslateMessage(pMsg);
}


15.对话框启动即隐藏

 
    添加 WM_SHOWWINDOW 的消息映射

     void CTest6Dlg::OnShowWindow(BOOL bShow, UINT nStatus)
     {
          if ( GetStyle() & WS_VISIBLE )
          {
               CDialog::OnShowWindow(bShow, nStatus);
          }
          else
          {
               long Style = ::GetWindowLong(*this, GWL_STYLE);
               ::SetWindowLong(*this, GWL_STYLE, Style | WS_VISIBLE);
               CDialog::OnShowWindow(SW_HIDE, nStatus);
          }
     }


16.对话框自动停靠在屏幕边


    const int DETASTEP = 50;
     BOOL AdjustPos(CWnd *pWnd, CRect* lpRect)
     {
        //自动靠边
        int iSX = GetSystemMetrics(SM_CXFULLSCREEN);
        int iSY = GetSystemMetrics(SM_CYFULLSCREEN);
        RECT rWorkArea;
        BOOL bResult = SystemParametersInfo(SPI_GETWORKAREA, sizeof(RECT), &rWorkArea, 0);

        CRect rcWA;
        if ( !bResult )
        {
            //如果调用不成功就利用GetSystemMetrics获取屏幕面积
            rcWA = CRect(0,0,iSX,iSY);
        }
        else
            rcWA = rWorkArea;

        int iX = lpRect->left;
        int iY = lpRect->top;
        if ( iX < rcWA.left + DETASTEP && iX!=rcWA.left )
        {
            //调整左
            pWnd->SetWindowPos(NULL,rcWA.left,iY,0,0,SWP_NOSIZE);
            lpRect->OffsetRect(rcWA.left-iX,0);
            AdjustPos(lpRect);
            return TRUE;
        }
        if ( iY < rcWA.top + DETASTEP && iY!=rcWA.top )
        {
            //调整上
            pWnd->SetWindowPos(NULL ,iX,rcWA.top,0,0,SWP_NOSIZE);
            lpRect->OffsetRect(0,rcWA.top-iY);
            AdjustPos(lpRect);
            return TRUE;
        }
        if ( iX + lpRect->Width() > rcWA.right - DETASTEP && iX !=rcWA.right-lpRect->Width() )
        {
            //调整右
            pWnd->SetWindowPos(NULL ,rcWA.right-rcW.Width(),iY,0,0,SWP_NOSIZE);
            lpRect->OffsetRect(rcWA.right-lpRect->right,0);
            AdjustPos(lpRect);
            return TRUE;
        }
        if ( iY + lpRect->Height() > rcWA.bottom - DETASTEP && iY !=rcWA.bottom-lpRect->Height() )
        {
            //调整下
            pWnd->SetWindowPos(NULL ,iX,rcWA.bottom-rcW.Height(),0,0,SWP_NOSIZE);
            lpRect->OffsetRect(0,rcWA.bottom-lpRect->bottom);
            return TRUE;
        }
        return FALSE;
    }

    //然后在ONMOVEING事件中使用如下过程调用
    CRect r=*pRect;
    AdjustPos(this, &r);
    *pRect=(RECT)r;


17.单击窗口任意位置都可拖动窗口

    方法一:
     添加 WM_LBUTTONDOWN 的消息映射
     void CTest6Dlg::OnLButtonDown(UINT nFlags, CPoint point)
     {
          PostMessage(WM_NCLBUTTONDOWN, HTCAPTION, 0);

          CDialog::OnLButtonDown(nFlags, point);
     }

    方法二:
   
添加 WM_NCHITTEST 的消息映射
    注意:在classwizard->message中找不到
WM_NCHITTEST的,需要在选项卡class info->message filter中选择window后该消息才会出现在message中。
 
     void CTest6Dlg::OnNCHitTest(CPoint point)
     {
            return HTCAPTION;
      //    return CDialog::
OnNCHitTest(point);
     }

     或者参考
        http://msdn.microsoft.com/msdnmag/issues/02/12/CQA/default.aspx


18.用Enter键替换Tab键实现焦点切换


     BOOL CTest6Dlg::PreTranslateMessage(MSG* pMsg)
     {
        if ( pMsg->message == WM_KEYDOWN )
          {
              if ( pMsg->wParam == VK_RETURN )
                   pMsg->wParam = VK_TAB;
          }
          return CDialog::PreTranslateMessage(pMsg);
     }


19.在对话框添加快捷键


     (1) 在CXXXApp中类中添加声明
        HACCEL m_haccel;
     (2) 在resource view中右键点击树的根目录,选择insert,添加一个新的Accelerator,默认ID为IDR_ACCELERATOR1。
         在其中添加相应菜单的快捷键。
     (3) 在BOOL CXXXApp::InitInstance()中添加代码
        m_haccel = LoadAccelerators(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_ACCELERATOR1));
     (4) 添加CXXXApp类的 ProcessMessageFilter 消息映射函数
         BOOL CTest6App::ProcessMessageFilter(int code, LPMSG lpMsg)
         {
              if ( m_haccel )
              {
                  if ( ::TranslateAccelerator(m_pMainWnd->m_hWnd, m_haccel, lpMsg) )
                       return TRUE;
              }
              return CWinApp::ProcessMessageFilter(code, lpMsg);
         }

或者参考
Q100770:
How to use accelerator keys and a main menu on the dialog box in Visual C++
http://support.microsoft.com/kb/100770/en-us

Adding Hot Keys to your Application
http://msdn.microsoft.com/msdnmag/issues/1200/c/default.aspx


20.对话框全屏


    int cx, cy;
    HDC dc = ::GetDC(NULL);
    cx = GetDeviceCaps(dc,HORZRES) + GetSystemMetrics(SM_CXBORDER);
    cy = GetDeviceCaps(dc,VERTRES) + GetSystemMetrics(SM_CYBORDER);
    ::ReleaseDC(0,dc);

    // Remove caption and border
    SetWindowLong(m_hWnd, GWL_STYLE,
                    GetWindowLong(m_hWnd, GWL_STYLE) & (~(WS_CAPTION | WS_BORDER)));

    // Put window on top and expand it to fill screen
    ::SetWindowPos(m_hWnd, HWND_TOPMOST,
          -(GetSystemMetrics(SM_CXBORDER)+1),
          -(GetSystemMetrics(SM_CYBORDER)+1),
          cx+1,cy+1, SWP_NOZORDER);
    或参考
        http://www.codeguru.com/cpp/w-d/dislog/dialog-basedapplications/article.php/c1837/


21.控制对话框最大最小尺寸


    (1) 对话框的属性的必须是resizing的
    (2) 打开classwizard->class info标签页->message filter中选择window
    (3) 添加 WM_GETMINMAXINFO 消息映射
        void CTest6Dlg::OnGetMinMaxInfo(MINMAXINFO *lpMMI)
        {
             lpMMI->ptMinTrackSize = CPoint(200, 200);
        }


22. 创建无模式对话框


Q103788:
Creating a Modeless Dialog Box with MFC Libraries
http://support.microsoft.com/kb/103788/EN-US/

Visual C++ MFC Samples      
MODELESS Sample: Uses a CDialog Object as a Modeless Dialog Box
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcsample/html/_sample_mfc_MODELESS.asp


23.在对话框中改变菜单项状态(enable/disable, check/uncheck, change text)


Q242577:
You cannot change the state of a menu item from its command user-interface handler if the menu is attached to a dialog box in Visual C++
http://support.microsoft.com/kb/242577/en-us


24. 按下F1出现帮助


Q141724:
Context-Sensitive Help in a CDialog Object
http://support.microsoft.com/kb/141724/en-us


msdn中的介绍
http://msdn2.microsoft.com/en-us/library/dyd1yfww.aspx

或者如果你要屏蔽按下F1后出现的“
找不到*.hlp文件”的提示对话框
添加 WM_HELPINFO 消息映射
BOOL CTest6Dlg::OnHelpInfo(HELPINFO* pHelpInfo)
{
     return TRUE;
    //return CDialog::OnHelpInfo(pHelpInfo);//屏蔽该句
}



25. 对话框初始化设置输入焦点的问题

默认情况下,对话框初始化显示的焦点按照在对话框编辑期间设置的tab order的第一个控件来设置的。(设置tab order可在对话框的resource view中用Ctrl+D显示出来,点鼠标进行顺序设置)。如果想人为的改变初始化时的输入焦点,可在对话框的OnInitDialog中把return  TRUE; 改为 return  FALSE;

MSDN上的解释如下:

Return Value

Specifies whether the application has set the input focus to one of the controls in the dialog box. If OnInitDialog returns nonzero, Windows sets the input focus to the first control in the dialog box. The application can return 0 only if it has explicitly set the input focus to one of the controls in the dialog box.



26. 在对话框间传递数据

CDlg1::OnButton1()
{
      CDlg2 dlg2;
      dlg2.m_str = _T("你好"; )
      dlg2.m_bJudge = TRUE;
      dlg2.DoModal();
}
 
//Dlg2.h
public:
     CString m_str;
     BOOL m_bJudge;
 
 
//Dlg2.cpp
CDlg2::OnInitDialog()
{
    if (m_bJudge)
        GetDlgItem(IDC_EDIT1)->SetWindowText(m_str);
}




27. 在 dlg1 中打开 dlg2 时,dlg2 能修改 dlg1 中的成员变量


//dlg1.cpp

    #include "dlg2.h"
    CDlg1::OnButton1()
    {
          CDlg2 dlg2;
          dlg2.m_pDlg1 = this;
          dlg2.DoModal();
    }

//dlg2.h
class CDlg1;//添加dlg1类的声明
class CDlg2 : public CDialog
{
...
public:
    CDlg1 *m_pDlg1;
}

//dlg2.cpp
#include "dlg1.h"

至此,你可以在dlg2.cpp中通过m_pDlg1操作CDlg1类中的成员变量了。




28. 改变对话框字体,对话框大小改变的问题


Q145994:
How to calculate dialog box units based on the current font in Visual C++
http://support.microsoft.com/kb/q145994/

Q125681:
How To Calculate Dialog Base Units with Non-System-Based Font
http://support.microsoft.com/kb/125681/en-us




29. 进行大数据量计算的时候,导致界面挂起无响应的问题


    当在程序中需要进行大数据量计算的时候(比如搜索磁盘,大数据量传输等),由于这些计算过程是在界面线程(UI Process)中,由此引发了界面线程的消息阻塞。我们创建一个工作线程(worker thread)来处理计算过程,以解决该问题。
下面是一个简单的创建一个工作线程的实现:
//xxxdlg.h
static UINT MyThread(LPVOID pParam);
CWinThread* pMyThread;

//xxxdlg.cpp
CXXXDlg::OnButton1()
{
     pMyThread = AfxBeginThread(MyThread, this);
     pMyThread = NULL;
}

UINT CXXXDlg::MyThread(LPVOID pParam)
{
     CXXXDlg *pDlg = (CXXXDlg *)pParam;

     //这里添加计算过程

     return 0;
}


30. 工程资源的合并


以把B对话框的资源插入到A对话框为例:

(1) 生成一个*.ogx文件
    打开B工程,在ClassView中鼠标右键点击所需的对话框类,单击"Add to Gallery"。
    这时,会在 " C:\Program Files\Microsoft Visual Studio\Common\MSDev98\Gallery\工程B " 的目录下产生一个ogx文件。

(2) 插入该*.ogx文件
    打开A工程,选择菜单Project->Add To Project->components and controls... ,选择刚生成的ogx文件,然后Insert。
这时B对话框资源和对话框类就插入到A中了。。


31. 在网上可以找到很多有用的代码,我只是把一些常用的功能列出链接,方便查看


http://support.microsoft.com              
http://www.codeproject.com/dialog/
http://www.codeguru.com/Cpp/W-D/dislog/


改变对话框大小时同时改变控件大小

http://www.codeproject.com/dialog/easysize.asp
http://www.codeproject.com/dialog/resizabledialog.asp
http://www.vchelp.net/vchelp/archive.asp?type_id=5&class_id=1&cata_id=1&article_id=548&search_term=
http://www.vchelp.net/vchelp/archive.asp?type_id=5&class_id=1&cata_id=1&article_id=538&search_term=


如何在可变大小(resizing)的对话框中实现滚动窗口

Q262954:
How to create a resizeable dialog box with scroll bars in Visual C++
http://support.microsoft.com/default.aspx?scid=kb;en-us;262954
http://www.codeproject.com/dialog/scrollablechilddialog.asp


从某一点或某一边逐渐变大显示对话框

http://www.codeproject.com/dialog/canidialog.asp


一个重载的MessageBox类

http://www.codeproject.com/dialog/xmessagebox.asp


option设置对话框(左边是树,右边是子对话框)

实现原理:create多个child类型的对话框,然后全部hide,点击左边树的item时,显示相应子对话框。

Q103375:
MultiDlg.exe Demonstrates Dynamic Child Dialog Boxes
http://support.microsoft.com/kb/103375/en-us

http://www.codeproject.com/dialog/ezoptionsdlg.asp
http://www.codeproject.com/dialog/csettingsdlg.asp
http://www.codeguru.com/cpp/w-d/dislog/optionsdialogs/article.php/c1953/
http://www.codeguru.com/cpp/w-d/dislog/optionsdialogs/article.php/c2015/


实现MSN的右下角的消息弹出提示窗口

http://www.codeproject.com/dialog/statusbarmsgwnd.asp


Tip of the day(每日一贴)功能的实现
    
http://www.codeproject.com/dialog/XHTMLTipOfTheDay.asp
http://www.codeguru.com/cpp/w-d/dislog/tipoftheday/article.php/c4993/


不规则对话框
    
http://www.codeproject.com/dialog/SimpleIrregular.asp


扩展和收缩对话框
    
http://www.codeproject.com/dialog/dlgexpand.asp


对话框渐变色
    
http://www.codeproject.com/dialog/WinMakeInactive.asp


屏幕捕捉
    
http://www.codeproject.com/dialog/screencap.asp


对话框菜单添加“最近使用文件列表”功能
    
http://www.codeproject.com/dialog/rfldlg.asp


关闭对话框时,逐渐消失
   
http://www.codeguru.com/cpp/w-d/dislog/animation/article.php/c5063/


对话框背景bitmap
   
http://www.codeguru.com/cpp/w-d/dislog/bitmapsimages/article.php/c1877/


透明对话框
   
http://www.codeguru.com/cpp/w-d/dislog/miscellaneous/article.php/c5065/
http://www.codeguru.com/cpp/w-d/dislog/miscellaneous/article.php/c5019/


在对话框中创建view
   
http://www.codeguru.com/cpp/w-d/dislog/article.php/c5009/


Splash Screen

Q817372:
How to insert a splash screen in a dialog-based application by using Visual C++ .NET or Visual C++ 2005
http://support.microsoft.com/kb/817372/en-us 

Q815376:
How to create and insert a splash screen in an SDI application or in an MDI application by using Visual C++ .NET or Visual C++ 2005
http://support.microsoft.com/kb/815376/en-us

http://www.codeguru.com/cpp/w-d/dislog/splashscreens/article.php/c2011/
http://www.codeguru.com/cpp/w-d/dislog/miscellaneous/article.php/c5019/
http://www.codeguru.com/cpp/w-d/dislog/splashscreens/article.php/c5029/


分割对话框

http://www.codeguru.com/cpp/w-d/dislog/splitterwindowswithingdialogs/article.php/c4973/
http://www.codeguru.com/cpp/w-d/dislog/splitterwindowswithingdialogs/article.php/c2031/
http://www.codeguru.com/cpp/w-d/dislog/splitterwindowswithingdialogs/article.php/c1979/


标题栏Title Bar
   
http://www.codeguru.com/cpp/w-d/dislog/titlebar/article.php/c1897/


添加状态栏statusbar和工具栏toolbar

Q123158:
Adding Control Bars to Foundation Classes Dialogs
http://support.microsoft.com/kb/123158/en-us

Visual C++ MFC Samples      
DLGCBR32 Sample: Demonstrates Adding a Status Bar and Toolbar to Dialog Boxes
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcsample/html/_sample_mfc_DLGCBR32.asp

http://www.codeguru.com/cpp/w-d/dislog/toolbarsandstatusbars/article.php/c1955/
http://www.codeguru.com/cpp/w-d/dislog/toolbarsandstatusbars/article.php/c1939/
http://www.codeguru.com/cpp/w-d/dislog/toolbarsandstatusbars/article.php/c1949/


Tooltip

Q141758:
How to add tooltips for controls to an MFC modal dialog box
http://support.microsoft.com/kb/141758/en-us

http://www.codeguru.com/cpp/w-d/dislog/tooltipsfordialogcontrols/article.php/c2017/
http://www.codeguru.com/cpp/w-d/dislog/tooltipsfordialogcontrols/article.php/c1843/   
http://www.codeguru.com/cpp/w-d/dislog/tooltipsfordialogcontrols/article.php/c1839/
http://www.codeproject.com/miscctrl/pptooltip.asp



从对话框边缘平滑弹出对话框

http://www.codeguru.com/cpp/w-d/dislog/miscellaneous/article.php/c5061/

- 作者: 小浪 2007年12月20日, 星期四 10:02  回复(0) |  引用(1) 加入博采

用二级指针有什么好处

用二级指针有什么好处

如果函数的参数是一个指针,不要指望用该指针去申请动态内存。示例7-4-1 中,
Test 函数的语句GetMemory(str, 200)并没有使str 获得期望的内存,str 依旧是NULL,
为什么?
void GetMemory(char *p, int num)
{
p = (char *)malloc(sizeof(char) * num);
}
void Test(void)
{
char *str = NULL;
GetMemory(str, 100); // str 仍然为 NULL
strcpy(str, "hello"); // 运行错误
}

毛病出在函数GetMemory 中。编译器总是要为函数的每个参数制作临时副本,指针
参数p 的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p 的内容,就导致
参数p 的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p 申请
了新的内存,只是把_p 所指的内存地址改变了,但是p 丝毫未变。所以函数GetMemory
并不能输出任何东西。事实上,每执行一次GetMemory 就会泄露一块内存,因为没有用
free 释放内存。

如果非要用指针作为返回值,那就用二级指针
bool   f(void **p)
{
 *p   =   new  CBase;
 return *p != NULL;
}  
   
这种情况下传入的二级指针p没有改变,改变的仅仅是*p

#include "stdafx.h"
class CBase
{
 const static int i = 10;
};
bool   f(void **p)
{
 *p   =   new  CBase;
 return *p != NULL;
}
int _tmain(int argc, _TCHAR* argv[])
{
 bool t = false;
 CBase *p;
 t= f((void **)&p);
 return 0;
}

- 作者: 小浪 2007年12月2日, 星期日 23:21  回复(0) |  引用(0) 加入博采

用VC++建立Service服务应用程序

摘自:http://www.vckbase.com/document/viewdoc/?id=1677

用VC++建立Service服务应用程序

作者:李佳颖(niying)

下载源代码

  本文主要介绍了 OpenSCManager、CreateService、OpenService、ControlService、DeleteService、RegisterServiceCtrlHandler、SetServiceStatus、StartServiceCtrlDispatcher等操作服务程序的主要几个API的用法,具体的函数参数大家可以查阅MSDN。
  为什么要使用服务应该程序呢?服务程序就像系统的一些服务一样,能够自动地启动,并执行相应的操作;而且因为服务程序的在层次上和一般的应用程序不同,其能够在系统启动时就自动地运行,而不像一般的应用程序那样一定要在登陆后才能运行,这些就是服务的一些好处了,如果你也想你的程序具有这样的功能,那么你就可以建立一个服务应用程序了。下面就跟着我一步一步地教你怎么去创建一个服务应用程序吧。

一、建立 Win32 Application 应用程序(当然你也可以建立其它的应用程序,但服务一般是没有用户界面的),并命名为 ServiceTest。

二、定义全局函数变量。这里主要是设置服务句柄和状态。

BOOL IsInstalled();
BOOL Install();
BOOL Uninstall();
void LogEvent(LPCTSTR pszFormat, ...);
void WINAPI ServiceMain();
void WINAPI ServiceStrl(DWORD dwOpcode);

TCHAR szServiceName[] = _T("ServiceTest");
BOOL bInstall;
SERVICE_STATUS_HANDLE hServiceStatus;
SERVICE_STATUS status;
DWORD dwThreadID;

三、添加Init初始化函数。

void Init()
{
	hServiceStatus = NULL;
	status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	status.dwCurrentState = SERVICE_STOPPED;
	tatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
	status.dwWin32ExitCode = 0;
	status.dwServiceSpecificExitCode = 0;
	status.dwCheckPoint = 0;
	status.dwWaitHint = 0;
}

四、添加安装和删除服务函数。这里主要是用到了四个函数 OpenSCManager 和 CreateService。OpenSCManager 用于打开服务控制管理器;CreateService 用于创建服务;OpenService用于打开已有的服务,返回该服务的句柄;ControlService则用于控制已打开的服务状态,这里是让服务停止后才删除;DeleteService 用于删除指定服务。

BOOL Install();
{
//这里列出主要的两个函数,其它的可以在代码里找。

//打开服务控制管理器
OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    //创建服务
  SC_HANDLE hService = ::CreateService(
        hSCM, szServiceName, szServiceName,
        SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
        SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
        szFilePath, NULL, NULL, _T(""), NULL, NULL);

    ::CloseServiceHandle(hService);
    ::CloseServiceHandle(hSCM);
}

BOOL Uninstall(); { //这里列出主要的两个函数,其它的可以在代码里找。 //打开服务控制管理器 OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); //打开服务 OpenService(hSCM, szServiceName, SERVICE_STOP | DELETE); //停止服务 ControlService(hService, SERVICE_CONTROL_STOP, &status); //删除服务 DeleteService(hService); … }

五、添加服务主线程函数和控制函数。这里调用 RegisterServiceCtrlHandler 来注册服务的控制函数,这里要设置status.dwControlsAccepted 为 SERVICE_ACCEPT_STOP,否则你不能控制这个服务的状态。

void WINAPI ServiceMain()
{
    // Register the control request handler
    status.dwCurrentState = SERVICE_START_PENDING;
    status.dwControlsAccepted = SERVICE_ACCEPT_STOP;//这个要使用,否则你不能控制

    //注册服务控制
    hServiceStatus = RegisterServiceCtrlHandler(szServiceName, ServiceStrl);
    if (hServiceStatus == NULL)
    {
        LogEvent(_T("Handler not installed"));
        return;
    }
    SetServiceStatus(hServiceStatus, &status);

    status.dwWin32ExitCode = S_OK;
    status.dwCheckPoint = 0;
    status.dwWaitHint = 0;
    status.dwCurrentState = SERVICE_RUNNING;
    SetServiceStatus(hServiceStatus, &status); 

    //模拟服务的运行,10后自动退出。应用时将主要任务放于此即可
    int i = 0;
    while (i < 10)
    {
           Sleep(1000);
           i++;
    }
    //

    status.dwCurrentState = SERVICE_STOPPED;
    SetServiceStatus(hServiceStatus, &status);
    LogEvent(_T("Service stopped"));
}

六、在主线程函数里注册控制函数和程序执行主体。这里主要是说明这就是程序的执行体。

void WINAPI ServiceMain()
{
    …

    //模拟服务的运行,10后自动退出。应用时将主要任务放于此即可
    int i = 0;
   while (i < 10)
    {
           Sleep(1000);

           i++;
    }

    …
}

七、最后,要在main函数里注册添加安装、删除、注册主函数。

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
	Init();
	dwThreadID = ::GetCurrentThreadId();
    SERVICE_TABLE_ENTRY st[] =
    {
        { szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain },
        { NULL, NULL }
    };

	if (stricmp(lpCmdLine, "/install") == 0)
	{
		Install();
	}
	else if (stricmp(lpCmdLine, "/uninstall") == 0)
	{
		Uninstall();
	}
	else
	{
		if (!::StartServiceCtrlDispatcher(st))
		{
			LogEvent(_T("Register Service Main Function Error!"));
		}
	}
	return 0;
}

八、总结。其实做一个服务程序并不难,主要是懂得程序的执行体放于哪里?和注册程序的主函数和注册控制函数,如果这两个没有注册的话,你就程序就不知道如何去控制了。status.dwControlsAccepted = SERVICE_ACCEPT_STOP;这个也重要,如果你没有设置的话,那么服务就不会受你控制了。

- 作者: 小浪 2007年11月23日, 星期五 15:43  回复(0) |  引用(0) 加入博采

Windows服务小解

摘自:http://www.cnblogs.com/janmson/

Windows服务是其实一种特殊的二进制可执行文件,后缀名一般为EXE,之所以说它特殊,因为它具有同Windows   NT/2K系统的服务控制管理器(SCM:   Service   Control   Manager)通信。  
          服务控制管理器通过维护数据库对已经安装到系统的所有服务和驱动程序进行统一而安全的控制和管理。服务控制管理器是一个远程进程调用(RPC)服务器,在系统导入时自动启动。  
          一个简单的服务程序至少包括一些几个部分:  
        1.   Win32/控制台应用主程序;  
        2.   一个服务主程序,作为服务的导入点;  
        3.   一个服务控制处理器,就是同服务控制管理器SCM通信的函数;  
        4.   一个服务安装/反安装程序用于将一个EXE文件注册为一个服务。  
        下面我们针对上述几个部分分别介绍怎样构造一个Windows服务。  
   
  控制台应用主程序  
   
  在Win32下为WinMain函数,在控制台下为main函数,是服务的主程序。下面是服务主程序中至少要包含的语句。  
  #include   "Winsvc.h"                                                 //服务头文件  
  main()  
  {  
          ......  
          SERVICE_TABLE_ENTRY   Table[]={{"gkeyService",gkeyServiceMain},{NULL,NULL}};      
          StartServiceCtrlDispatcher(Table);    
                  ......  
  }  
  当然这是一个非常简单的主程序了。这里main只做了一件事情,就是填写SERVICE_TABLE_ENTRY结构数组Table。Table[0][0]是服务的名字(可以是您喜欢的任意字符串,此处我用的是gkeyService);Table[0][1]指定了服务主程序的名字,实际上这是一个指向服务主程序的函数指针,它也可以用您喜欢的函数名字(我用的是gkeyServiceMain)。现在通过调用参数为SERVICE_TABLE_ENTRY结构数组的函数StartServiceCtrlDispatcher()开始启动服务解析。注意这个函数的参数必须要符合一定的格式,Table[1][0]和Table[1][1]必须是NULL,就是说到了数组的结尾。当然并非必须这样,如果需要在这个执行程序中运行多个服务,可以在这个数组列表中加入更多的入口,构成多对服务名称和服务中程序,自然您需要在以下的步骤中需要为每个服务构造相应的完成函数。  
   
  服务主程序  
  典型的服务主程序的声明如下:  
  void   WINAPI   gkeyServiceMain(   DWORD   argc,   LPTSTR   *argv   )  
   
          在gkeyServiceMain函数中,需要实现的主要步骤包括:    
  1.   用合适的值填写SERVICE_STATUS结构来完成同服务控制管理器SCM的通信;  
  2.   在列表中注册前面所说的服务控制处理函数;  
  3.   调用实际的处理函数。  
   
          为了完成上述功能,需要使用两个全局变量:  
  SERVICE_STATUS                   m_ServiceStatus;  
  SERVICE_STATUS_HANDLE           m_ServiceStatusHandle;  
   
          服务主程序gkeyServiceMain()能够象通常的c/c++里的main()函数一样接受命令行参数,并且接受参数的方式也完全一样。第一个参数argc包含了传递给服务的参数个数,同c/c++的main()一样至少有一个参数就是服务应用本身。第二个参数是一个字符指针数组的指针。同main()函数一样,数组的第一个值总是指向服务的名字。  
          使用SERVICE_STATUS数据结构记录服务的当前状态,并将状态及时通告给服务控制管理器SCM,使用一个API函数SetServiceStatus()来实现这一目标。SERVICE_STATUS的数据成结构员如下:  
   
  dwServiceType                 =   SERVICE_WIN32;                    
  dwCurrentState               =   SERVICE_START_PENDING;   //   试图启动(初始状态)  
  dwControlsAccepted       =   SERVICE_ACCEPT_STOP;       //   仅接收服务控制程序的启动/停止,服务控制程序通常在  
   
  Windows   NT下的控制面板或者Windows   2K下的管理工具,我们也可以设置服务接受暂停/继续功能。  
   
          在服务主程序gkeyServiceMain()的开始应该设置SERVICE_STATUS的状态字段dwCurrentState为SERVICE_START_PENDING,通知SCM服务处于运行状态。如果发生错误,应该发送SERVICE_STOPPED通知服务控制管理器SCM。缺省状态下,服务控制管理器SCM将监视服务的活动,如果2分钟之类没有发现进程活动就杀死这个服务。  
          使用API函数RegisterServiceCtrlHandler()设置服务控制管理器SCM的服务控制处理函数,这个函数需要两个参数,一个是服务名称字符串,一个是服务控制处理函数句柄。  
          现在要设置dwCurrentState为SERVICE_RUNNING用以通知服务已经启动。  
   
  服务控制处理函数  
   
          服务控制管理器SCM使用服务控制处理函数和服务程序进行通信来了解服务的诸如启动、停止、暂停或继续等用户指令,它主要包含一个switch语句来处理每种情况,调用相应的步骤来启动、急需、清除和中断进程。函数收到一个象SERVICE_CONTROL_PAUSE,   SERVICE_CONTROL_CONTINUE,   SERVICE_CONTROL_STOP,   SERVICE_CONTROL_INTERROGATE等操作码,就需要为每种指令提供相应的处理步骤。  
   
  安装/反安装  
   
          要安装一个服务,在系统注册时需要生成一些入口,通常使用Windows有现成的API而不是注册函数来完成这些步骤,这些函数有CreateService()和DeleteService()。为了安装服务,首先使用OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS)打开服务控制管理器SCM。然后调用CreateService()来建立服务,给出服务的名字,如果要删除指定的服务,也将需要使用这个名字删除。  
   
  例子代码如下:  
  //   创建服务  
  String   strSrvName   =   Application->ExeName;  
  SC_HANDLE   schService   =   CreateService(  
                  scm,  
                  "ccrunSrv",                       //   服务名称  
                  "ccrun's   Service",         //   服务详细说明  
                  SERVICE_ALL_ACCESS,  
                  SERVICE_WIN32_OWN_PROCESS   |   SERVICE_INTERACTIVE_PROCESS,  
                  SERVICE_AUTO_START,       //   以自动方式开始  
                  SERVICE_ERROR_NORMAL,  
                  strSrvName.c_str(),       //   Service本体程序路径,必须与具体位置相符  
                  NULL,  
                  NULL,  
                  NULL,  
                  NULL,  
                  NULL);  
  if(schService   !=   NULL)  
  {  
          CloseServiceHandle(schService);  
  }  
  //---------------------------------------------------------------------------  
  //   开始Service  
  sHandle   =   OpenService(scm,   "ccrunSrv",   SERVICE_START);  
  if(sHandle!=NULL)  
  {  
          StartService(sHandle,   0,   NULL);  
          CloseServiceHandle(sHandle);  
  }  
  //---------------------------------------------------------------------------  
  //   关闭服务管理器  
  CloseServiceHandle(scm);  
 
2楼  nalichina   (Belina) 一级用户 该版得分小于等于100分  回复于 2004-06-07 09:25:34  得分 0

●   3.   如何控制WinNT下的服务   ●  
  ----在WindowsNT下   各种Service都存在service   control   manager   database中   因此我们可以通过对service   control   manager   database进行操作来实现对Service的编程。下面介绍常用的函数:  
   
  1:SC_HANDLE   OpenSCManager(LPCTSTR   lpszMachineName,   LPCTSTR   lpszDatabaseName,   DWORD   fdwDesiredAccess)    
  ----Open   SCManager   函数打开指定计算机上的service   control   manager   database。其中参数lpszMachineName指定计算机名   若为空则指定为本机。参数lpszDatabaseName指定要打开的service   control   manager   database,默认为空。  
   
  ----参数fdwDesiredAccess指定操作的权限,可以为下面取值之一  
  SC_MANAGER_ALL_ACCESS                 //   所有权限  
  SC_MANAGER_CONNECT                       //   允许连接service   control   manager  
  SC_MANAGER_CREATE_SERVICE         //   允许创建服务对象并把它加入service   control   manager   database  
  SC_MANAGER_ENUMERATE_SERVICE   //   允许枚举service   control   manager   database中的服务  
  SC_MANAGER_LOCK                             //   允许锁住service   control   manager   database  
  SC_MANAGER_QUERY_LOCK_STATUS   //   允许获取servicecontrolmanagerdatabase的封锁信息  
   
  ----函数返回值:函数执行成功则返回一个指向service   control   manager   database的句柄   失败则返回NULL。  
   
  2:SC_HANDLE   OpenService(SC_HANDLE   schSCManager,   LPCTSTR   lpszServiceName,   DWORD   fdwDesiredAccess)    
  ----OpenService函数打开指定的Service。  
  ----其中参数schSCManager是指向service   control   manager   database的句柄   由OpenSCManager函数返回。  
  ----参数lpszServiceName要打开的服务的名字   注意大小写。  
  ----参数fdwDesiredAccess指定操作的权限,可以为下面取值之一  
  SERVICE_ALL_ACCESS                         //   所有权限  
  SERVICE_CHANGE_CONFIG                   //   允许更改服务的配置  
  SERVICE_ENUMERATE_DEPENDENTS     //   允许获取依赖于该服务的其他服务  
  SERVICE_INTERROGATE                       //   允许立即获取服务状态  
  SERVICE_PAUSE_CONTINUE                 //   允许暂停和唤醒服务  
  SERVICE_QUERY_CONFIG                     //   允许获取服务配置  
  SERVICE_QUERY_STATU                       //   允许通过访问service   control   manager获取服务状态  
  SERVICE_START                                   //   允许启动服务  
  SERVICE_STOP                                     //   允许停止服务  
  SERVICE_USER_DEFINE_CONTROL       //   允许用户指定特殊的服务控制码  
  ----函数返回值:函数执行成功则返回指向某项服务的句柄   失败则返回NULL。  
   
  3:BOOL   QueryServiceStatus(SC_HANDLE   schService,LPSERVICE_STATUS   lpssServiceStatus)    
  ----QueryServiceStatus函数返回指定服务的当前状态。  
  ----其中参数schService是指向某项服务的句柄   由OpenService函数返回   且必须SERVICE_QUERY_STATUS的权限。  
  ----参数lpssServiceStatus中存放返回的服务状态信息   结构如下  
  typedef   struct   _SERVICE_STATUS  
  {  
          DWORD   dwServiceType                           //   服务类型  
          DWORD   dwCurrentState                         //   当前状态  
          DWORD   dwControlsAccepted                 //   服务可接受的控制码  
          DWORD   dwWin32ExitCode                       //   Win32出错代码  
          DWORD   dwServiceSpecificExitCode   //   服务出错代码  
          DWORD   dwCheckPoint                             //   用于跟踪服务长时间操作  
          DWORD   dwWaitHint                                 //   服务某一操作的最大允许时间,以毫秒为单位  
  }SERVICE_STATUS,   *LPSERVICE_STATUS;  
  ----函数返回值:函数执行成功则返回True,失败则返回False。  
   
  4:BOOLStartService(SC_HANDLE   schService,   DWORD   dwNumServiceArgs,   LPCTSTR   *   lpszServiceArgs)    
  ----StartService函数启动指定的服务。  
  ----其中参数schService是指向某项服务的句柄   由OpenService函数返回   且必须有SERVICE_START的权限。  
  ----dwNumServiceArgs为启动服务所需的参数的个数。  
  ----lpszServiceArgs为启动服务所需的参数。函数返回值:函数执行成功则返回True,失败则返回False。  
   
  5:BOOL   ControlService(SC_HANDLE   hService,   DWORD   dwControl,   LPSERVICE_STATUS   lpServiceStatus)    
  ----ControlService函数向Win32service发送控制码。  
  ----其中参数hService是指向某项服务的句柄   由OpenService函数返回。  
  ----参数dwControl为控制码   常用的有  
          SERVICE_CONTROL_STOP                 //   停止服务  
          SERVICE_CONTROL_PAUSE               //   暂停服务  
          SERVICE_CONTROL_CONTINUE         //   唤醒暂停的服务  
          SERVICE_CONTROL_INTERROGATE   //   刷新某服务的状态  
  ----参数lpServiceStatus指向SERVICE_STATUS结构   用于存放该服务最新的状态信息。  
  ----函数返回值:函数执行成功则返回True,失败则返回False。  
   
  6:BOOL   EnumServicesStatus(SC_HANDLE   hSCManager,   DWORD   dwServiceType,   DWORD   dwServiceState,  
                                                        LPENUM_SERVICE_STATUS   lpServices,   DWORD   cbBufSize,   LPDWORD   pcbBytesNeeded,  
                                                        LPDWORD   lpServicesReturned,   LPDWORD   lpResumeHandle)    
  ----EnumServicesStatus函数用于枚举NT下存在的Service。  
  ----其中参数hSCManager是指向service   control   manager   database的句柄   由OpenSCManager函数返回   且必须有SC_MANAGER_ENUMERATE_SERVICE的权限。  
  ----参数dwServiceType指定按服务的类型枚举。  
  ----参数dwServiceState指定按服务的状态枚举。  
  ----参数lpServices指向ENUM_SERVICE_STATUS结构   用于存放返回的服务的名字和状态信息。  
  ----参数cbBufSize返回参数lpServices的长度   以字节为单位。  
  ----参数pcbBytesNeeded返回获取剩余的Service所需字节的个数。  
  ----参数lpServicesReturned返回服务的个数。  
  ----参数lpResumeHandle   当第一次调用时该参数为0   当该函数再次被调用以获取另外的信息时   该参数表示下一个被读的Service。  
  ----函数返回值:函数执行成功则返回True,失败则返回False。  
  ----值得注意的是通常情况下该函数返回的结果为FALSE   我们可以调用GetLastError()来获取进一步信息。因为一台机器上有多种服务存在   所以GetLastError()应为ERROR_MORE_DATA   此时应再次调用EnumServicesStatus函数以获取正确的Service列表。
下面是一个对话框类的实例:
void   __fastcall   CServe::CreateBtnClick()  
{  
 
 m_scm=OpenSCManager(  
  NULL,                                             //   指定计算机名为本机  
  NULL,                                             //   指定要打开的service   control   managerdatabase名,   默认为空  
  SC_MANAGER_CREATE_SERVICE     //   允许创建服务对象并把它加入database  
  );  
 if   (m_scm!=NULL)//if   open   server   database   succeeds  
 {  
  /*SC_HANDLE*/m_svc=CreateService(  
  m_scm,                                   //   server   database   的句柄  
   "AccessControlService",                 //   Service名字  
   "AccessControlService",                 //   为Service显示用名  
   SERVICE_ALL_ACCESS,         //   指定server的使用权限,可使用所有的权限  
   //   指定server的类型,  
   //   Service   that   runs   in   its   own   process  
   //   The   service   can   interact   with   the   desktop  
   SERVICE_WIN32_OWN_PROCESS   |   SERVICE_INTERACTIVE_PROCESS,  
   SERVICE_AUTO_START,         //   以自动方式开始  
   SERVICE_ERROR_IGNORE,     //说明当Service在启动中出错时采取什么动作  
   m_ExeFile,  
   NULL,NULL,NULL,NULL,NULL  
   );  
  if   (m_svc!=NULL)  
  {  
   MessageBox("CREATE     SERVIVR   SUCCEEDS   !   ");  
  }  
  else  
  {  
   MessageBox("CREATE     SERVIVR   FAILED")   ;  
  }  
  GetDlgItem(IDB_CREATE)->EnableWindow(FALSE);   //   将“CreateServer”按扭失效  
  GetDlgItem(IDB_DEL)->EnableWindow(TRUE);           //   将“DeleteServer”按扭生效  
  GetDlgItem(IDB_STR)->EnableWindow(TRUE);    
  CloseServiceHandle(m_svc);  
  CloseServiceHandle(m_scm);  
 }//end   of   if  
}  

void   __fastcall   CServerCallDlg::DeleteBtnClick()  
{  
 m_scm=OpenSCManager   (  
  NULL,  
  NULL,  
  SC_MANAGER_CONNECT//允许连接到service   control   manager   database    
  );  
 if   (m_scm!=NULL)//   1st   if  
 {  
  m_svc   =   OpenService   (  
   m_scm,  
   "AccessControlService",  
   SERVICE_ALL_ACCESS  
   );  
  if   (m_svc!=NULL)//2st   if  
  {  
   //查询servers数据库的状态  
   QueryServiceStatus(m_svc,&m_ServiceStatus);  
   //删除前,先停止此Service.  
   if   (m_ServiceStatus.dwCurrentState   ==   SERVICE_RUNNING)  
   {  
    ControlService(m_svc,SERVICE_CONTROL_STOP,&m_ServiceStatus);  
   }    
   DeleteService(m_svc);  
   CloseServiceHandle(m_svc);   //删除Service后,最好再调用CloseServiceHandle,以便立即从数据库中移走此条目。  
  }    
  CloseServiceHandle(m_scm);  
 }        
 GetDlgItem(IDB_DEL)->EnableWindow(FALSE);               //   将“DeleteServer”按扭失效  
 GetDlgItem(IDB_CREATE)->EnableWindow(TRUE);   //   将“CreateServer”按扭失效  
 GetDlgItem(IDB_STR)->EnableWindow(FALSE);      
 GetDlgItem(IDB_STOP)->EnableWindow(FALSE);      
 MessageBox("DELETE   SERVICE   SUCCEEDS   !");  
}
void   __fastcall   CServerCallDlg::StartBtnClick()  
{  
 m_scm=OpenSCManager(  
  NULL,  
  NULL,  
  SC_MANAGER_CONNECT//允许连接到service   control   manager   database    
  );  
 if   (m_scm!=NULL)  
 {  
  m_svc=OpenService(  
   m_scm,  
   "AccessControlService",  
   SERVICE_START   //Enables   calling   of   the   StartService   function   to   start   the   service.                      
   );  
  if   (m_svc!=NULL)  
  {  
   //开始Service  
   StartService   (     m_svc,       //为指向Service的句柄,由OpenService返回  
    0,               //为启动服务所需的参数的个数  
    NULL           //为   启   动   服务所需的参数  
    );  
   CloseServiceHandle(m_svc);  
   CloseServiceHandle(m_scm);  
   GetDlgItem(IDB_STR)->EnableWindow(FALSE);      
   GetDlgItem(IDB_STOP)->EnableWindow(TRUE);      
   MessageBox("START   SERVICE   SUCCEEDS   !");  
  }  
  else  
  {  
   DWORD     error   =   GetLastError();  
   CString   errcode   ;  
   errcode.Format("启动服务错误,错误的类型识:%d",(int)error);  
   AfxMessageBox(errcode);  
  }  
 }  

}  
void   __fastcall   CServerCallDlg::StopBtnClick()  
{  
 //LPSERVICE_STATUS   ServiceStatus   ;  
 m_scm=OpenSCManager(  
  NULL,  
  NULL,  
  SC_MANAGER_ALL_ACCESS//取得所有的权限  
  );  
 if   (m_scm!=NULL)   //1st   if  
 {  
  m_svc=OpenService(  
   m_scm,  
   "AccessControlService",  
   //Enables   calling   of   the   ControlService   function   to   stop   the   service  
   //Enables   calling   of   the   QueryServiceStatus   function   to   query   the   status   of   the   service.  
   SERVICE_STOP|SERVICE_QUERY_STATUS  
   );  
  if   (m_svc!=NULL)//2st   if  
  {  
   QueryServiceStatus(m_svc,&m_ServiceStatus);//查询server的状态  
   if   (m_ServiceStatus.dwCurrentState==SERVICE_RUNNING)//3st   if  
   {  
    ControlService(m_svc,SERVICE_CONTROL_STOP,&m_ServiceStatus);  
    MessageBox("STOP   SERVVICE   SUCCEEDS   !");  
   }//end     3st   if  
   CloseServiceHandle(m_svc);  
  }//end   of   2st   if  
  CloseServiceHandle(m_scm);  
 }//end     1st   if  
 GetDlgItem(IDB_STR)->EnableWindow(TRUE);      
 GetDlgItem(IDB_STOP)->EnableWindow(FALSE);      
}  

- 作者: 小浪 2007年11月23日, 星期五 15:40  回复(0) |  引用(0) 加入博采

[转载]不应该不知道C++的常用库

摘自:http://tb.blog.csdn.net/TrackBack.aspx?PostId=1872705

非常惭愧,我过去也仅仅了解boost、STLport这样的库,以及一些GUI库,但是居然有如此众多的C++库,其实令我惊讶。当然,这个问题应该辩证的看,对于拿来主义确实可以直接使用这些库,但是如果学习和专业的开发,确实应该自己写,因为——适合的才是最好的。无论效率还是简洁性都是自己开发的好,否则还要那么多程序员干什么。程序就像做衣服,需要量身定做,拼凑起来的,一定不会好用,不过借鉴也是必须的,可以少走弯路。

还有什么库希望大家补充。

在C++中,库的地位是非常高的。C++之父   Bjarne   Stroustrup先生多次表示了设计库来扩充功能要好过设计更多的语法的言论。现实中,C++的库门类繁多,解决的问题也是极其广泛,库从轻量级到重量级的都有。不少都是让人眼界大开,亦或是望而生叹的思维杰作。由于库的数量非常庞大,而且限于笔者水平,其中很多并不了解。所以文中所提的一些库都是比较著名的大型库。  
   
  标准库  
   
  标准库中提供了C++程序的基本设施。虽然C++标准库随着C++标准折腾了许多年,直到标准的出台才正式定型,但是在标准库的实现上却很令人欣慰得看到多种实现,并且已被实践证明为有工业级别强度的佳作。  
   
  1、       Dinkumware   C++   Library  
   
  参考站点:http://www.dinkumware.com/'>http://www.dinkumware.com/  
   
  P.J.   Plauger编写的高品质的标准库。P.J.   Plauger博士是Dr.   Dobb's程序设计杰出奖的获得者。其编写的库长期被Microsoft采用,并且最近Borland也取得了其OEM的license,在其C/C++的产品中采用Dinkumware的库。  
   
  2、       RogueWave   Standard   C++   Library  
   
  参考站点:http://www.roguewave.com/'>http://www.roguewave.com/'>http://www.roguewave.com/'>http://www.roguewave.com/  
   
  这个库在Borland   C++   Builder的早期版本中曾经被采用,后来被其他的库给替换了。笔者不推荐使用。  
   
  3、SGI   STL  
   
  参考站点:http://www.roguewave.com/'>http://www.roguewave.com/'>http://www.roguewave.com/'>http://www.roguewave.com/  
   
  SGI公司的C++标准模版库。  
   
  4、STLport  
   
  参考站点:http://www.stlport.org/'>http://www.stlport.org/  
   
  SGI   STL库的跨平台可移植版本。  
   
   
   
  准标准库——Boost  
   
  Boost库是一个经过千锤百炼、可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的发动机之一。   Boost库由C++标准委员会库工作组成员发起,在C++社区中影响甚大,其成员已近2000人。   Boost库为我们带来了最新、最酷、最实用的技术,是不折不扣的“准”标准库。  
   
  Boost中比较有名气的有这么几个库:  
   
  Regex  
  正则表达式库  
   
  Spirit  
  LL   parser   framework,用C++代码直接表达EBNF  
   
  Graph  
  图组件和算法  
   
  Lambda  
  在调用的地方定义短小匿名的函数对象,很实用的functional功能  
   
  concept   check  
  检查泛型编程中的concept  
   
  Mpl  
  用模板实现的元编程框架  
   
  Thread  
  可移植的C++多线程库  
   
  Python  
  把C++类和函数映射到Python之中  
   
  Pool  
  内存池管理  
   
  smart_ptr  
  5个智能指针,学习智能指针必读,一份不错的参考是来自CUJ的文章:  
   
  Smart   Pointers   in   Boost,哦,这篇文章可以查到,CUJ是提供在线浏览的。中文版见笔者在《Dr.   Dobb's   Journal软件研发杂志》第7辑上的译文。  
   
   
  Boost总体来说是实用价值很高,质量很高的库。并且由于其对跨平台的强调,对标准C++的强调,是编写平台无关,现代C++的开发者必备的工具。但是Boost中也有很多是实验性质的东西,在实际的开发中实用需要谨慎。并且很多Boost中的库功能堪称对语言功能的扩展,其构造用尽精巧的手法,不要贸然的花费时间研读。Boost另外一面,比如Graph这样的库则是具有工业强度,结构良好,非常值得研读的精品代码,并且也可以放心的在产品代码中多多利用。  
   
  参考站点:http://www.boost.org'>http://www.boost.org(国内镜像:http://www.c'>http://www.c'>http://www.c'>http://www.c-view.org/tech/lib/boost/index.htm)  
   
  GUI  
   
  在众多C++的库中,GUI部分的库算是比较繁荣,也比较引人注目的。在实际开发中,GUI库的选择也是非常重要的一件事情,下面我们综述一下可选择的GUI库,各自的特点以及相关工具的支持。  
   
  1、       MFC  
   
  大名鼎鼎的微软基础类库(Microsoft   Foundation   Class)。大凡学过VC++的人都应该知道这个库。虽然从技术角度讲,MFC是不大漂亮的,但是它构建于Windows   API   之上,能够使程序员的工作更容易,编程效率高,减少了大量在建立   Windows   程序时必须编写的代码,同时它还提供了所有一般   C++   编程的优点,例如继承和封装。MFC   编写的程序在各个版本的Windows操作系统上是可移植的,例如,在   Windows   3.1下编写的代码可以很容易地移植到   Windows   NT   或   Windows   95   上。但是在最近发展以及官方支持上日渐势微。  
   
   
   
  2、       QT  
   
  参考网站:http://www.trolltech.com/'>http://www.trolltech.com/  
   
  Qt是Trolltech公司的一个多平台的C++图形用户界面应用程序框架。它提供给应用程序开发者建立艺术级的图形用户界面所需的所用功能。Qt是完全面向对象的很容易扩展,并且允许真正地组件编程。自从1996年早些时候,Qt进入商业领域,它已经成为全世界范围内数千种成功的应用程序的基础。Qt也是流行的Linux桌面环境KDE   的基础,同时它还支持Windows、Macintosh、Unix/X11等多种平台。  
   
   
   
  3、WxWindows  
   
  参考网站:http://www.wxwindows.org/'>http://www.wxwindows.org/  
   
  跨平台的GUI库。因为其类层次极像MFC,所以有文章介绍从MFC到WxWindows的代码移植以实现跨平台的功能。通过多年的开发也是一个日趋完善的GUI库,支持同样不弱于前面两个库。并且是完全开放源代码的。新近的C++   Builder   X的GUI设计器就是基于这个库的。  
   
  4、Fox  
   
  开放源代码的GUI库。作者从自己亲身的开发经验中得出了一个理想的GUI库应该是什么样子的感受出发,从而开始了对这个库的开发。有兴趣的可以尝试一下。  
   
  参考网站:http://www.fox'>http://www.fox-toolkit.org/  
   
  5、       WTL  
   
  基于ATL的一个库。因为使用了大量ATL的轻量级手法,模板等技术,在代码尺寸,以及速度优化方面做得非常到位。主要面向的使用群体是开发COM轻量级供网络下载的可视化控件的开发者。  
   
  6、       GTK  
   
  参考网站:http://gtkmm.sourceforge.net/  
   
  GTK是一个大名鼎鼎的C的开源GUI库。在Linux世界中有Gnome这样的杀手应用。而GTK就是这个库的C++封装版本。  

库  
   
   
  网络通信  
   
  ACE  
   
  参考网站:http://www.c'>http://www.c'>http://www.c'>http://www.cs.wustl.edu/~schmidt/ACE.html  
   
  C++库的代表,超重量级的网络通信开发框架。ACE自适配通信环境(Adaptive   Communication   Environment)是可以自由使用、开放源代码的面向对象框架,在其中实现了许多用于并发通信软件的核心模式。ACE提供了一组丰富的可复用C++包装外观(Wrapper   Facade)和框架组件,可跨越多种平台完成通用的通信软件任务,其中包括:事件多路分离和事件处理器分派、信号处理、服务初始化、进程间通信、共享内存管理、消息路由、分布式服务动态(重)配置、并发执行和同步,等等。  
   
  StreamModule  
   
  参考网站:http://www.omnifarious.org/StrMod/'>http://www.omnifarious.org/StrMod/  
   
  设计用于简化编写分布式程序的库。尝试着使得编写处理异步行为的程序更容易,而不是用同步的外壳包起异步的本质。  
   
  SimpleSocket  
   
  参考网站:http://home.hetnet.nl/~lcbokkers/simsock.htm  
   
  这个类库让编写基于socket的客户/服务器程序更加容易。  
   
  A   Stream   Socket   API   for   C++  
   
  参考网站:http://www.pcs.cnu.edu/'>http://www.pcs.cnu.edu/~dgame/sockets/socketsC++/sockets.html  
   
  又一个对Socket的封装库。  
   
  XML  
   
  Xerces  
   
  参考网站:http://xml.apache.org/xerces-c/  
   
  Xerces-C++   是一个非常健壮的XML解析器,它提供了验证,以及SAX和DOM   API。XML验证在文档类型定义(Document   Type   Definition,DTD)方面有很好的支持,并且在2001年12月增加了支持W3C   XML   Schema   的基本完整的开放标准。  
   
  XMLBooster  
   
  参考网站:http://www.xmlbooster.com/'>http://www.xmlbooster.com/  
   
  这个库通过产生特制的parser的办法极大的提高了XML解析的速度,并且能够产生相应的GUI程序来修改这个parser。在DOM和SAX两大主流XML解析办法之外提供了另外一个可行的解决方案。  
   
  Pull   Parser  
   
                    参考网站:http://www.extreme.indiana.edu/xgws/xsoap/xpp/'>http://www.extreme.indiana.edu/xgws/xsoap/xpp/  
   
                    这个库采用pull方法的parser。在每个SAX的parser底层都有一个pull的parser,这个xpp把这层暴露出来直接给大家使用。在要充分考虑速度的时候值得尝试。  
   
  Xalan  
   
                    参考网站:http://xml.apache.org/xalan-c/  
   
                    Xalan是一个用于把XML文档转换为HTML,纯文本或者其他XML类型文档的XSLT处理器。  
   
  CMarkup  
   
                    参考网站:http://www.firstobject.com/xml.htm'>http://www.firstobject.com/xml.htm  
   
                    这是一种使用EDOM的XML解析器。在很多思路上面非常灵活实用。值得大家在DOM和SAX之外寻求一点灵感。  
   
  libxml++  
   
  http://libxmlplusplus.sourceforge.net/  
   
  libxml++是对著名的libxml   XML解析器的C++封装版本  
   
   
   
  科学计算  
   
  Blitz++  
   
  参考网站:http://www.oonumerics.org/blitz/'>http://www.oonumerics.org/blitz/  
   
  Blitz++   是一个高效率的数值计算函数库,它的设计目的是希望建立一套既具像C++   一样方便,同时又比Fortran速度更快的数值计算环境。通常,用C++所写出的数值程序,比   Fortran慢20%左右,因此Blitz++正是要改掉这个缺点。方法是利用C++的template技术,程序执行甚至可以比Fortran更快。Blitz++目前仍在发展中,对于常见的SVD,FFTs,QMRES等常见的线性代数方法并不提供,不过使用者可以很容易地利用Blitz++所提供的函数来构建。  
   
  POOMA  
   
  参考网站:http://www.c'>http://www.c'>http://www.c'>http://www.codesourcery.com/pooma/pooma  
   
  POOMA是一个免费的高性能的C++库,用于处理并行式科学计算。POOMA的面向对象设计方便了快速的程序开发,对并行机器进行了优化以达到最高的效率,方便在工业和研究环境中使用。  
   
  MTL  
   
  参考网站:http://www.osl.iu.edu/research/mtl/'>http://www.osl.iu.edu/research/mtl/  
   
  Matrix   Template   Library(MTL)是一个高性能的泛型组件库,提供了各种格式矩阵的大量线性代数方面的功能。在某些应用使用高性能编译器的情况下,比如Intel的编译器,从产生的汇编代码可以看出其与手写几乎没有两样的效能。  
   
  CGAL  
   
  参考网站:www.cgal.org  
   
  Computational   Geometry   Algorithms   Library的目的是把在计算几何方面的大部分重要的解决方案和方法以C++库的形式提供给工业和学术界的用户。  
   
   
   
  游戏开发  
   
  Audio/Video   3D   C++   Programming   Library  
   
  参考网站:http://www.galacticasoftware.com/products/av/'>http://www.galacticasoftware.com/products/av/  
   
  AV3D是一个跨平台,高性能的C++库。主要的特性是提供3D图形,声效支持(SB,以及S3M),控制接口(键盘,鼠标和遥感),XMS。  
   
  KlayGE  
   
  参考网站:http://home.g365.net/enginedev/  
   
  国内游戏开发高手自己用C++开发的游戏引擎。KlayGE是一个开放源代码、跨平台的游戏引擎,并使用Python作脚本语言。KlayGE在LGPL协议下发行。感谢龚敏敏先生为中国游戏开发事业所做出的贡献。  
   
  OGRE  
   
  参考网站:http://www.ogre3d.org'>http://www.ogre3d.org  
   
  OGRE(面向对象的图形渲染引擎)是用C++开发的,使用灵活的面向对象3D引擎。它的目的是让开发者能更方便和直接地开发基于3D硬件设备的应用程序或游戏。引擎中的类库对更底层的系统库(如:Direct3D和OpenGL)的全部使用细节进行了抽象,并提供了基于现实世界对象的接口和其它类。  
   
   
   
  线程  
   
  C++   Threads  
   
  参考网站:http://threads.sourceforge.net/  
   
  这个库的目标是给程序员提供易于使用的类,这些类被继承以提供在Linux环境中很难看到的大量的线程方面的功能。  
   
  ZThreads  
   
  参考网站:http://zthread.sourceforge.net/  
   
  一个先进的面向对象,跨平台的C++线程和同步库。  
   
   
   
  序列化  
   
  s11n  
   
  参考网站:http://s11n.net/  
   
  一个基于STL的C++库,用于序列化POD,STL容器以及用户定义的类型。  
   
  Simple   XML   Persistence   Library  
   
  参考网站:http://sxp.sourceforge.net/  
   
  这是一个把对象序列化为XML的轻量级的C++库。  
   
   
   
  字符串  
   
  C++   Str   Library  
   
  参考网站:http://www.utilitycode.com/str/'>http://www.utilitycode.com/str/  
   
  操作字符串和字符的库,支持Windows和支持gcc的多种平台。提供高度优化的代码,并且支持多线程环境和Unicode,同时还有正则表达式的支持。  
   
  Common   Text   Transformation   Library  
   
  参考网站:http://cttl.sourceforge.net/  
   
  这是一个解析和修改STL字符串的库。CTTL   substring类可以用来比较,插入,替换以及用EBNF的语法进行解析。  
   
  GRETA  
   
  参考网站:http://research.microsoft.com/projects/greta/  
   
  这是由微软研究院的研究人员开发的处理正则表达式的库。在小型匹配的情况下有非常优秀的表现。

图像:

CxImage库,这是个很经典的图像库,使用起来也很方便。使我们做对象处理的基石。

介绍http://www.codeproject.com/bitmap/cximage.asp

综合  
   
  P::Classes  
   
  参考网站:http://pclasses.com/  
   
  一个高度可移植的C++应用程序框架。当前关注类型和线程安全的signal/slot机制,i/o系统包括基于插件的网络协议透明的i/o架构,基于插件的应用程序消息日志框架,访问sql数据库的类等等。  
   
  ACDK   -   Artefaktur   Component   Development   Kit  
   
  参考网站:http://acdk.sourceforge.net/  
   
  这是一个平台无关的C++组件框架,类似于Java或者.NET中的框架(反射机制,线程,Unicode,废料收集,I/O,网络,实用工具,XML,等等),以及对Java,   Perl,   Python,   TCL,   Lisp,   COM   和   CORBA的集成。  
   
  dlib   C++   library  
   
  参考网站:http://www.c'>http://www.c'>http://www.c'>http://www.cis.ohio-state.edu/~kingd/dlib/  
   
  各种各样的类的一个综合。大整数,Socket,线程,GUI,容器类,以及浏览目录的API等等。  
   
  Chilkat   C++   Libraries  
   
  参考网站:http://www.c'>http://www.c'>http://www.c'>http://www.chilkatsoft.com/cpp_libraries.asp  
   
  这是提供zip,e-mail,编码,S/MIME,XML等方面的库。  
   
  C++   Portable   Types   Library   (PTypes)  
   
  参考网站:http://www.melikyan.com/ptypes/'>http://www.melikyan.com/ptypes/  
   
  这是STL的比较简单的替代品,以及可移植的多线程和网络库。  
   
  LFC  
   
  参考网站:http://lfc.sourceforge.net/  
   
  哦,这又是一个尝试提供一切的C++库  
   
   
   
  其他库  
   
  Loki  
   
  参考网站:http://www.moderncppdesign.com/'>http://www.moderncppdesign.com/'>http://www.moderncppdesign.com/'>http://www.moderncppdesign.com/  
   
  哦,你可能抱怨我早该和Boost一起介绍它,一个实验性质的库。作者在loki中把C++模板的功能发挥到了极致。并且尝试把类似设计模式这样思想层面的东西通过库来提供。同时还提供了智能指针这样比较实用的功能。  
   
  ATL  
   
  ATL(Active   Template   Library)是一组小巧、高效、灵活的类,这些类为创建可互操作的COM组件提供了基本的设施。  
   
  FC++:   The   Functional   C++   Library  
   
  这个库提供了一些函数式语言中才有的要素。属于用库来扩充语言的一个代表作。如果想要在OOP之外寻找另一分的乐趣,可以去看看函数式程序设计的世界。大师Peter   Norvig在   “Teach   Yourself   Programming   in   Ten   Years”一文中就将函数式语言列为至少应当学习的6类编程语言之一。  
   
  FACT!  
   
  参考网站:http://www.kfa'>http://www.kfa-juelich.de/zam/FACT/start/index.html  
   
                    另外一个实现函数式语言特性的库  
   
  Crypto++  
   
  提供处理密码,消息验证,单向hash,公匙加密系统等功能的免费库。  
   
  还有很多非常激动人心或者是极其实用的C++库,限于我们的水平以及文章的篇幅不能包括进来。在对于这些已经包含近来的库的介绍中,由于并不是每一个我们都使用过,所以难免有偏颇之处,请读者见谅。  
   
   
   
  资源网站  
   
  正如我们可以通过计算机历史上的重要人物了解计算机史的发展,C++相关人物的网站也可以使我们得到最有价值的参考与借鉴,下面的人物我们认为没有介绍的必要,只因下面的人物在C++领域的地位众所周知,我们只将相关的资源进行罗列以供读者学习,他们有的工作于贝尔实验室,有的工作于知名编译器厂商,有的在不断推进语言的标准化,有的为读者撰写了多部千古奇作……  
   
  Bjarne   Stroustrup     http://www.research.att.com/'>http://www.research.att.com/~bs/  
   
  Stanley   B.   Lippman  
   
  http://blogs.msdn.com/slippman/(中文版http://www.zengyihome.net'>http://www.zengyihome.net/slippman/index.htm'>http://www.zengyihome.net'>http://www.zengyihome.net/slippman/index.htm)  
   
  Scott   Meyers     http://www.aristeia.com/'>http://www.aristeia.com/  
   
  David   Musser     http://www.c'>http://www.c'>http://www.c'>http://www.cs.rpi.edu/~musser/  
   
  Bruce   Eckel     http://www.bruceeckel.com'>http://www.bruceeckel.com  
   
  Nicolai   M.   Josuttis     http://www.josuttis.com/'>http://www.josuttis.com/  
   
  Herb   Sutter     http://www.gotw.ca/'>http://www.gotw.ca/  
   
  Andrei   Alexandrescu     http://www.moderncppdesign.com/'>http://www.moderncppdesign.com/'>http://www.moderncppdesign

参考http://topic.csdn.net/t/20041004/18/3426620.html

- 作者: 小浪 2007年11月15日, 星期四 09:46  回复(0) |  引用(0) 加入博采

探究Singleton设计模式
上期我们刊登了Jeffrey Richter之《探究Observer模式》,详细讲解了在微软架构下实现Observer模式之技术和经验。本期我们继续探讨设计模式方面之话题。Singleton通常被认为是最简单之设计模式,很多初学者都是通过它来了解设计模式之含义。然而,熟悉设计模式之技术人员都知道,要正确实现Singleton模式实际上是非常难之,涉及到很多技术细节。本文对于Singleton做了大胆深入之研究,并且探讨了C++、Java和C#中之Singleton实现。

在开发软件应用程序过程中,随着应用程序之开发,会出现重复性之模式。随着整个软件系统之开发,很多相同之模式会逐渐显现出来。
这种重复性模式概念在其他应用中是非常明显之。汽车制造就是一种此类应用。很多不同之汽车型号使用相同之子构件,包括大多数基本部件(例如,灯泡和紧固零件)以及较大之构件(例如,底盘和发动机)。
在住宅建筑中,重复性模式概念适用于螺丝和螺钉以及整体总体建筑物配电系统。无论组建之小组是为了开发新之汽车设计还是新之建筑物设计,它通常不必没有考虑到以前已解决之问题。如果设计和建筑住宅之小组必须重新构思和设计房子之每一个组成部分,则整个过程所花之时间比现在要长得多。门高或灯开关功能等许多设计决策(例如,门高或灯开关功能)很容易理解。房为满足给房子不同部分提供洗手功能之要求,房屋设计师不必重新设计和重新建造不同类型之输供水和蓄水设施,以便达到为房子不同部分提供洗手功能之要求:标准水槽以及标准之热水和冷水输入接头和排水输出接头是很容易理解非常常见之房屋建筑构件。可以将重复性模式概念反复应用于我们周围之几乎每样东西上,包括软件。
汽车和住宅建筑示例有助于在软件设计和构造中体现某些一般性之抽象概念。易于理解且明确定义之通用功能部件之概念是设计模式之源动力,它也是其他两篇设计模式文章探究工厂设计模式和探究观察者设计模式之重点。这些模式几乎涵盖了面向对象之软件设计之各个方面,包括对象创建、对象交互和对象生存期。在本文中,我们将讨论Singleton模式,它包含在创造性模式系列中。
创造性模式指示如何以及何时创建对象。很多实例需要只能通过创造性方法解决之特殊行为,而不是在创建实例后强制实施所需之行为。此类行为要求最好之例子之一包含在Singleton模式中。Singleton模式在《设计模式:可复用之面向对象软件之基础》这一经典参考书目中有正式之定义,该书之作者包括Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides(也称为四人组或GoF)。在设计模式中,此模式是最简单也是使用最广泛之模式之一。但是,正如我们将会看到之一样,在实现此模式时可能会出现一些问题。本文试图通过Singleton模式之多个早期实现来从头开始分析Singleton模式,以及如何在Microsoft .NET应用程序开发中发挥其最佳用途。

Singleton模式
按照设计模式中之定义,Singleton模式之用途是“ensure a class has only one instance, and provide a global point of access to it(确保每个类只有一个实例,并提供它之全局访问点)”。


它可以解决什么问题,或者换句话说,我们使用它之动机是什么?几乎在每个应用程序中,都需要有一个从中进行全局访问和维护某种类型数据之区域。在面向对象之(OO)系统中也有这种情况,在此类系统中,在任何给定时间只应运行一个类或某个类之一组预定义数量之实例。例如,当使用某个类来维护增量计数器时,此简单之计数器类需要跟踪在多个应用程序领域中使用之整数值。此类需要能够增加该计数器并返回当前之值。对于这种情况,所需之类行为应该仅使用一个类实例来维护该整数,而不是使用其它类实例来维护该整数。
最初,人们可能会试图将计数器类实例只作为静态全局变量来创建。这是一种通用之方法,但实际上只解决一部分问题;它解决了全局可访问性问题,但没有采取任何措施来确保在任何给定之时间只运行一个类实例。应该由类本身来负责只使用一个类实例,而不是由类用户来负责。应该始终不要让类用户来监视和控制运行之类实例之数量。
所需要之是使用某种方法来控制如何创建类实例,然后确保在任何给定之时间只创建一个类实例。这会确切之给我们提供所需之行为,并使客户端不必了解任何类细节。

逻辑模型
Singleton模型非常简单直观。(通常)只有一个Singleton实例。客户端通过一个已知之访问点来访问Singleton实例。在这种情况下,客户端是一个需要访问唯一Singleton实例之对象。图1以图形方式显示此关系。
物理模型
Singleton模式之物理模型也是非常简单之。但是,随着时间之推移,实现Singleton之方式也略有不同。让我们看一下原始之GoFSingleton实现。图2显示按设计模式所定义之原始Singleton模式之UML模型。
我们看到之是一个简单之类图表,显示有一个Singleton对象之私有静态属性以及返回此相同属性之公共方法Instance()。这实际上是Singleton之核心。还有其他一些属性和方法,用于说明在该类上允许执行之其他操作。为了便于此次讨论,让我们将重点放在实例属性和方法上。
客户端仅通过实例方法来访问任何Singleton实例。此处没有定义创建实例之方式。我们还希望能够控制如何以及何时创建实例。在OO开发中,通常可以在类之构造函数中最好之处理特殊对象之创建行为。这种情况也不例外。我们可以做之是,定义我们何时以及如何构造类实例,然后禁止任何客户端直接调用该构造函数。这是在Singleton构造中始终使用之方法。让我们看一下设计模式中之原始示例。通常,将下面所示之C++Singleton示例实现代码示例视为Singleton之默认实现。本示例已移植到很多其他编程语言中,通常它在任何之方之形式与此几乎相同。
C++Singleton示例实现代码

//Declaration
class Singleton{
public:
static Singleton* Instance();
protected:
Singleton();
private:
static Singleton* _instance;
}

// Implementation
Singleton* Singleton::_instance = 0;

Singleton* Singleton::Instance() {
if (_instance == 0) {
_instance = new Singleton;
}
return _instance;
}

让我们先花点时间分析一下此代码。该简单类有一个成员变量,此变量是指向该类自身之指针。注意,构造函数是受保护之,并且只有公共方法才是实例方法。在实例方法实现中,有一个控制块(if),它检查成员变量是否已初始化,如果没有之话,则创建一个新实例。控制块中这种惰性初始化意味着仅在第一次调用Instance()方法时初始化或创建Singleton实例。对于很多应用程序,这种方法效果很好。但对于多线程应用程序,这种方法证明具有潜在危险之副作用。如果两个线程同时进入控制块,则可能会创建该成员变量之两个实例。要解决这一问题,您可能想只将重要部分放在控制块周围以确保线程安全。如果您这样做,则将对实例方法之所有调用进行序列化处理,并且可能会对性能产生不利影响(取决于应用程序)。正是由于这个原因,创建了此模式之另一个版本,它使用某种称为双重检验机制之功能。下一个代码示例显示使用Java语法之双重检验锁定。


使用Java语法之双重检验锁定Singleton代码

//C++ port to Java
class Singleton
{
public staticSingletonInstance() {
if (_instance == null) {
synchronized (Class.forName("Singleton")) {
if (_instance == null) {
_instance = new Singleton();
}
}
}
return _instance;
}
protected Singleton() {}
private staticSingleton_instance = null;
}

在使用Java语法之双重检验锁定Singleton代码示例中,我们直接将C++代码移植到Java代码,以便利用Java关键部分块(已同步)。主要差别是不再有单独之声明和实现部分,没有指针数据类型,并且采用了新之双重检验机制。双重检验发生在第一个IF块上。如果成员变量为空,则执行进入关键部分块,该块再次双重检验该成员变量。仅在通过此最终测试后,才会实例化该成员变量。一般来说,两个线程无法使用这种方法创建两个类实例。另外,因为在第一次检查时没有出现线程阻塞,所以对此方法之大多数调用不会由于必须进入锁定而导致性能下降。目前,在实现Singleton模式时,很多Java应用程序中都广泛使用这种方法。这种方法很巧妙,但也有瑕疵。某些优化编译器可以将惰性初始化代码优化掉或对其重新进行排序,并且会重新产生线程安全问题。有关更深入之解释,请参阅"The Double-Check Locking is Broken" (http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html)。


另一种试图解决此问题之方法可能是,在成员变量声明中使用volatile关键字。这应该告诉编译器不要对代码重新排序,并且放弃优化。目前,这是唯一建议之JVM内存模型,并且不会立即解决该问题。
实现Singleton之最好方法是什么?最终(而不是碰巧),Microsoft .NET框架解决了所有这些问题,从而更易于实现Singleton,却不会产生我们目前讨论之不利副作用。.NET框架以及C#语言允许我们在必要时通过替换语言关键字,将上述之Java语法移植到C#语法。因此,Singleton代码变为以下内容:
以C#编码之双重检验锁定

// Port to C#
class Singleton
{
public staticSingletonInstance() {
if (_instance == null) {
lock (typeof(Singleton)) {
if (_instance == null) {
_instance = new Singleton();
}
}
}
return _instance;
}
protected Singleton() {}
private static volatileSingleton_instance = null;
}

此处,我们替换了锁定关键字来执行关键部分块,使用typeof操作并添加volatile关键字,以确保没有对代码进行优化程序重新排序。虽然此代码或多或少是GoFSingleton模式之直接移植,但它可达到我们之目之,并且我们可获得所需之行为。此代码还说明了将C++移植到Java和将Java移植到C#代码之一些相似之处和主要差别。但是,正如任何代码移植一样,通常目标语言或平台之一些优点可能在移植过程中失去。需要做之就是对代码重构,以便利用新目标语言或平台之功能。
在前面之每个代码示例中,Singleton之原始实现随时间之推移而发生变化,以解决在每个新模式实现中发现之问题。一些问题(例如,线程安全)要求对大多数实现进行更改,以满足在目前应用程序中日益增长之需要并解决演变发展问题。.NET在应用程序开发中提供了一个演变步骤。可以在“框架”级别解决前面示例中出现之很多亟待解决之问题,而不是在实现级别解决。虽然上一个示例显示了一个使用.NET框架和C#之有效Singleton类,但只需更好之利用.NET框架本身就可以大大简化此代码。以下示例使用.NET,它是一个松散之基于原始GoF模式之最小限度之Singleton类,并且仍然可获得类似之行为。
.NETSingleton示例
//.NET Singleton
sealed class Singleton
{
private Singleton() {}
public static readonlySingletonInstance = new Singleton();
}

此版本已大大简化并且更加直观。它仍然是Singleton吗?让我们看一下更改了哪些内容,然后再做决定。我们修改了要密封之类本身(该类密封后是不可继承之),删除了惰性初始化代码,删除了Instance()方法,并且对_instance变量做了大量之修改。对_instance变量所做之更改包括修改对公共方法之访问级别,将变量标记为只读,以及在声明时初始化该变量。此处,我们可以直接定义所需之行为,而不关心实现之潜在有害之副作用。那么,使用惰性初始化有什么优点以及使用多个线程有什么危险呢?在.NET框架中内置了所有正确之行为。让我们先看第一种情况:惰性初始化。
最初使用惰性初始化之主要原因是要获取仅在第一次调用Instance()方法中创建实例之行为,还因为C++规范中具有某种开放性,并不定义静态变量之确切初始化顺序。要在C++中获得所需之Singleton行为,必须采用涉及使用惰性初始化之运算方法。我们真正关心之是在第一次(在该情况下)调用实例属性中创建该实例,还是在此调用之前创建该实例之,并且类中之静态变量是否有已定义之初始化顺序。对于.NET框架,这就是我们获取之行为。在JIT过程中,当(且仅当)任何方法使用静态属性时,“框架”将初始化此静态属性。如果没有使用该属性,则不会创建实例。更准确之说,在JIT过程中发生之事情就是,在任何调用方使用该类之任何静态成员时构造和加载该类。在这种情况下,结果是相同之。
那么,线程安全初始化呢?“框架”也解决了这一问题。“框架”内部保证静态类型初始化之线程安全。换句话说,在上面之示例中,只创建一个Singleton类实例。还要注意,用于保存类实例之属性字段称为实例。此选项更好之说明了,在本文中之讨论过程中,此值是类之实例。在“框架”本身中,虽然使用之属性名称称为值,但有多个类使用此类型之Singleton。概念完全相同。
对类所做之其他更改意味着禁止划分子类。添加密封类修饰符可确保不会将该类划分为子类。GoFSingleton模式详细介绍了试图对Singleton划分子类所产生之问题,该划分通常并不是小事。在大多数情况下,可以很容易之开发没有父类之Singleton,并且添加划分子类功能会增加通常根本不需要之新之复杂性级别。随着复杂性之提高,测试、培训和文档编制等所需之时间也会增加。通常,除非绝对必要,否则您不希望提高任何代码之复杂性。

让我们看一下如何使用Singleton。使用我们最初之计数器之有关动机之概念,我们可以创建一个简单之Singleton计数器类并说明我们将如何使用它。图3显示了UML类说明将包含什么内容。
相应之类实现代码以及示例客户端使用如下所示。
示例Singleton使用

sealed class SingletonCounter {
public static readonly SingletonCounter Instance =
new SingletonCounter();
private long Count = 0;
private SingletonCounter() {}
public long NextValue() {
return ++Count;
}
}

class SingletonClient {
[STAThread]
static void Main() {
for (int i=0; i<20; i++) {
Console.WriteLine("NextSingletonvalue: {0}",
SingletonCounter.Instance.NextValue());
}
}
}

此处,我们还创建了一个Singleton类来维护具有long类型之增量计数。客户端是一个简单之控制台应用程序,它显示计数器类之20个值。虽然此示例极其简单,但它却说明了如何使用.NET来实现Singleton,然后将其用在应用程序中。

小结
Singleton设计模式是一个非常有用之机制,可用于在面向对象之应用程序中提供单个对象访问点。无论使用之是什么实现,该模式提供一个大家所熟知之概念,以便其在设计和开发小组之间方便之进行共享。但是,正如我们所发现之一样,注意到这些实现有多大差异及其潜在之副作用也是非常重要之。.NET框架为模式实现者在设计所需之功能类型方面提供了很大之帮助,实现者无需处理本文中所讨论之很多副作用。在正确实现后,可以证实模式之最初目之之有效性。


设计模式是非常有用之软件设计概念,可使小组将重点放在提供最佳类型之应用程序上,而不考虑它们是什么应用程序。关键在于正确而有效之使用设计模式,目前有很多关于将设计模式用于Microsoft .NET方面之MSDN系列文档,其中介绍了如何正确而有效地使用设计模式。

- 作者: 小浪 2007年11月6日, 星期二 09:50  回复(0) |  引用(0) 加入博采