《Windwos via C++》之精华

《Windwos via C++》之精华

  来自:梦在天涯C++博客(http://www.cppblog.com/mzty/)

引言

作为一名windows系统上图形软件开发者已经有3个多年头了,在此过程中有不少的经历和经验,非常高兴与大家分享,今天主要是《windows via C++》一书的阅读心得,所以主要谈到的是windowsC++的开发。

 

C++语言

我们知道C++是一门ISO的面向对象的语言,下面我介绍他的三个方面,也正是这三个主要方面决定了他被广泛的使用。第一,C++是一个功能强大的语言。比如C++语言本身提供的基于C语言的内嵌类型,数组,结构体和指针等,C++C新加入的面向对象的Class,继承,多态和模板等,还有C++标准模板库(STL)提供的实际开发当中常用的数据结构和算法的实现;第二,大量的开源和第三方库,如果在windows上使用C++开发,我们还可以借助microsoftC++提供的强有力的支持,首先的VS编译器,还有UIMFC,还有ATL等;除此之外我们还可以看到其他的许多非microsoft的开源的和第三方的支持,比如说Boost很大程度的扩充了STL,提供了更多更高级的功能,还有LokiC++与设计模式结合起来,还有很多的第三方的UI库,比如QTWxWindowsWTLGTK等,第三方的网络通信库,比如ACEStreamModule等,第三那的XML库,比如XercesCMarkuptinyxml等,第三方的科学计算库,比如Blitz++MTL等,第三方的游戏开发库,比如OGREKlayGE等,第三方的线程库,比如C++ ThreadsZThreads等,还有其他的很多。第三,C++的高性能。这个也是毋庸质疑的,我们知道的大部分的操作系统,嵌入式开发,游戏,图像软件离不开C++,这都是跟C++的高性能有很大的关系。但从这一点来看,这就是很多现代的其他的高级语言没有的,所以C++以后仍然有很长的路要走。

 

Windows via c++》之经典

上面说了C++语言本身,这里具体到windows系统上C++的开发,我们要基于windows系统开发软件,就难免的要和windows系统打交道,比如系统本身对字符编码的支持,系统的异常处理机制,线程的创建管理,进程的创建管理,内存和虚拟内存的使用,堆栈的操作,DLL的使用等。上面提到的这些都不是C++语言本身的职责,是需要操作系统提供的支持,而所有这些内容正是《windows via C++》所覆盖的内容,也正是本书很好的把C++Windows连接起来,引领C++程序员走向windows的底层,这也正是本书的经典之处!再次感谢作者Jeffrey Richter的伟大贡献!

 

1)字符编码:字符编码作为字符串的基础,自然而然就成了我们软件开发必不可缺少的基石,那么什么是字符编码那?字符编码就是字符在计算机内部的表示形式,我们都知道在计算机内部,所有的信息最终都表示为一个二进制的字符串。每一个二进制位(bit)有01两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为ASCII码,一直沿用至今。ASCII码一共规定了128个字符的编码,因为他只占用了一个字节的后面7位,最前面的1位统一规定为0。英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。所以后来就各国有了各国的编码,比如日文中的汉字远远大于256个,所以日文使用如果第一个字符在0 x 8 1 0 x 9 F 之间,或者在0 x E 0 0 x F C 之间,那么就必须观察下一个字节,才能确定字符串中的这个完整的字符。这样他就使用一个或两个字节来表示一个字符,这种编码被称为双字节字符集(D B C S )。由于世界上存在着多种编码方式,这使得同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。这个时候Unicode就出现了他将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。但是unicode也有不同的实现,主要的有三种:UTF-16:其本身就是标准的Unicode编码方案,又称为UCS-2,它固定使用16 bits(两个字节)整数来表示一个字符。UTF-32:又称为UCS-4,它固定使用32 bits(四个字节)整数来表示一个字符。UTF -8:最广泛的使用的UTF方案,UTF-8使用可变长度字节来储存Unicode字符,例如ASCII字母继续使用1字节储存,重音文字、希腊字母或西里尔字母等使用2字节来储存,而常用的汉字就要使用3字节。辅助平面字符则使用4字节。UTF-8更便于在使用Unicode的系统与现存的单字节的系统进行数据传输和交换。与前两个方案不同:UTF-8以字节为编码单元,没有字节序的问题。但是不管是哪种unicode的实现都可以包含世界所有的字符,解决软件本地化的难题。

 

