Rise的自留地

记录生活中的点滴,分享编程技术和游戏开发经验。

0%

(三)对服务的深入讨论之下

  现在我们还剩下一个函数可以在细节上讨论,那就是服务的CtrlHandler函数。

  当调用RegisterServiceCtrlHandler函数时,SCM得到并保存这个回调函数的地址。一个SCP调一个告诉SCM如何去控制服务的Win32函数,现在已经有10个预定义的控制请求:

Control code

Meaning

SERVICE_CONTROL_STOP
Requests the service to stop. The hService handle must have SERVICE_STOP access.

SERVICE_CONTROL_PAUSE
Requests the service to pause. The hService handle must have SERVICE_PAUSE_CONTINUE access.

SERVICE_CONTROL_CONTINUE
Requests the paused service to resume. The hService handle must have SERVICE_PAUSE_CONTINUE access.

SERVICE_CONTROL_INTERROGATE
Requests the service to update immediately its current status information to the service control manager. The hService handle must have SERVICE_INTERROGATE access.

SERVICE_CONTROL_SHUTDOWN
Requests the service to perform cleanup tasks, because the system is shutting down.
For more information, see Remarks.

SERVICE_CONTROL_PARAMCHANGE
Windows 2000: Requests the service to reread its startup parameters. The hService handle must have SERVICE_PAUSE_CONTINUE access.

SERVICE_CONTROL_NETBINDCHANGE
Windows 2000: Requests the service to update its network binding. The hService handle must have SERVICE_PAUSE_CONTINUE access.

SERVICE_CONTROL_NETBINDREMOVE
Windows 2000: Notifies a network service that a component for binding has been removed. The service should reread its binding information and unbind from the removed component.

SERVICE_CONTROL_NETBINDENABLE
Windows 2000: Notifies a network service that a disabled binding has been enabled. The service should reread its binding information and add the new binding.

SERVICE_CONTROL_NETBINDDISABLE
Windows 2000: Notifies a network service that one of its bindings has been disabled. The service should reread its binding information and remove the binding.

  上表中标有Windows 2000字样的就是2000中新添加的控制代码。除了这些代码之外,服务也可以接受用户定义的,范围在128-255之间的代码。

  当CtrlHandler函数收到一个SERVICE_CONTROL_STOP、SERVICE_CONTROL_PAUSE、
SERVICE_CONTROL_CONTINUE控制代码的时候,SetServiceStatus必须被调用去确认这个代码,并指定你认为服务处理这个状态变化所需要的时间。

  例如:你的服务收到了停止请求,首先要把SERVICE_STATUS结构的dwCurrentState成员设置成SERVICE_STOP_PENDING,这样可以使SCM确定你已经收到了控制代码。当一个服务的暂停或停止操作正在执行的时候,必须指定你认为这种操作所需要的时间:这是因为一个服务也许不能立即改变它的状态,它可能必须等待一个网络请求被完成或者数据被刷新到一个驱动器上。指定时间的方法就像我上一章说的那样,用成员dwCheckPoint和dwWaitHint来指明它完成状态改变所需要的时间。如果需要,可以用增加dwCheckPoint成员的值和设置dwWaitHint成员的值去指明你期待的服务到达下一步的时间的方式周期性的报告进展情况。

  当整个启动的过程完成之后,要再一次调用SetServiceStatus。这时就要把SERVICE_STATUS结构的dwCurrentState成员设置成SERVICE_STOPPED,当报告状态代码的同时,一定要把成员dwCheckPoint和dwWaitHint设置为0,因为服务已经完成了它的状态变化。暂停或继续服务的时候方法也一样。

  当CtrlHandler函数收到一个SERVICE_CONTROL_INTERROGATE控制代码的时候,服务将简单的将dwCurrentState成员设置成服务当前的状态,同时,把成员dwCheckPoint和dwWaitHint设置为0,然后再调用SetServiceStatus就可以了。

  在操作系统关闭的时候,CtrlHandler函数收到一个SERVICE_CONTROL_SHUTDOWN控制代码。服务根本无须回应这个代码,因为系统即将关闭。它将执行保存数据所需要的最小行动集,这是为了确定机器能及时关闭。缺省时系统只给很少的时间去关闭所有的服务,MSDN里面说大概是20秒的时间,不过那可能是Windows NT 4的设置,在我的Windows 2000 Server里这个时间是10秒,你可以手动的修改这个数值,它被记录在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control子键里面的WaitToKillServiceTimeout,单位是毫秒。

  当CtrlHandler函数收到任何用户定义的代码时,它应该执行期望的用户自定义行动。除非用户自定义的行动要强制服务去暂停、继续或停止,否则不调SetServiceStatus函数。如果用户定义的行动强迫服务的状态发生变化,SetServiceStatus将被调用去设置dwCurrentState、dwCheckPoint和dwWaitHint,具体控制代码和前面说的一样。

  如果你的CtrlHandler函数需要很长的时间执行操作的话,千万要注意:假如CtrlHandler函数在30秒内没有返回的话,SCM将返回一个错误,这不是我们所期望的。所以如果出现上述情况,最好的办法是再建立一个线程,让它去继续执行操作,以便使得CtrlHandler函数能够迅速的返回。例如,当收到一个SERVICE_CONTROL_STOP请求的时候,就像上面说的一样,服务可能正在等待一个网络请求被完成或者数据被刷新到一个驱动器上,而这些操作所需要的时间是你不能估计的,那么就要建立一个新的线程等待操作完成后执行停止命令,CtrlHandler函数在返回之前仍然要报告SERVICE_STOP_PENDING状态,当新的线程执行完操作之后,再由它将服务的状态设置成SERVICE_STOPPED。如果当前操作的时间可以估计的到就不要这样做,仍然使用前面交待的方法处理。

CtrlHandler函数我就先讲这些,下面说说服务怎么安装。一个服务程序可以使用CreateService函数将服务的信息添加到SCM的数据库。

SC_HANDLE CreateService(
SC_HANDLE hSCManager, // handle to SCM database
LPCTSTR lpServiceName, // name of service to start
LPCTSTR lpDisplayName, // display name
DWORD dwDesiredAccess, // type of access to service
DWORD dwServiceType, // type of service
DWORD dwStartType, // when to start service
DWORD dwErrorControl, // severity of service failure
LPCTSTR lpBinaryPathName, // name of binary file
LPCTSTR lpLoadOrderGroup, // name of load ordering group
LPDWORD lpdwTagId, // tag identifier
LPCTSTR lpDependencies, // array of dependency names
LPCTSTR lpServiceStartName, // account name
LPCTSTR lpPassword // account password
);

hSCManager是一个标示SCM数据库的句柄,可以简单的通过调用OpenSCManager得到。

SC_HANDLE OpenSCManager(
LPCTSTR lpMachineName, // computer name
LPCTSTR lpDatabaseName, // SCM database name
DWORD dwDesiredAccess // access type
);

lpMachineName是目标机器的名字,还记得我在第一章里说过可以在其它的机器上面安装服务吗?这就是实现的方法。对方机器名字必须以“\\”开始。如果传递NULL或者一个空的字符串的话就默认是本机。

lpDatabaseName是目标机器上面SCM数据库的名字,但MSDN里面说这个参数要默认的设置成SERVICES_ACTIVE_DATABASE,如果传递NULL,就默认的打开SERVICES_ACTIVE_DATABASE。所以我还没有真的搞明白这个参数的存在意义,总之使用的时候传递NULL就行了。

dwDesiredAccess是SCM数据库的访问权限,具体值见下表:

Object access

Description

SC_MANAGER_ALL_ACCESS
Includes STANDARD_RIGHTS_REQUIRED, in addition to all of the access types listed in this table.

SC_MANAGER_CONNECT
Enables connecting to the service control manager.