Windows2000以前使用ASCII2000及以后的系统都使用unicode来编码,但是需要注意的是Windows2000既支持Unicode,也支持ANSI,因此可以为任意一种开发应用程序.如果调用任何一个Windows函数并给它传递一个ANSI字符串,那么系统首先要将字符串转换成Unicode,然后将Unicode字符串传递给操作系统。如果希望函数返回ANSI字符串,系统就会首先将Unicode字符串转换成ANSI字符串,然后将结果返回给你的应用程序。所有这些转换操作都是在你看不见的情况下发生的。当然,进行这些字符串的转换需要占用系统的时间和内存。比如同一个函数CreateWindowEx(),在内部其实是当ASCII是调用CreateWindowExA(),而当unicode时调用CreateWindowExW()。同时windows系统还给我们提供了ASCIIunicode间的转化函数MultiByteToWideChar()和WideCharToMultiByte()

 

2)进程和线程:进程通常被定义为一个正在运行的程序的实例,它由两个部分组成,第一, 一个是操作系统用来管理进程的内核对象,内核对象也是系统用来存放关于进程的统计信息的地方;第二,是地址空间,它包含所有可执行模块或 D L L 模块的代码和数据,它还包含动态内存分配的空间,如线程堆栈和堆分配空间。线程是应用程序执行时的最小单位,同样线程也是由两个部分组成的:第一, 一个是线程的内核对象,操作系统用它来对线程实施管理,内核对象也是系统用来存放线程统计信息的地方;第二,线程堆栈,它用于维护线程在执行代码时需要的所有函数参数和局部变量。

 

进程是不活泼的,从来不执行任何东西,它只是线程的容器,若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,该线程负责执行包含在进程的地址空间中的代码,当创建一个进程时,系统会自动创建它的第一个线程,称为主线程,然后,该线程可以创建其他的线程,而这些线程又能创建更多的线程。如果在单进程环境中,你有两个或多个线程正在运行,那么这两个线程将共享单个地址空间。这些线程能够执行相同的代码,对相同的数据进行操作。这些线程还能共享内核对象句柄,因为句柄表依赖于每个进程而不是每个线程存在。

windows提供和很多函数来创建和管理线程和进程,比如CreateThread(),ExitThread(),TerminateThread(),GetCurrentThread()CreateProcess(),ExitProcess(),TerminateProcess (),GetCurrentProcess()等。

 

3)线程同步和线程局部存储:由于同一进程的所有线程共享进程的虚拟地址空间,并且线程的中断是汇编语言级的,所以可能会发生两个线程同时访问同一个对象(包括全局变量、共享资源、API函数和MFC对象等)的情况,这有可能导致程序错误。属于不同进程的线程在同时访问同一内存区域或共享资源时,也会存在同样的问题。因此,在多线程应用程序中,常常需要采取一些措施来同步线程的执行。

 

windows提供了3中同步机制,分别是信号量Semaphore,互斥量Mutex,关键代码段CriticalSection。其中信号量Semaphore,是一个可以限制对指定的临界段进行访问的线程的数目的数据结构。互斥量Mutex和关键代码段CriticalSection,他们的作用是相同的,都是用来保证某时刻只有一个线程能够访问全局或静态的资源。区别是:Mutex是内核对象,可以设置等待超时时间,可以在不同的进程的线程中使用,但是所消耗的时间也比较多。CriticalSectionMutex相反。

 

线程局部存储(TLS),可以为全局或静态变量对不同的线程有不同的拷贝。

 

4)线程池:可以实现在程序中根据需要动态的创建线程,比如在server端,根据访问的用户的多少来创建线程的多少。在windows2000以后增加了创建线程池的API,比如 QueueUserWorkItem()。

 

5) 纤程:用户级的线程机制,比线程小的单位,开发人员负责管理纤程的调度,同时负责确定纤程何时在线程时间片上下文中运行,一般不会带来性能的提高,主要的目的是为开发人员调度多个不需要并行执行的任务提供一个便捷的机制。单线程可以包含一个或多个纤程。就内核而言,线程是抢占调度的,是正在执行的代码。然而,线程每次执行一个纤程的代码,你决定究竟执行哪个纤程,你可以调用ConvertThreadToFiber()将已有的线程转化位纤程,当对纤程的执行环境进行分配和初始化后,就可以将执行环境的地址与线程关联起来,该线程被转换成一个纤程,而纤程则在该线程上运行。现在,如果你的纤程(线程)返回或调用ExitThread函数,那么纤程和线程都会终止运行。除非打算创建更多的纤程以便在同一个线程上运行,否则没有理由将线程转换成纤程。若要创建另一个纤程,该线程(当前正在运行纤程的线程)可以调用CreateFiber函数。但是调用CreateFilber创建的纤程并不执行,直到调用SwitchToFiber函数。若要撤消纤程,可以调用DeleteFiber函数。

 