SC_MANAGER_CREATE_SERVICE
Enables calling of the CreateService function to create a service object and add it to the database.

SC_MANAGER_ENUMERATE_SERVICE
Enables calling of the EnumServicesStatus function to list the services that are in the database.

SC_MANAGER_LOCK
Enables calling of the LockServiceDatabase function to acquire a lock on the database.

SC_MANAGER_QUERY_LOCK_STATUS
Enables calling of the QueryServiceLockStatus function to retrieve the lock status information for the database.

  想要获得访问权限的话,似乎没那么复杂。MSDN里面说所有进程都被允许获得对所有SCM数据库的SC_MANAGER_CONNECT, SC_MANAGER_ENUMERATE_SERVICE, and SC_MANAGER_QUERY_LOCK_STATUS权限,这些权限使得你可以连接SCM数据库,枚举目标机器上安装的服务和查询目标数据库是否已被锁住。但如果要创建服务,首先你需要拥有目标机器的管理员权限,一般的传递SC_MANAGER_ALL_ACCESS就可以了。这个函数返回的句柄可以被CloseServiceHandle函数关闭。

lpServiceName是服务的名字,lpDisplayName是服务在“服务”管理工具里显示的名字。

dwDesiredAccess也是访问的权限,有一个比上面的还长的多的一个表,各位自己查MSDN吧。我们要安装服务,仍然简单的传递SC_MANAGER_ALL_ACCESS。

dwServiceType是指你的服务是否和其它的进程相关联,一般是SERVICE_WIN32_OWN_PROCESS,表示不和任何进程相关联。如果你确认你的服务需要和某些进程相关联,就设置成SERVICE_WIN32_SHARE_PROCESS。当你的服务要和桌面相关联的时候,需要设置成SERVICE_INTERACTIVE_PROCESS。

dwStartType是服务的启动方式。服务有三种启动方式,分别是“自动(SERVICE_AUTO_START)”“手动(SERVICE_DEMAND_START)”和“禁用(SERVICE_DISABLED)”。在MSDN里还有另外的两种方式,不过是专为驱动程序设置的。

dwErrorControl决定服务如果在系统启动的时候启动失败的话要怎么办。

意义

SERVICE_ERROR_IGNORE
启动程序记录错误发生,但继续启动。

SERVICE_ERROR_NORMAL
启动程序记录错误发生,并弹出一个消息框,但仍继续启动

SERVICE_ERROR_SEVERE
启动程序记录错误发生,如果是以last-known-good configuration启动的话,启动会继续。否则会以last-known-good configuration重新启动计算机。

SERVICE_ERROR_CRITICAL
启动程序记录错误发生,如果可能的话。如果是以last-known-good configuration启动的话,启动会失败。否则会以last-known-good configuration重新启动计算机。好严重的错误啊。

lpBinaryPathName是服务程序的路径。MSDN里面特别提到如果服务路径里面有空格的话一定要将路径用引号引起来。例如"d:\\my share\\myservice.exe"就一定要指定为"\"d:\\my share\\myservice.exe\""。

lpLoadOrderGroup的意义在于,如果有一组服务要按照一定的顺序启动的话,这个参数用于指定一个组名用于标志这个启动顺序组,不过我还没有用过这个参数。你的服务如果不属于任何启动顺序组,只要传递NULL或者一个空的字符串就行了。

lpdwTagId是应用了上面的参数之后要指定的值,专用于驱动程序,与本文内容无关。传递NULL。

lpDependencies标示一个字符串数组,用于指明一串服务的名字或者一个启动顺序组。当与一个启动顺序组建立关联的时候,这个参数的含义就是只有你指定的启动顺序组里有至少一个经过对整个组里所有的成员已经全部尝试过启动后,有至少一个成员成功启动,你的服务才能启动。不需要建立依存关系的话,仍是传递NULL或者一个空的字符串。但如果你要指定启动顺序组的话,必须为组名加上SC_GROUP_IDENTIFIER前缀,因为组名和服务名是共享一个命名空间的。

lpServiceStartName是服务的启动账号,如果你设置你的服务的关联类型是SERVICE_WIN32_OWN_PROCESS的话,你需要以DomainName\UserName的格式指定用户名,如果这个账户在你本机的话,用.\UserName就可以指定。如果传递NULL的话,会以本地的系统账户登陆。如果是Win NT 4.0或更早的版本的话,如果你指定了SERVICE_WIN32_SHARE_PROCESS,就必须传递.\System指定服务使用本地的系统账户。最后,如果你指定了SERVICE_INTERACTIVE_PROCESS,你必须使服务运行在本机系统账户。

  看名字就知道了,lpPassword是账户的密码。如果指定系统账户的话,传递NULL。如果账户没有密码的话,传递空字符串。

  总之服务的基本原理就是这样子了,到了这里这篇文章似乎可以告一段落了,但实际上还有很多内容必须要讨论,所以我还不能草草收笔,敬请关注下一章。

(二)对服务的深入讨论之上

  上一章其实只是概括性的介绍,下面开始才是真正的细节所在。在进入点函数里面要完成ServiceMain的初始化,准确点说是初始化一个SERVICE_TABLE_ENTRY结构数组,这个结构记录了这个服务程序里面所包含的所有服务的名称和服务的进入点函数,下面是一个SERVICE_TABLE_ENTRY的例子:

SERVICE_TABLE_ENTRY service_table_entry[] =
{
  { "MyFTPd" , FtpdMain },
  { "MyHttpd", Httpserv},
  { NULL, NULL },
};

  第一个成员代表服务的名字,第二个成员是ServiceMain回调函数的地址,上面的服务程序因为拥有两个服务,所以有三个SERVICE_TABLE_ENTRY元素,前两个用于服务,最后的NULL指明数组的结束。

  接下来这个数组的地址被传递到StartServiceCtrlDispatcher函数:

BOOL StartServiceCtrlDispatcher(
LPSERVICE_TABLE_ENTRY lpServiceStartTable
)

  这个Win32函数表明可执行文件的进程怎样通知SCM包含在这个进程中的服务。就像上一章中讲的那样,StartServiceCtrlDispatcher为每一个传递到它的数组中的非空元素产生一个新的线程,每一个进程开始执行由数组元素中的lpServiceStartTable指明的ServiceMain函数。

SCM启动一个服务程序之后,它会等待该程序的主线程去调StartServiceCtrlDispatcher。如果那个函数在两分钟内没有被调用,SCM将会认为这个服务有问题,并调用TerminateProcess去杀死这个进程。这就要求你的主线程要尽可能快的调用StartServiceCtrlDispatcher。