6) 作业:作业可以看作是一组进程的容器,把这些进程当作一个整体,对这个整体整个加入更多的限制. 因为Windows并不维护进程之间的父/子关系。即使父进程已经终止运行,子进程仍然会继续运行。Microsoft Windoss 2000提供了一个新的作业内核对象,使你能够将进程组合在一起,并且创建一个“沙框”,以便限制进程能够进行的操作。最好将作业对象视为一个进程的容器。但是,创建包含单个进程的作业是有用的,因为这样一来,就可以对该进程加上通常情况下不能加的限制。创建一个新作业内核对象可以调用CreateJobObject(),另一个进程要访问作业,可以通过OpenJobObject(),应该知道,关闭作业对象并不会迫使作业中的所有进程终止运行。该作业对象实际上做上了删除标记,只有当作业中的所有进程全部终止运行之后,该作业对象才被自动撤消。注意,关闭作业的句柄后,尽管该作业仍然存在,但是该作业将无法被所有进程访问。通过调用SetInformationJobObject()可以给作业加上各种限制.查询限制QueryInformationJobObject(),将进程放入作业AssignProcessToJobObject(),若要撤销作业中的进程调用TerminateJobObject()。

 

7) 虚拟内存,内存映射文件和堆栈:每个进程都被赋予它自己的虚拟地址空间。对于3 2位进程来说,这个地址空间是4 G B,其中默认有2G是用户可以使用的。由于每个进程可以接收它自己的私有的地址空间,因此当进程中的一个线程正在运行时,该线程可以访问只属于它的进程的内存。属于所有其他进程的内存则隐藏着,并且不能被正在运行的线程访问。注意在Windows 2000中,属于操作系统本身的内存也是隐藏的,正在运行的线程无法访问。这意味着线程常常不能访问操作系统的数据。

 

Windows提供了3种进行内存管理的方法,它们是第一, 虚拟内存,最适合用来管理大型对象或结构数组。 第二,内存映射文件,最适合用来管理大型数据流(通常来自文件)以及在单个计算机上运行的多个进程之间共享数据。第三, 内存堆栈,最适合用来管理大量的小对象。

 

在较老的操作系统中,物理存储器被视为计算机拥有的R A M的容量。换句话说,如果计算机拥有1 6 M BR A M,那么加载和运行的应用程序最多可以使用1 6 M BR A M。今天的操作系统能够使得磁盘空间看上去就像内存一样。磁盘上的文件通常称为页文件,它包含了可供所有进程使用的虚拟内存。

 

内存映射文件与虚拟内存一样,内存映射文件可以用来保留一个地址空间的区域,并将物理存储器提交给该区域。它们之间的差别是,物理存储器来自一个已经位于磁盘上的文件,而不是系统的页文件。一旦该文件被映射,就可以访问它,就像整个文件已经加载内存一样。

 

堆栈可以用来分配许多较小的数据块。例如,若要对链接表和链接树进行管理,最好的方法是使用堆栈,堆栈的优点是,可以不考虑分配粒度和页面边界之类的问题,集中精力处理手头的任务。堆栈的缺点是,分配和释放内存块的速度比其他机制要慢,并且无法直接控制物理存储器的提交和回收。进程和线程都有自己的堆栈,而且大小可以更改。

 

8) DLL使用:windows中广泛的使用dll来实现模块化,dll的使用一般有静态和动态加载,静态加载一般通过编译器的设置来实现,动态加载通过使用loadlibrary()和freelibrary()来实现。

 

9)结构化异常处理(SEH):SEH Windows 系统提供的异常处理功能,跟开发工具无关。windows上其他程序中的异常处理一般底层都是转化为SEH来实现。实际上,当你写一条C++ throw语句时,编译器就生成一个对WindowsRaiseException函数的调用。用于throw语句的变量传递给RaiseException作为附加的参数。

 

Windows via c++》之新增

 

164位支持:最新的系统有64的版本,这样的话进程就不再有最大4G的内存限制,但是由于64位系统与以前的32位系统其实是一个codebase,所以基本有所的接口都与以前32位保持椅子,这样的话我们以前的所有的32位的程序只需要从新编译就可以运行在64系统上,需要特别注意的是指针和句柄的从32位到64位的转变。

 

2windows vista/2008特有:提供了更加安全的stirng函数,一些内核对象的改变等。

 

3)可重复使用的代码:大量可在我们日常开发中重复使用的代码和让我们更好的理解进程的源代码processInfoLockCop等。

 

总结

总之,通过阅读《Windows via C++》使我们更好的了解windows系统,更好的在windows进行C++开发!推荐windowsC++开发人员可以一读!

你可能感兴趣的:(《Windwos via C++》之精华)