StartServiceCtrlDispatcher函数则并不立即返回,相反它会驻留在一个循环内。当在该循环内时,StartServiceCtrlDispatcher悬挂起自己,等待下面两个事件中的一个发生。第一,如果SCM要去送一个控制通知给运行在这个进程内一个服务的时候,这个线程就会激活。当控制通知到达后,线程激活并调用相应服务的CtrlHandler函数。CtrlHandler函数处理这个服务控制通知,并返回到StartServiceCtrlDispatcher。StartServiceCtrlDispatcher循环回去后再一次悬挂自己。

  第二,如果服务线程中的一个服务中止,这个线程也将激活。在这种情况下,该进程将运行在它里面的服务数减一。如果服务数为零,StartServiceCtrlDispatcher就会返回到入口点函数,以便能够执行任何与进程有关的清除工作并结束进程。如果还有服务在运行,哪怕只是一个服务,StartServiceCtrlDispatcher也会继续循环下去,继续等待其它的控制通知或者剩下的服务线程中止。

  上面的内容是关于入口点函数的,下面的内容则是关于ServiceMain函数的。还记得以前讲过的ServiceMain函数的的原型吗?但实际上一个ServiceMain函数通常忽略传递给它的两个参数,因为服务一般不怎么传递参数。设置一个服务最好的方法就是设置注册表,一般服务在
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Service\ServiceName\Parameters
子键下存放自己的设置,这里的ServiceName是服务的名字。事实上,可能要写一个客户应用程序去进行服务的背景设置,这个客户应用程序将这些信息存在注册表中,以便服务读取。当一个外部应用程序已经改变了某个正在运行中的服务的设置数据的时候,这个服务能够用RegNotifyChangeKeyValue函数去接受一个通知,这样就允许服务快速的重新设置自己。

  前面讲到StartServiceCtrlDispatcher为每一个传递到它的数组中的非空元素产生一个新的线程。接下来,一个ServiceMain要做些什么呢?MSDN里面的原文是这样说的:The ServiceMain function should immediately call the RegisterServiceCtrlHandler function to specify a Handler function to handle control requests. Next, it should call the SetServiceStatus function to send status information to the service control manager. 为什么呢?因为发出启动服务请求之后,如果在一定时间之内无法完成服务的初始化,SCM会认为服务的启动已经失败了,这个时间的长度在Win NT 4.0中是80秒,Win2000中不详...

  基于上面的理由,ServiceMain要迅速完成自身工作,首先是必不可少的两项工作,第一项是调用RegisterServiceCtrlHandler函数去通知SCM它的CtrlHandler回调函数的地址:

SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(
LPCTSTR lpServiceName, //服务的名字
LPHANDLER_FUNCTION lpHandlerProc //CtrlHandler函数地址
)

  第一个参数指明你正在建立的CtrlHandler是为哪一个服务所用,第二个参数是CtrlHandler函数的地址。lpServiceName必须和在SERVICE_TABLE_ENTRY里面被初始化的服务的名字相匹配。RegisterServiceCtrlHandler返回一个SERVICE_STATUS_HANDLE,这是一个32位的句柄。SCM用它来唯一确定这个服务。当这个服务需要把它当时的状态报告给SCM的时候,就必须把这个句柄传给需要它的Win32函数。注意:这个句柄和其他大多数的句柄不同,你无需关闭它。

SCM要求ServiceMain函数的线程在一秒钟内调用RegisterServiceCtrlHandler函数,否则SCM会认为服务已经失败。但在这种情况下,SCM不会终止服务,不过在NT 4中将无法启动这个服务,同时会返回一个不正确的错误信息,这一点在Windows 2000中得到了修正。

  在RegisterServiceCtrlHandler函数返回后,ServiceMain线程要立即告诉SCM服务正在继续初始化。具体的方法是通过调用SetServiceStatus函数传递SERVICE_STATUS数据结构。

BOOL SetServiceStatus(
SERVICE_STATUS_HANDLE hService, //服务的句柄
SERVICE_STATUS lpServiceStatus //SERVICE_STATUS结构的地址
)

  这个函数要求传递给它指明服务的句柄(刚刚通过调用RegisterServiceCtrlHandler得到),和一个初始化的SERVICE_STATUS结构的地址:

typedef struct _SERVICE_STATUS
{
DWORD dwServiceType;
DWORD dwCurrentState;
DWORD dwControlsAccepted;
DWORD dwWin32ExitCode;
DWORD dwServiceSpecificExitCode;
DWORD dwCheckPoint;
DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;

SERVICE_STATUS结构含有七个成员,它们反映服务的现行状态。所有这些成员必须在这个结构被传递到SetServiceStatus之前正确的设置。

  成员dwServiceType指明服务可执行文件的类型。如果你的可执行文件中只有一个单独的服务,就把这个成员设置成SERVICE_WIN32_OWN_PROCESS;如果拥有多个服务的话,就设置成SERVICE_WIN32_SHARE_PROCESS。除了这两个标志之外,如果你的服务需要和桌面发生交互(当然不推荐这样做),就要用“OR”运算符附加上SERVICE_INTERACTIVE_PROCESS。这个成员的值在你的服务的生存期内绝对不应该改变。

  成员dwCurrentState是这个结构中最重要的成员,它将告诉SCM你的服务的现行状态。为了报告服务仍在初始化,应该把这个成员设置成SERVICE_START_PENDING。在以后具体讲述CtrlHandler函数的时候具体解释其它可能的值。

  成员dwControlsAccepted指明服务愿意接受什么样的控制通知。如果你允许一个SCP去暂停/继续服务,就把它设成SERVICE_ACCEPT_PAUSE_CONTINUE。很多服务不支持暂停或继续,就必须自己决定在服务中它是否可用。如果你允许一个SCP去停止服务,就要设置它为SERVICE_ACCEPT_STOP。如果服务要在操作系统关闭的时候得到通知,设置它为SERVICE_ACCEPT_SHUTDOWN可以收到预期的结果。这些标志可以用“OR”运算符组合。

  成员dwWin32ExitCode和dwServiceSpecificExitCode是允许服务报告错误的关键,如果希望服务去报告一个Win32错误代码(预定义在WinError.h中),它就设置dwWin32ExitCode为需要的代码。一个服务也可以报告它本身特有的、没有映射到一个预定义的Win32错误代码中的错误。为了这一点,要把dwWin32ExitCode设置为ERROR_SERVICE_SPECIFIC_ERROR,然后还要设置成员dwServiceSpecificExitCode为服务特有的错误代码。当服务运行正常,没有错误可以报告的时候,就设置成员dwWin32ExitCode为NO_ERROR。

  最后的两个成员dwCheckPoint和dwWaitHint是一个服务用来报告它当前的事件进展情况的。当成员dwCurrentState被设置成SERVICE_START_PENDING的时候,应该把dwCheckPoint设成0,dwWaitHint设成一个经过多次尝试后确定比较合适的数,这样服务才能高效运行。一旦服务被完全初始化,就应该重新初始化SERVICE_STATUS结构的成员,更改dwCurrentState为SERVICE_RUNNING,然后把dwCheckPoint和dwWaitHint都改为0。

dwCheckPoint成员的存在对用户是有益的,它允许一个服务报告它处于进程的哪一步。每一次调用SetServiceStatus时,可以增加它到一个能指明服务已经执行到哪一步的数字,它可以帮助用户决定多长时间报告一次服务的进展情况。如果决定要报告服务的初始化进程的每一步,就应该设置dwWaitHint为你认为到达下一步所需的毫秒数,而不是服务完成它的进程所需的毫秒数。

  在服务的所有初始化都完成之后,服务调用SetServiceStatus指明SERVICE_RUNNING,在那一刻服务已经开始运行。通常一个服务是把自己放在一个循环之中来运行的。在循环的内部这个服务进程悬挂自己,等待指明它下一步是应该暂停、继续或停止之类的网络请求或通知。当一个请求到达的时候,服务线程激活并处理这个请求,然后再循环回去等待下一个请求/通知。

  如果一个服务由于一个通知而激活,它会先处理这个通知,除非这个服务得到的是停止或关闭的通知。如果真的是停止或关闭的通知,服务线程将退出循环,执行必要的清除操作,然后从这个线程返回。当ServiceMain线程返回并中止时,引起在StartServiceCtrlDispatcher内睡眠的线程激活,并像在前面解释过的那样,减少它运行的服务的计数。

有那么一类应用程序,是能够为各种用户(包括本地用户和远程用户)所用的,拥有用户授权级进行管理的能力,并且不论用户是否物理的与正在运行该应用程序的计算机相连都能正常执行,这就是所谓的服务了。

单田芳先生是中国著名的评书演员,我非常喜欢听单田芒的评书,在那个没有电视机的时代,收听单田芳先生的评书对我来说就是一种最大的人生享受。所以,“单田芳”这三个字早就镌刻在我的脑海之中。一直到今天我还深深地敬仰着这位全国闻名的优秀演员。俗话说,金杯银杯,不如老百姓的口碑。单先生的评书艺术在全国人民中是有口皆碑!

http://www.cnblogs.com/guaiguai/archive/2007/09/17/894819.html


现代的软件科学中, 很多内容和概念, 实际上是从数学/语言学等相当古老的领域里借来的, 为什么呢? 因为软件科学中的很多方面, 与其它学科中所碰到的问题并无不同. 一套数学理论,某个数学公式,无论从哪个层次去看,和它们有关的人分为两种:发明者,使用者. 这和软件也是相当一致的,  软件首先要有人编制, 然后别人来使用(好不好另说). 数学的一个特性就是他的抽象性, 本文讨论软件设计中, 由抽象所展开的一些问题.

算起来,我用Visual C++也有将近5年的历史了。在这期间,我也曾涉猎过Visual Basic和Delphi,但都是浅尝而止;Visual C++始终是我的主业。可是努力的成果如何呢?我用Delphi作出了十多个有规模的软件,用VB--虽然我用在VB上的时间只有短短的两三个月--也有两个像样的项目;然而,在我付出了最大热情和最多努力的Visual C++上面,却只作出了三个自己看得上眼的软件。

函数重载


 C++允许在参数类型不同的前提下重载函数。重载的函数与具有多态性的函数(即虚函数)不同处在于:调用正确的被重载函数实体是在编译期间就被决定了的;而对于具有多态性的函数来说,是通过运行期间的动态绑定来调用我们想调用的那个函数实体。多态性是通过重定义(或重写)这种方式达成的。请不要被重载 (overloading)和重写(overriding)所迷惑。重载是发生在两个或者是更多的函数具有相同的名字的情况下。区分它们的办法是通过检测它们的参数个数或者类型来实现的。重载与CLOS中的多重分发(multiple dispatching)不同,对于参数的多重分发是在运行期间多态完成的。

继承的本质


继承关系是一种耦合度很高的关系,它与组合及一般化(genericity)一样,提供了OO中的一种基本方法,用以将不同的软件组件组合起来。一个类的实例同时也是那个类的所有的祖先的实例。为了保证面向对象设计的有效性,我们应该保存下这种关系的一致性。在子类中的每一次重新定义都应该与在其祖先类中的最初定义进行一致性检查。子类中应该保存下其祖先类的需求。如果存在着不能被保存的需求,就说明了系统的设计有错误,或者是在系统中此处使用继承是不恰当的。由于继承是面向对象设计的基础,所以才会要求有一致性检测。C++中对于非虚拟函数重载的实现, 意味着编译器将不会为其进行一致性检测。C++并没有提供面向对象设计的这方面的保证。

以下文章翻译自Ian Joyner所著的

《C++?? A Critique of C++ and Programming and Language Trends of the 1990s》 3/E【Ian Joyner 1996】


该篇文章已经包含在Ian Joyner所写的《Objects Unencapsulated 》一书中(目前已经有了日文的翻译版本),该书的介绍可参见于:

全局分析


 【P&S 94】中提到对于类型安全的检测来说有两种假设。一种是封闭式环境下的假设,此时程序中的各个部分在编译期间就能被确定,然后我们可以对于整个程序来进行类型检测。另一种是开放式环境下的假设,此时对于类型的检测是在单独的模块中进行的。对于实际开发和建立原型来说,第二种假设显得十分有效。然而,【P&S 94】中又提到,“当一种已经完成的软件产品到达了成熟期时,采用封闭式环境下的假设就可以被考虑了,因为这样可以使得一些比较高级的编译技术得以有了用武之处。只有在整个程序都被了解的情况下,我们才可能在其上面执行诸如全局寄存器分配、程序流程分析及无效代码检测等动作。”(附:【P&S 94】Jens Palsberg and Michael I. Schwartzbach, Object-Oriented Type Systems, Wiley 1994)