关于作者

用户名:sunyes
笔名:sunyes
地区:
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



我的大学

摄影论坛-都是mm啊

常去的论坛-都贼又意思

程序员该去的网站

OPC相关网站

访问统计:
文章个数:15
评论个数:30
留言条数:10




Powered by BlogDriver 2.1

远志的博客

 

东北人,活雷锋 招人稀罕,长得帅 读书20载,身无分文,满脑子迂腐思想,一肚子坏水

文章

基于OPC规范的客户应用程序实现

石林锁,王涛,刘顺波
(第二炮兵工程学院 机电工程系,陕西 西安 710025
摘要:介绍了过程控制领域中引进的一种新技术-OPC数据访问标准,论述了OPC相关概念、基础及OPC标准接口,并以OPC数据访问定制接口规范(1.0A)为蓝本,给出了利用Visual C++实现OPC客户应用程序的详细步骤。
关键词:过程控制;OPC规范;接口;客户应用
中图分类号:TP   文献标码:A

The Realization of Client Application Based on OPC Specification

SHI Lin-suoWANG Tao LIU Shun-bo
(Dept. of Mechanical and Electronic, 2nd Artillery Engineering College, Xi'an 710025, China)

Abstract: The paper introduces the OPC data access standard which was introduced into process control field recently, and discusses relative concepts, fundamentals and standard interfaces of OPC (Ole for Process Control). On the basis of OPC Data Access Custom Interface Specification (1.0A), the author gives out the detailed procedures to realize OPC client application using Visual C++.

Key Words: Process control; OPC specification; Interface; Client application


随着计算机技术与控制技术的不断发展,现代工业过程控制系统逐渐发展成为由现场设备管理、过程管理和商业管理三个层次组成的系统。在这种新的过程工业信息体系结构中,从现场设备管理层、过程控制管理层到商业管理层包含了各种信息。为了实现整个过程控制系统的信息集成,需要解决许多问题,其中最重要的是通信兼容问题。因为现场管理层中大量的现场数据信息必须以一致的形式提供给用户或应用程序;过程管理层必须将现场管理层送来的信息及时加以处理并递交商业管理层;在商业管理层中这些信息又以一致的形式送给客户应用程序以简化信息的综合过程。所以,解决这些问题的关键在于为过程控制系统的数据访问提供一种开放有效的通信标准。
    在传统系统中,解决客户应用程序从数据源(如:现场设备、SCADA系统等)读取数据的方法是为不同的客户应用程序编写不同的驱动程序。但是,这种方式存在许多问题,如同一个设备为适应不同的应用程序可能需要多种驱动程序,不同的驱动程序之间存在着不一致性,驱动程序对硬件存在着极大的依赖性等等。为了解决这些问题,一些与微软公司合作的自动化硬件和软件供应商联合制定了一套称为OPC规范的OLE/COM接口协议,以此来提高过程控制工业中的自动化/控制应用程序,现场系统/设备以及商业/办公室应用程序之间的互操作性。可以说OPC是工业监控软件的现场总线,其基本思想是:每个硬件供应商为其设备开发一个通用的数据接口(即OPC Server),供其它系统读写信息,客户应用软件也可以通过OPC规范的接口来读写硬件设备的信息(作为OPC Client)。由于硬件供应商通常将硬件驱动程序封装成OPC Server单独出售,这样作为OPC数据客户端的上层应用,可以不包含任何通讯接口程序,不必关心底层硬件内部的具体细节,只需遵循OPC数据接口协议,就能够从不同的硬件供应商提供的OPC数据服务器中取得数据。

OPC规范提供了两套接口方案,即定制接口和自动化接口。定制接口效率高,通过该接口,客户能够发挥OPC服务器的最佳性能,采用C++语言的客户一般采用定制接口方案;自动化接口使解释性语言和宏语言访问OPC服务器成为可能,采用VB等语言的客户一般采用自动化接口。本文详细介绍了在Visual C++环境下,使用OPC定制接口访问OPC服务器的通用实现方法。

1  OPC的基本结构

OPC由两套接口组成:OPC定制接口和OPC自动化接口,如图1所示。OPC服务器必须实现定制接口,可选择实现自动化接口。这两套标准接口的制定极大地方便了服务器和用不同语言开发的客户应用之间的通信,使用户对开发工具的选择有了较大的自由。

SCADA

1  OPC接口

OPC接口可以潜在地应用在许多应用程序中。它们可以用于从最低层设备中读取未加工的数据,再转化至SCADA 或者DCS系统;也可以用于从SCADA 或者DCS系统中采集数据输入到应用程序中。OPC是为从某一网络节点中的某一服务器中采集数据而设计的,同时又能够形成OPC服务器。该服务器允许客户应用软件在由许多不同的OPC供应商提供的服务器中传输数据,并可通过单一的对象在不同的节点上运行,其工作特点如图2所示。

2  OPC客户/服务器关系

2  OPC定制接口

CC++编写OPC客户应用程序时可以使用定制接口,也可以使用自动化接口。由于定制接口具有更高的性能,建议尽可能使用定制接口。本文在VC下实现的客户应用程序采用的是OPC定制接口1.0OPC定制接口的类模式可以根据其接口及其方法划分为3组,依次呈包含关系,如图3所示。

3  OPC定制接口的类模式

2.1  OPC Server对象

OPC ServerOPC启动服务器,通过它获得其他对象和服务的起始类,并用于返回OPC Group类对象。OPC Server级别有多种属性,其中包含一个OPC服务器对象的状态和版本等信息。这种级别中的对象由客户应用创建。IOPCServer接口包含管理OPC Group级别中的对象的方法。如将组加入服务器或从服务器中删除组的方法("AddGroup","RemoveGroup")。IOPCBrowseServerAddressSpace接口包含查找服务器地址空间的方法。IOPCCommon接口方法用于通知服务器语言的设置和客户机的名称。同时还存在以下接口:图4说明了OPC Server对象及其定制接口。

4  OPC Server对象

2.2  OPC Group对象

OPC Group存储由若干OPC Item组成的Group信息,并用于返回OPC Item类对象。OPC Group级别管理被称为OPC Item的各个过程变量。IOPCItemMgt接口提供将项加入组或从组中删除项的方法("AddItem","RemoveItem")。IOPCGroupStateMgt接口的方法用于处理组专用的参数或复制组。同时还存在以下接口:图5说明了OPC Group对象及其定制接口。

5  OPC Group对象

2.3  OPC Item对象

    OPC Item存储具体Item 的定义、数据值、状态值等信息。OPC Item级别的一个对象代表与一个过程变量的连接。该对象的唯一接口是OPCItemDisp。关于OPC Item的信息可以在属性表中找到,例如数值("Value")属性或存取路径("AccessPath")属性。图6说明了"OPC Item"对象及其接口。

6  OPC Item对象

由于本文使用定制接口实现OPC客户应用程序,所以不使用IOPCItemDisp接口,而是使用枚举器对象EnumOPCItemAttributesIEnumOPCItemAttributes接口枚举服务器中的所有OPC Item。如图7所示。

7  EnumOPCItemAttributes对象

3 OPC客户应用程序的实现

3.1  操作OPC的类模型

按照OPC的类模型,当对象方法调用OPC对象时必须遵循一定的顺序。如果要创建一个OPC Item类的实例,则首先需要一个OPC Group对象。而要创建一个OPC Group对象的前提是存在一个OPC Server类的实例,并建立一个与该服务器的连接。图8说明了操作OPC类模型的流程。

操作OPC类模型的流程

3.2  编程顺序

本文在Visual C++环境中实现的OPC客户应用程序包括所有通常在典型客户应用下都会有的部分,如:建立与服务器的连接,初始化变量的组,以及为一个项读写数据。下面详细介绍一下在VC环境下OPC应用的基本结构。
第一步:登陆COM
如果程序要调用COM库的某一函数,必须先登陆COM。函数CoInitialize()可以完成此功能。从函数CoGetMalloc()可以得到一个指向COM内存管理接口的指针。
HRESULT r1;
r1= CoInitialize(NULL);
r1= CoGetMalloc(MEMCTX_TASK,&g_pIMalloc);
第二步:将ProgID变换为CLSID
每个COM服务器有一个字符串类型的ProgID,通过它可以得到一个全球唯一的CLSID。用CLSIDFromProgID()函数可以实现这个转换。ProgID用变量szName进行参数传递。
r1= CLSIDFromProgID(szName,&clsid);
第三步:建立与OPC服务器的连接
CoCreateInstance()函数创建一个OPC Server类实例,其CLSID值设定如下。
r2=CoCreateInstance(clsid,NULL,CLSCTX_LOCAL_SERVER,IID_IUnkown,(void**)&pUNK);
这段程序的结果是得到一个指向服务器对象IUnkown接口的指针(变量pUNK)。
第四步:请求其它接口指针
IUnkown接口,通过QueryInterface()方法可以得到其它接口的指针。
HRESULT r3;
r3=punk->QueryInterface(IID_IOPCServer,(void**)&m_pOPC);
这段程序的结果是得到一个指向服务器对象IOPCSever接口的指针(变量m_pOPC)。
第五步:创建OPC
IOPCServer接口的AddGroup()方法可以创建OPC组。
HRESULT r1;
r1=m_pOPC->AddGroup(szName,TRUE,500,&TimeBias,&PercDeadband,dwLCID,&m_GrpServerHandle,
&RevUpRate,IID_IOPCItemMgt,(LPUNKNOWN*)&m_pItemMgt);
这段程序的执行结果是创建一个有指定名称和属性的组。在返回的参数中,有一个指向所需要的进程组对象IOPCItemMgt接口的指针(变量m_pItemMgt)。
第六步:添加项
IOPCItemMgt接口的AddItems()方法可以添加OPC项。
HRESULT r1;
r1=m_pItemMgt->AddItems(NumItems,pItems,&m_pItResult,&pErrors);
这段程序的结果是添加具有特殊属性的指定数量的项。除此之外,事件结构变量m_pItResult(服务器句柄,目标系统上的项数据类型等)也被赋值。
第七步:用OPC项执行所需的操作
用于执行所需操作的指针需要通过现有的指向IOPCItemMgt接口的指针得到。如:如果用户要进行异步通信,就需要指向IOPCAsyncIO接口的指针。
HRESULT r1;
r1=m_pItemMgt->QueryInterface(IID_IOPCAsyncIO,(void**)&pAsyncIO);
通过该接口的Read()Write()两个方法,就可以读写项的数值。
HRESULT r2;

r2=pAsyncIO->Read(m_dwConnection,OPC_DS_CACHE,dwNumItems,phServer,&m_TransactionID,

&pErrors);
这段程序的执行结果是,OPC项的数据被送到客户程序的IAdviseSink接口。
HRESULT r3;
r3=pAsyncIO->Write((m_dwConnection,dwNumItems,phServer, pItemValues,&m_TransactionID,&pErrors);
这段程序的执行结果是,OPC服务器代替OPC客户刷新物理设备的数据。
第八步:删除对象,释放内存
在程序停止运行之前,必须删除已创建的OPC对象并释放内存。到目前为止,用到的接口都有相应的函数。
r1=m_pItemMgt->RemoveItems(dwNumItems,phServer,&pErrors);
r1=m_pOPC->RemoveGroup(m_GrpServerHandle,TRUE);
m_pItemMgt->Release();
m_pOPC->Release();

3.3  异步通信的说明

OPC客户和OPC服务器进行数据交换可以有两种不同的方式,即同步方式和异步方式。同步方式实现较为简单,当客户数目较少而且同服务器交互的数据量也比较少的时候可以采用这种方式;异步方式实现较为复杂,需要在客户程序中实现服务器回调函数。然而当有大量客户和大量数据交互时,异步方式的效率更高,能够避免客户数据请求的阻塞,并可以最大限度地节省CPU和网络资源。本文中用VC实现的OPC客户程序可以执行异步读写数据,异步意味着程序继续执行后面的操作,只要读或写的任务送达马上申请读写,并由OPC服务器返回回调函数的执行结果。为了实现异步通信,客户程序必须提供IAdviseSink接口与服务器方的IDataObject接口通信。下面详细描述一下用VC实现OPC客户应用程序中异步通讯的基本步骤。
第一步:得到指向IDataObject接口的指针
可以用这个接口在客户和服务器之间建立连接。为此目的,可以在程序中创建由IAdviseSink接口派生的COPCData类,并建立一个此类的实例m_pOPCIData,指向IDataObject接口的指针临时保存在这个类的m_pDataObject成员变量中。
m_pOPCIData=new COPCData(pWnd);
..........
r1=m_pItemMgt->QueryInterface(IID_IDataObject,(LPVOID*)&m_pOPCIData->m_pDataObject);
第二步:取得指向IAdviseSink接口的指针
客户必须能够和服务器建立联系,借此可以收到服务器的通知。IAdviseSink接口可以完成这个任务。通过QueryInterface()方法可以得到这个接口的指针并把它赋给变量pAdviseSink
m_pOPCIData-> QueryInterface(IID_IAdviseSink,(LPVOID*)&pAdviseSink);
第三步:建立连接
如果有关接口都存在,就可以建立连接。这可由IDataObject接口的DAdvise()方法完成,在这个方法中指向IAdviseSink接口的指针被传递给服务器。
r1=m_pOPCIData->m_pDataObject->DAdviseSink(&formatEtc,ADVF_PRIMEFIRST,pAdviseSink,
&m_dwConnection);
第四步:收到服务器的通知
如果数据发生变化,服务程序将调用IAdviseSink接口的OnDataChange()方法。这个方法是在客户应用程序中执行的。OPC项的实际数据被赋值给用户定义的成员变量m_ItemValues, 这就意味着在客户应用程序中可以获得这些想要的数据。
r2=VariantChangeType(&(g_pOPCServer->m_ItemValues[hItClient]),&Value,0,VT_BSTR);
第五步:显示读出的数据
在这里,OnDataChange()方法发送Windows消息WM_DATA_CHANGE启动下一步的处理。
SendMessage(*m_pWnd,WM_DATA_CHANGE,0,0);
通过在OPC客户应用程序的显示程序中定义消息映射,如果收到上述消息,则调用OnDataChange()事件过程。
ON_MESSAGE(WM_DATA_CHANGE,OnDataChange);
这个事件过程很简单,它只是调用用户自定义的ItemsView()函数在OPC客户应用程序的显示界面上显示实际的数据。
ItemView(g_pOPCServer->m_pItAttr,g_pOPCServer->m_ItemValues,4);

4 结束语

OPC规范把硬件供应商和应用软件开发者分离开来,使得双方的工作效率都有了很大提高。软件开发商无需了解硬件的实质和操作过程,只要遵循OPC规范进行开发,就能够访问OPC服务器中的数据。OPC十分适应过程控制的需要,开发商可用C++等高级语言编写软件程序,大大简化了过去从设备传输数据的复杂过程。本文介绍的在Visual C++环境下开发使用定制接口访问数据的OPC客户程序,能够发挥OPC服务器的最佳性能,完全可以满足过程控制领域对数据的实时、高效的要求。

参考文献:

[1] OPC Data Access Specification 1.0A[S].OPC Fundation1997.
[2] OPC Overview 1.0[S]. OPC Fundation1998.
[3] OPC Common Definations and Interfaces 1.0[S]. OPC Fundation1998.
[4] Dale Rogerson,杨秀章 江英译.COM技术内幕[M].北京:清华大学出版社,1999.
[5] 潘爱民. COM原理与应用[M].北京:清华大学出版社,1999.
[6] Michael J. Young .邱仲潘等译.Visual C++6从入门到精通[M].北京:电子工业出版社,1999.
[7] Al Chisholm. A Technical Overview of the OPC DadaAccess Interface[J]. Intellution Inc.1999.
[8] 马国华.监控组态软件及其应用[M].北京:清华大学出版社, 2001.

作者简介:石林锁(1959-),男,硕士,教授,主要从事自动控制、自动检测方面的教学和研究;王涛(1977-),男,硕士,研究生,主攻计算机应用;刘顺波(1963-),男,博士后,副教授,主要从事制冷空调装置自动化方面的教学和研究。

通讯地址:西安市第二炮兵工程学院502         邮编 710025
                                 涛(收)

E—mail: taotaowang0927@sina.com

电话:0293344102

- 作者: sunyes 2005年01月20日, 星期四 13:40  回复(0) |  引用(0) 加入博采

OPC规范
  OPC规范

传统的实时监控系统存在的问题

传统的实时监控系统作为支撑现代工业生产和社会生活的基础设施,得到了广泛的应用和发展。但过去,动用了当时最好的技术和最好的人才,可以开发出好的实时监控系统,但由于系统不具备开放性,各个部分的联系过于紧密,使系统过于复杂。这样系统的更新、扩展、升级变得非常困难。修改什么地方,怎样修改往往无从下手。同时当参与开发的技术人员可能因为其它原因而各奔东西,更加造成了系统得维护困难。而这种复杂得关系造成文档的难以形成。

传统的实时监控系统开发中出现的另一个主要问题是软件的重复开发,软件不能够重用,资源不能共享,造成大量人力与物力资源的浪费。随着计算机软件的发展,这种情况有所改观,高级语言中库函数的采用,实现了一定程度上资源的共享,尤其是面向对象的方法的应用,使得我们可以利用面向对象的继承等方法大量重用源代码。但这些重用只是对源代码级的重用而不是对可执行文件级的重用,对每一类库都要重新编译,所以并没有真正实现资源共享,并且对某个某个模块中某个类库的修改将"触一发而动全身",引起所有引用该类库的模块的修改,因此非常难以实现某个模块的升级。同时,为一种语言开发的类库以及函数库都不能够为其他语言所用,也大大限制了软件的重用。

一般实时监控系统为分布式的结构,实现了人机接口、通信、数据处理等功能在网络上的分布,同时将一个系统划分为各个子系统,降低了系统的复杂程度,改善了系统性能,便于整个系统的开发,减少了开发周期与维护费用。但由于系统各个计算机的通信协议依赖于某个厂家,没有形成统一的标准,不同厂家之间的软件与硬件的集成难于实现。因此也没有真正实现不同厂家的软件共享。

OPC规范简介为此,由

为此,由OPC Task Force制定的OPC(OLE for Process Control)规范于19968月正式诞生了,随着19972Microsoft公司推出Windows95支持的DCOM技术,19979月新成立的OPC FoundationOPC规范进行修改,增加了数据访问等一些标准,OPC规范得到了进一步的完善。

"OPC 基于Microsoft公司的 Distributed interNet Application (DNA) 构架和 Component Object Model (COM) 技术的,根据易于扩展性而设计的。OPC规范定义了一个工业标准接口,这个标准使得COM技术适用于过程控制和制造自动化等应用领域。"OPC 基础委员会主席Dave Rehbein是这样描述的

"OPC 基于Microsoft公司的 Distributed interNet Application (DNA) 构架和 Component Object Model (COM) 技术的,根据易于扩展性而设计的。OPC规范定义了一个工业标准接口,这个标准使得COM技术适用于过程控制和制造自动化等应用领域。"OPC 基础委员会主席Dave Rehbein是这样描述的OPC是以OLE/COM机制作为应用程序的通讯标准。OLE/COM是一种客户/服务器模式,具有语言无关性、代码重用性、易于集成性等优点。OPC规范了接口函数,不管现场设备以何种形式存在,客户都以统一的方式去访问,从而保证软件对客户的透明性,使得用户完全从低层的开发中脱离出来。

基于OPC的软件结构如图。

OPC的软件结构如图。

由图可见,应用程序与OPC服务器之间必须有OPC接口,OPC规范提供了两套标准接口:Custom标准接口,OLE自动化标准接口。通常在系统设计中采用OLE自动化标准接口。

OPC服务器之间必须有OPC接口,OPC规范提供了两套标准接口:Custom标准接口,OLE自动化标准接口。通常在系统设计中采用OLE自动化标准接口。

OLE自动化标准接口,及采用OLE自动化技术进行调用,其技术为上节所述的OLE自动化技术。OLE自动化标准接口定义了以下三层接口,依次呈包含关系。

OPC ServerOPC启动服务器,获得其他对象和服务的起始类,并用于返回OPC Group类对象;

OPC Group:存储由若干OPC Item组成的Group信息,并用于返回OPC Item类对象。

OPC Item:存储具体Item 的定义、数据值、状态值等信息。 由于OPC规范基于OLE/COM技术,同时OLE/COM的扩展远程OLE自动化与DCOM技术支持TCP/IP等多种网络协议,因此可以将OPC客户、服务器在物理上分开,分布于网络不同节点上。

OPC规范可以应用在许多应用程序中,如它们可以应用于从SCADA 或者DCS系统的物理设备中获取原始数据的最低层,它们同样可以应用于从SCADA 或者DCS系统中获取数据到应用程序中。实际上,OPC设计的目的就是从网络上某节点获取数据。图4.2.2OPC的客户/服务器关系图同样描述了OPCSCADA系统的应用。

采用OPC规范设计系统的好处在进行新型微机远动系统的研制中,各个计算机以及各个模块的数据交换应该按照

在进行新型微机远动系统的研制中,各个计算机以及各个模块的数据交换应该按照OPC规范进行。这样做有以下好处:

OPC规范以OLE/DCOM为技术基础,而OLE/DCOM支持TCP/IP等网络协议,因此可以将各个子系统从物理上分开,分布于网络的不同节点上。

OPC按照面向对象的原则,将一个应用程序(OPC服务器)作为一个对象封装起来,只将接口方法暴露在外面,客户以统一的方式去调用这个方法,从而保证软件对客户的透明性,使得用户完全从低层的开发中脱离出来。

OPC实现了远程调用,使得应用程序的分布与系统硬件的分布无关,便于系统硬件配置以及,使得系统的应用范围更广。

采用OPC规范,便于系统的组态化,将系统复杂性大大简化,可以大大缩短软件开发周期,提高软件运行的可靠性和稳定性,便于系统的升级与维护。

OPC规范,便于系统的组态化,将系统复杂性大大简化,可以大大缩短软件开发周期,提高软件运行的可靠性和稳定性,便于系统的升级与维护。

OPC规范了接口函数,不管现场设备以何种形式存在,客户都以统一的方式去访问,从而实现系统的开放性,易于实现与其它系统的接口。

OPC规范以OLE/DCOM为技术基础,而OLE/DCOM支持TCP/IP等网络协议,因此可以将各个子系统从物理上分开,分布于网络的不同节点上。

OPC按照面向对象的原则,将一个应用程序(OPC服务器)作为一个对象封装起来,只将接口方法暴露在外面,客户以统一的方式去调用这个方法,从而保证软件对客户的透明性,使得用户完全从低层的开发中脱离出来。

OPC实现了远程调用,使得应用程序的分布与系统硬件的分布无关,便于系统硬件配置以及,使得系统的应用范围更广。

采用OPC规范,便于系统的组态化,将系统复杂性大大简化,可以大大缩短软件开发周期,提高软件运行的可靠性和稳定性,便于系统的升级与维护。

OPC规范,便于系统的组态化,将系统复杂性大大简化,可以大大缩短软件开发周期,提高软件运行的可靠性和稳定性,便于系统的升级与维护。

OPC规范了接口函数,不管现场设备以何种形式存在,客户都以统一的方式去访问,从而实现系统的开放性,易于实现与其它系统的接口。

- 作者: sunyes 2005年01月20日, 星期四 11:09  回复(0) |  引用(0) 加入博采

OPC技术及其在工控组态软件中的应用
 [摘要]介绍了OPC技术及在工业控制软件中的具体应用。重点叙述了OPC技术原理和特点,OPC数据采集技术和OPC冗余技术。
 [关键词] OPC,OPC控件,OPC冗余服务器。
  1.引言
  OPC全称是OLE for Process Control,是过程控制业中的新兴标准,它的出现为基于Windows的应用程序和现场过程控制应用建立了桥梁。在过去,为了存取现场设备的数据信息,每一个应用软件开发商都需要编写专用的接口函数。由于现场设备的种类繁多,且产品的不断升级,往往给用户和软件开发商带来了巨大的工作负担。通常这样也不能满足工作的实际需要,系统集成商和开发商急切需要一种具有高效性、可靠性、开放性、可互操作性的即插即用的设备驱动程序。在这种情况下,OPC标准应运而生。OPC 以OLE/COM/DCOM机制作为应用程序级的通信标准,采用客户/服务器模式,把开发访问接口的任务放在硬件生产厂家或第三方厂家,以OPC服务器的形式提供给用户,解决了软、硬件厂商的矛盾,完成了系统的集成,提高了系统的开放性和可互操作性。
  2.OPC技术及接口
  OPC技术的实现包括两个组成部分,OPC服务器部分及OPC客户应用部分,其应用模式如图1所示。
  
  OPC服务器是一个典型的现场数据源程序,它收集现场设备数据信息,通过标准的OPC接口传送给OPC客户端应用。OPC客户应用是一个典型的数据接收程序,如人机界面软件(HMI)、数据采集与处理软件(SCADA)等。OPC客户应用通过OPC标准接口与OPC服务器通信,获取OPC服务器的各种信息。符合OPC标准的客户应用可以访问来自任何生产厂商的OPC服务器程序。
  OPC标准以微软公司的OLE技术为基础,它的制定是通过提供一套标准的OLE/COM接口完成的。在Windows 3.1下,微软公司通过OLE 1技术使相对独立的不同应用程序结合到一起成为可能,向用户软件模块化迈进了关键一步。在OPC技术中使用的是OLE 2技术,微软不仅对OLE 1进行了扩展,还引入了一种新的深层次结构,使得在微软Windows下开发软件的思维方式有所改变,OLE标准允许多台微机之间交换文档、图形等对象。COM是Component Object Model的缩写,是所有OLE机制的基础。COM是一种为了实现与编程语言无关的对象而制定的标准,该标准将Windows下的对象定义为独立单元,可不受程序限制地访问这些单元。这种标准可以使两个应用程序通过对象化接口通讯,而不需要知道对方是如何创建的。例如,用户可以使用C++语言创建一个Windows对象,它支持一个接口,通过该接口,用户可以访问该对象提供的各种功能,用户可以使用Visual Basic,C,Pascal,Smalltalk或其它语言编写对象访问程序。在Windows NT4.0操作系统下,COM规范扩展到可访问本机以外的其它对象,一个应用程序所使用的对象可分布在网络上,COM的这个扩展被称为DCOM(Distributed COM)。通过DCOM技术和OPC标准,完全可以创建一个开放的、可互操作的控制系统软件。很多国际先进的自动化软件供应商均以OPC技术作为核心,开发出了多种功能的自动化软件,如ICONICS、PCSOFT等公司。同时,具有OPC标准接口的自动化软件组件,遵守统一的数据访问标准,配置灵活,方便了软件集成,如ICONICS公司的具有软逻辑(Soft Logic)功能的ControlWorX32组件,通过OPC标准可以自由的集成在HMI/SCADA 软件中。
  OPC服务器通常支持两种类型的访问接口,它们分别为不同的编程语言环境提供访问机制。这两种接口是:自动化接口(Automation interface);自定义接口(Custom interface)。自动化接口通常是为基于脚本编程语言而定义的标准接口,可以使用VisualBasic、Delphi、PowerBuilder等编程语言开发OPC服务器的客户应用。而自定义接口是专门为C++等高级编程语言而制定的标准接口。OPC服务器的访问方式与接口如图2。
  
  
  
  3.OPC技术在过程控制中的应用
  OPC技术目前已经在国内开始使用,下面介绍OPC技术在控制应用软件开发中的典型应用。
  3.1 OPC数据采集技术
  OPC技术通常在数据采集软件中广泛使用。现在众多硬件厂商提供的产品均带有标准的OPC接口,可以编制符合标准OPC接口的客户端应用软件完成数据的采集任务。随着软件组件化的发展,过去提出的搭积木方式在工控软件中成为现实,用户可以通过各种软件模块完成控制应用的实现。因此,我们提出了创建OPC数据采集ActiveX控件方式完成OPC服务器的数据采集任务。通过OPC数据采集控件,用户可以方便的编写数据采集监控程序,同时也可以为原有的数据采集系统添加新的功能。OPC数据采集控件的想法来自于VisualBasic编程环境中的数据库(Data)控件,数据库控件的主要作用是连接各种数据库,为用户进行数据库编程提供方便。OPC数据采集控件提供类似数据库控件的功能,使用方式与数据库控件相仿,通过数据订阅和数据的动态绑定,为具有数据绑定功能的所有ActiveX控件提供数据源,用户甚至无须编程就可显示来自OPC服务器的实时数据。我们在实际应用中,开发了OPC数据采集控件,现将该控件的属性、方法、事件归纳如下:
  * OPC数据采集控件属性
  属性名称
  属性描述
  OPCItems
  连接OPC服务器中数据项的个数
  OPCItemID
  数据项的维一性编号。用于从数据项集合中选择某一数据项。
  OPCItemName
  OPC服务器中数据项的名称
  OPCServerName
  OPC服务器名称
  OPCRemoteNodeName
  OPC服务器所在的网络节点名称。如果该属性是空串,则表明OPC服务器是在本机上。
  OPCItemValue
  OPC数据项的当前值
  OPCItemQuality
  OPC数据项的当前值的质量
  OPCItemTimeStamp
  OPC数据项的当前值的时间戳
  UpdateRate
  OPC服务器数据的更新速率。单位是1/1000秒
  DeadBand
  用来设置参数的变化量,当变化量超过死区时,OPC服务器发出DataChange事件
  TimeBias
  OPC服务器的时间偏置
  LocaleID
  与OPC服务器进行通讯时所用的语言标识
  DisplayFullOPCItemName
  是否显示OPC数据项的全称。数据项的全称包括节点名、服务器名和数据项名
  OPCItemGain
  数据变换的增益。数据输出值=OPCItemGain*OPC数据原始值+OPCItemBias
  OPCItemBias
  数据变换的偏置。数据输出值=OPCItemGain*OPC数据原始值+OPCItemBias
  OPCItemDigits
  数据项的小数据点位数
  PrintConfiguration
  当该属性从False变成True时,自动打印出控件的配置参数
  
  * OPC数据采集控件方法
  方法名称
  方法属性
  Refresh()
  该方法用来强制更新当前数据
  WriteOPCItemValue(ByVal ItemID As Long, ByVal Value As Variant)
  该方法用来将编号为ItemID的数据项数值改写为Value,并返回该数据项的名称
  PrintItem()
  该方法用来打印控件的配置参数
  
  * OPC数据采集控件事件
  事件名称
  事件描述
  ItemIDChanged(ByVal ItemID As Long)
  当改变OPCItemID属性时,触发该事件。
  DataChange(ByVal NumItems As Long, ItemIDs() As Long, ItemValues() As Variant, Qualities() As Long, TimeStamps() As Date)
  当数据项集合中的一个或多个数据项取值发生变化时,触发该事件
  OneDataChange(ItemID As Long, ItemValue As Variant, Quality As Long, TimeStamp As Date)
  当数据项集合中的一个数据项取值发生变化时,触发该事件
  OPCServerShutdown()
  当OPC服务由于某种原因关闭时,触发该事件
  
  3.2 OPC服务器冗余技术
  在工控软件开发中,一项最为重要的技术就是冗余技术,优秀的软、硬件冗余技术是系统长期稳定工作的保障。目前流行的工控软件也都具有冗余功能。OPC标准的制定为软件冗余提出了新的思路,我们可以通过OPC技术更加方便的实现软件冗余。在实践应用中,我们开发了OPC冗余服务器,解决了对任何厂商的OPC服务器冗余问题。图 3是OPC冗余技术的结构图。
  
  OPC客户应用程序可以是任何符合OPC标准的客户端应用,如用户自己编写的采集监控程序或其他软件厂商开发的符合OPC标准的HMI、SCADA应用。OPC冗余服务器通过主/备份OPC服务器采集数据,同时通过标准的OPC接口为客户端应用提供数据信息。因此,OPC冗余服务器既是OPC服务器的客户端应用,同时又是符合OPC标准的服务器程序。由于OPC冗余服务器采用OPC标准,具有开放性和可互操作性,可以和任何符合OPC标准的软件无缝集成,真正作到了即插即用。OPC冗余服务器可以根据用户配置的检测时间定时检测OPC服务器的连接关系,在主从服务器之间自动切换,也可以按照用户指定的切换目标进行切换,方便了设备的维护,使系统的运行更加平稳。
  4.结束
   OPC标准的制定方便了控制系统的开发与集成。我们在实际应用中,采用OPC标准开发了奥康2000监控应用软件,同时开发了FF现场总线OPC服务器。通过OPC数据采集控件,连接了大量的OPC服务器程序,包括NI公司的FF现场总线OPC服务器、西门子公司的PROFIBUS总线OPC服务器及我们开发的OPC服务器,实现了控制系统的无逢集成。
   随着软件技术的不断发展,OPC标准也正在向新的领域扩展。OPC基金会现在已经颁布了数据访问和报警事件标准,其它的OPC标准,如历史数据OPC标准,也正在酝酿之中。去年在美国费城举行的ISA会议上,OPC基金会发布与微软公司BizTalk体系兼容的XML(Extensible Markup Laguage)的纲要,将Internet技术应用在工业控制中。相信在不久的将来,OPC技术及标准将应用于更加广泛的领域,OPC技术必将赋予现代工业控制软件更强的生命活力。
  参考文献
  [1] OPC基金会。OPC Data Access Custom Interface Standard ,Version 2.0。[M]
  [2] OPC基金会。OPC Data Access Automation Interface Standard ,Version 2.0。[M]
  [3] OPC基金会。OPC Overview,Version 1.0。[M]
  [

- 作者: sunyes 2005年01月20日, 星期四 10:39  回复(0) |  引用(0) 加入博采

WIN32内存管理笔记

转自csdn


       内存管理的概念
进程和内存空间
进程: 一旦程序正在运行,它就叫进程,进程拥有它自己的内存,文件句柄及其他系统资源. Windows任务栏显示的是主窗口而不是进程,单个进程可能有几个主窗口,每个窗口都由它自己的线程支持.

每个进程都具有它自己"私有"的4GB虚拟地址空间, 它包括:程序的EXE映像,所加载的任何非系统的DLL(包括MFC DLL),程序的全局数据,内丰映射文件等等.
Windows95的进程地址空间
在95中,只有地址空间最底部的2GB(0--0X7FFFFFFF)才是真正私有的,顶部的2GB对于所有的进程都是相同的,被所有的进程共享,它顶部的1GB包括Windows95内核,可执行程序,虚拟设备驱动程序(VxDs)和文件系统代码等,另外1GB存放Windows DLL, 内存映射文件.
Windows NT进程地址空间
NT进程只能访问其底部2GB地址空间,且其中最低的和最高的64KB不可访问.NT内核,执行程序及设备程序都驻留在顶部2GB之中.

虚拟内存的工作方式
一般分页存储,每一页为4KB,当使用一页时,占用物理内存,但物理地址你永远看不到,Intel微处理器可以有效地把一个32位虚拟地址映射为物理页以及在该页内的偏移量.每个进程都有它自己的分页表,芯片的CR3寄存器就保持指向当前运行的进程的目录页的指针,进程之间的切换只需要更新CR3即可.
当我们试图访问一个不在当前RAM之中的页,将触发一个中断,Windows通过检查,如果内存引用是假的,就会得到我们常见的"页面错误",程序退出.否则就把该页从磁盘文件读入RAM中.
内存分配函数
malloc

    在内存中分配一块指定大小的空间, 返回的类型为void *型, 可以强制转换为其他类型, 如果内存空间已经不足, 则会返回NULL. 注意: 实际分配的空间可能大于指定的大小, 因为内存块还需要保存队列或其他相关的信息.
free
    释放由malloc,calloc,realloc所分配的内存空间, 如果释放不是由这些函数所分配的内存空间会发生错误.
new
为变量初始化内存空间, 例如:
double *pdoub = new double(20.4);
delete
    与new对应,释放由new分配的变量所拥有的内存空间.
HeapAlloc, HeapFree
GlobalAlloc
    此函数从堆里面分配一个指定大小的内存空间, 此函数仅仅是为了与16位版本兼容而设.

    uFlags: 分配的内存属性, 它可以有以下几种属性值:
    如果此参数为0,缺省为GMEM_FIXED. 它分配一个固定的内存空间, 返回的值为一个指针
    GMEM_MOVEABLE分配一个活动的内存, 在WIN32里,内存块在物理内存里是从不移动的,但它们可以在缺省的堆里面移动. 此参数不能与GMEM_FIXED联合. 它的返回值是一个内存对象句柄,如果要把它转变为一个指针,需使用GlobalLock函数.
    GPTR与GMEM_FIXED和GMEM_ZEROINIT联合
    GHND与GMEM_MOVEABLE和GMEM_ZEROINIT联合
    GMEM_DDESHARE, GMEM_SHARE, 这两个属性首先是为了与16位兼容的,然后使用它可以提高应用程序执行DDE操作的效率,所以如果内存被用于DDE, 可以指定它.

    GMEM_DISCARDABLE, 被忽略,仅是为了与16位兼容.在WIN32里,你必须使用GlobalDiscard函数释放掉一个内存块.它不能与GMEM_FIXED联合.
    GMEM_LOWER,GMEM_NOCOMPACT,GMEM_NODISCARD,GMEM_NOT_BANKED,GMEM_NOTIFY 被忽略,仅为了与16位兼容
    GMEM_ZEROINIT, 将内存块初始化为0.
    
    dwBytes: 内存块大小
    如果置为0, 且uFlags置为GMEM_MOVEABLE的话, 函数将返回一个内存对话句柄, 此内存对象被标记为已丢弃.
    如果函数调用失败, 将返回NULL, 得到错误信息,调用GetLastError

    释放内存使用GlobalFree函数.
下面的代码示例使用一块内存:
HGLOBAL hMem;
char *pStartBuffer=NULL;
#define MAX_BUFFER_SIZE 102400
if ( (hMem = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, MAX_BUFFER_SIZE))==NULL ) 
return FALSE;
pStartBuffer = (char *)GlobalLock(hMem);
GlobalUnlock(hMem);
GlobalFree(hMem); 
内存映射文件
假定程序需要阅读一个DIB文件, 一种方法是分配大小合适的一个缓冲区,打开文件,然后调用一个读函数将整个磁盘文件复制进缓冲区; 还有一种更有效的方法, 就是将一个地址范围直接映射到该文件中即可, 当进程访问一个内存页的时候,Windows将分配RAM并从磁盘中读取数据. 代码如下:

HANDLE hFile = ::CreateFile(strPathname,GENERIC_READ,
FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
ASSERT(hFile!=NULL);
HANDLE hMap=
::CreateFileMapping(hFile,NULL,PAGE_READONLY,0,0,NULL);
ASSERT(hMap != NULL);
LPVOID lpvFile=::MapViewOfFile(hMap,FILE_MAP_READ,0,0,0);//map whole file
DWORD dwFileSize = ::GetFileSize(hFile,NULL);
// Use the file
::UnmapViewOfFile(lpvFile);
::CloseHandle(hMap);
::CloseHandle(hFile);
lpvFile是起始地址,hMap变量包含有文件映射对象的句柄,该对象可以在进程之间共享.

访问资源
资源包括在EXE文件和DLL文件里面,因此它们所占用的虚拟地址空间在进程生存期间不发生改变.例如: 需要访问一个位图:
LPVOID lpvResource=
(LPVOID)::LoadResource(NULL,::FindResource(NULL,MAKEINTRESOURCE(IDB_REDBLOCKS),RT_BITMAP));

- 作者: sunyes 2005年01月20日, 星期四 10:32  回复(0) |  引用(0) 加入博采

基于Visual C++6.0的DDL编程实现

  自从微软推出16位的Windows操作系统起,此后每种版本的Windows操作系统都非常依赖于动态链接库(DLL)中的函数和数据,实际上Windows操作系统中几乎所有的内容都由DLL以一种或另外一种形式代表着,例如显示的字体和图标存储在GDI DLL中、显示Windows桌面和处理用户的输入所需要的代码被存储在一个User DLL中、Windows编程所需要的大量的API函数也被包含在Kernel DLL中。

  在Windows操作系统中使用DLL有很多优点,最主要的一点是多个应用程序、甚至是不同语言编写的应用程序可以共享一个DLL文件,真正实现了资源"共享",大大缩小了应用程序的执行代码,更加有效的利用了内存;使用DLL的另一个优点是DLL文件作为一个单独的程序模块,封装性、独立性好,在软件需要升级的时候,开发人员只需要修改相应的DLL文件就可以了,而且,当DLL中的函数改变后,只要不是参数的改变,程序代码并不需要重新编译。这在编程时十分有用,大大提高了软件开发和维护的效率。

  既然DLL那么重要,所以搞清楚什么是DLL、如何在Windows操作系统中开发使用DLL是程序开发人员不得不解决的一个问题。本文针对这些问题,通过一个简单的例子,即在一个DLL中实现比较最大、最小整数这两个简单函数,全面地解析了在Visual C++编译环境下编程实现DLL的过程,文章中所用到的程序代码在Windows98系统、Visual C++6.0编译环境下通过。


       一、前言



  二、DLL的概念

  DLL是建立在客户/服务器通信的概念上,包含若干函数、类或资源的库文件,函数和数据被存储在一个DLL(服务器)上并由一个或多个客户导出而使用,这些客户可以是应用程序或者是其它的DLL。DLL库不同于静态库,在静态库情况下,函数和数据被编译进一个二进制文件(通常扩展名为*.LIB),Visual C++的编译器在处理程序代码时将从静态库中恢复这些函数和数据并把他们和应用程序中的其他模块组合在一起生成可执行文件。这个过程称为"静态链接",此时因为应用程序所需的全部内容都是从库中复制了出来,所以静态库本身并不需要与可执行文件一起发行。

  在动态库的情况下,有两个文件,一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL和.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。

  微软的Visual C++支持三种DLL,它们分别是Non-MFC Dll(非MFC动态库)、Regular Dll(常规DLL)、Extension Dll(扩展DLL)。Non-MFC DLL指的是不用MFC的类库结构,直接用C语言写的DLL,其导出的函数是标准的C接口,能被非MFC或MFC编写的应用程序所调用。Regular DLL:和下述的Extension Dlls一样,是用MFC类库编写的,它的一个明显的特点是在源文件里有一个继承CWinApp的类(注意:此类DLL虽然从CWinApp派生,但没有消息循环),被导出的函数是C函数、C++类或者C++成员函数(注意不要把术语C++类与MFC的微软基础C++类相混淆),调用常规DLL的应用程序不必是MFC应用程序,只要是能调用类C函数的应用程序就可以,它们可以是在Visual C++、Dephi、Visual Basic、Borland C等编译环境下利用DLL开发应用程序。

  常规DLL又可细分成静态链接到MFC和动态链接到MFC上的,这两种常规DLL的区别将在下面介绍。与常规DLL相比,使用扩展DLL用于导出增强MFC基础类的函数或子类,用这种类型的动态链接库,可以用来输出一个从MFC所继承下来的类。

  扩展DLL是使用MFC的动态链接版本所创建的,并且它只被用MFC类库所编写的应用程序所调用。例如你已经创建了一个从MFC的CtoolBar类的派生类用于创建一个新的工具栏,为了导出这个类,你必须把它放到一个MFC扩展的DLL中。扩展DLL 和常规DLL不一样,它没有一个从CWinApp继承而来的类的对象,所以,开发人员必须在DLL中的DllMain函数添加初始化代码和结束代码。

  三、动态链接库的创建

  在Visual C++6.0开发环境下,打开File\New\Project选项,可以选择Win32 Dynamic-Link Library或MFC AppWizard[dll]来以不同的方式来创建Non-MFC Dll、Regular Dll、Extension Dll等不同种类的动态链接库。

  1. Win32 Dynamic-Link Library方式创建Non-MFC DLL动态链接库

  每一个DLL必须有一个入口点,这就象我们用C编写的应用程序一样,必须有一个WINMAIN函数一样。在Non-MFC DLL中DllMain是一个缺省的入口函数,你不需要编写自己的DLL入口函数,用这个缺省的入口函数就能使动态链接库被调用时得到正确的初始化。如果应用程序的DLL需要分配额外的内存或资源时,或者说需要对每个进程或线程初始化和清除操作时,需要在相应的DLL工程的.CPP文件中对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为保留参数。到此为止,DLL的入口函数已经写了,剩下部分的实现也不难,你可以在DLL工程中加入你所想要输出的函数或变量了。

  我们已经知道DLL是包含若干个函数的库文件,应用程序使用DLL中的函数之前,应该先导出这些函数,以便供给应用程序使用。要导出这些函数有两种方法,一是在定义函数时使用导出关键字_declspec(dllexport),另外一种方法是在创建DLL文件时使用模块定义文件.Def。需要读者注意的是在使用第一种方法的时候,不能使用DEF文件。下面通过两个例子来说明如何使用这两种方法创建DLL文件。

  1)使用导出函数关键字_declspec(dllexport)创建MyDll.dll,该动态链接库中有两个函数,分别用来实现得到两个数的最大和最小数。在MyDll.h和MyDLL.cpp文件中分别输入如下原代码:


//MyDLL.h
extern "C" _declspec(dllexport) int Max(int a, int b);
extern "C" _declspec(dllexport) int Min(int a, int b);
//MyDll.cpp
#include
#include"MyDll.h"
int Max(int a, int b)
{
if(a>=b)return a;
else
return b;
}
int Min(int a, int b)
{
if(a>=b)return b;
else
return a;
}

  该动态链接库编译成功后,打开MyDll工程中的debug目录,可以看到MyDll.dll、MyDll.lib两个文件。LIB文件中包含DLL文件名和DLL文件中的函数名等,该LIB文件只是对应该DLL文件的"映像文件",与DLL文件中,LIB文件的长度要小的多,在进行隐式链接DLL时要用到它。读者可能已经注意到在MyDll.h中有关键字"extern C",它可以使其他编程语言访问你编写的DLL中的函数。

  2)用.def文件创建工程MyDll

  为了用.def文件创建DLL,请先删除上个例子创建的工程中的MyDll.h文件,保留MyDll.cpp并在该文件头删除#include MyDll.h语句,同时往该工程中加入一个文本文件,命名为MyDll.def,再在该文件中加入如下代码:

LIBRARY MyDll
EXPORTS
Max
Min

  其中LIBRARY语句说明该def文件是属于相应DLL的,EXPORTS语句下列出要导出的函数名称。我们可以在.def文件中的导出函数后加@n,如Max@1,Min@2,表示要导出的函数顺序号,在进行显式连时可以用到它。该DLL编译成功后,打开工程中的Debug目录,同样也会看到MyDll.dll和MyDll.lib文件。

  2.MFC AppWizard[dll]方式生成常规/扩展DLL

  在MFC AppWizard[dll]下生成DLL文件又有三种方式,在创建DLL是,要根据实际情况选择创建DLL的方式。一种是常规DLL静态链接到MFC,另一种是常规DLL动态链接到MFC。两者的区别是:前者使用的是MFC的静态链接库,生成的DLL文件长度大,一般不使用这种方式,后者使用MFC的动态链接库,生成的DLL文件长度小;动态链接到MFC的规则DLL所有输出的函数应该以如下语句开始:


AFX_MANAGE_STATE(AfxGetStaticModuleState( )) //此语句用来正确地切换MFC模块状态

  最后一种是MFC扩展DLL,这种DLL特点是用来建立MFC的派生类,Dll只被用MFC类库所编写的应用程序所调用。前面我们已经介绍过,Extension DLLs 和Regular DLLs不一样,它没有一个从CWinApp继承而来的类的对象,编译器默认了一个DLL入口函数DLLMain()作为对DLL的初始化,你可以在此函数中实现初始化,代码如下:


BOOL WINAPI APIENTRY DLLMain(HINSTANCE hinstDll,DWORD reason ,LPVOID flmpload)
{
switch(reason)
{
...............//初始化代码;
}
return true;
}

  参数hinstDll存放DLL的句柄,参数reason指明调用函数的原因,lpReserved是一个被系统所保留的参数。对于隐式链接是一个非零值,对于显式链接值是零。

  在MFC下建立DLL文件,会自动生成def文件框架,其它与建立传统的Non-MFC DLL没有什么区别,只要在相应的头文件写入关键字_declspec(dllexport)函数类型和函数名等,或在生成的def文件中EXPORTS下输入函数名就可以了。需要注意的是在向其它开发人员分发MFC扩展DLL 时,不要忘记提供描述DLL中类的头文件以及相应的.LIB文件和DLL本身,此后开发人员就能充分利用你开发的扩展DLL了。

  四、动态链接库DLL的链接

  应用程序使用DLL可以采用两种方式:一种是隐式链接,另一种是显式链接。在使用DLL之前首先要知道DLL中函数的结构信息。Visual C++6.0在VC\bin目录下提供了一个名为Dumpbin.exe的小程序,用它可以查看DLL文件中的函数结构。另外,Windows系统将遵循下面的搜索顺序来定位DLL: 1.包含EXE文件的目录,2.进程的当前工作目录, 3.Windows系统目录, 4.Windows目录,5.列在Path环境变量中的一系列目录。

  1.隐式链接

  隐式链接就是在程序开始执行时就将DLL文件加载到应用程序当中。实现隐式链接很容易,只要将导入函数关键字_declspec(dllimport)函数名等写到应用程序相应的头文件中就可以了。下面的例子通过隐式链接调用MyDll.dll库中的Min函数。首先生成一个项目为TestDll,在DllTest.h、DllTest.cpp文件中分别输入如下代码:


//Dlltest.h
#pragma comment(lib,"MyDll.lib")
extern "C"_declspec(dllimport) int Max(int a,int b);
extern "C"_declspec(dllimport) int Min(int a,int b);
//TestDll.cpp
#include
#include"Dlltest.h"
void main()
{int a;
a=min(8,10)
printf("比较的结果为%d\n",a);
}


  在创建DllTest.exe文件之前,要先将MyDll.dll和MyDll.lib拷贝到当前工程所在的目录下面,也可以拷贝到windows的System目录下。如果DLL使用的是def文件,要删除TestDll.h文件中关键字extern "C"。TestDll.h文件中的关键字Progam commit是要Visual C+的编译器在link时,链接到MyDll.lib文件,当然,开发人员也可以不使用#pragma comment(lib,"MyDll.lib")语句,而直接在工程的Setting->Link页的Object/Moduls栏填入MyDll.lib既可。

  2.显式链接

  显式链接是应用程序在执行过程中随时可以加载DLL文件,也可以随时卸载DLL文件,这是隐式链接所无法作到的,所以显式链接具有更好的灵活性,对于解释性语言更为合适。不过实现显式链接要麻烦一些。在应用程序中用LoadLibrary或MFC提供的AfxLoadLibrary显式的将自己所做的动态链接库调进来,动态链接库的文件名即是上述两个函数的参数,此后再用GetProcAddress()获取想要引入的函数。自此,你就可以象使用如同在应用程序自定义的函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxFreeLibrary释放动态链接库。下面是通过显式链接调用DLL中的Max函数的例子。


#include
#include
void main(void)
{
typedef int(*pMax)(int a,int b);
typedef int(*pMin)(int a,int b);
HINSTANCE hDLL;
PMax Max
HDLL=LoadLibrary("MyDll.dll");//加载动态链接库MyDll.dll文件;
Max=(pMax)GetProcAddress(hDLL,"Max");
A=Max(5,8);
Printf("比较的结果为%d\n",a);
FreeLibrary(hDLL);//卸载MyDll.dll文件;
}

  在上例中使用类型定义关键字typedef,定义指向和DLL中相同的函数原型指针,然后通过LoadLibray()将DLL加载到当前的应用程序中并返回当前DLL文件的句柄,然后通过GetProcAddress()函数获取导入到应用程序中的函数指针,函数调用完毕后,使用FreeLibrary()卸载DLL文件。在编译程序之前,首先要将DLL文件拷贝到工程所在的目录或Windows系统目录下。

  使用显式链接应用程序编译时不需要使用相应的Lib文件。另外,使用GetProcAddress()函数时,可以利用MAKEINTRESOURCE()函数直接使用DLL中函数出现的顺序号,如将GetProcAddress(hDLL,"Min")改为GetProcAddress(hDLL, MAKEINTRESOURCE(2))(函数Min()在DLL中的顺序号是2),这样调用DLL中的函数速度很快,但是要记住函数的使用序号,否则会发生错误。

  本文通过通俗易懂的方式,全面介绍了动态链接库的概念、动态链接库的创建和动态链接库的链接,并给出个简单明了的例子,相信读者看了本文后,能够创建自己的动态链接库并应用到后续的软件开发当中去了,当然,读者要熟练操作DLL,还需要在大量的实践中不断摸索,希望本文能起到抛砖引玉的作用。

- 作者: sunyes 2005年01月20日, 星期四 10:30  回复(2) |  引用(0) 加入博采

深入浅出HOOKS(上)

hook学习


HOOKS 说明书

hook是WINDOWS提供的一种消息处理机制,它使得程序员可以使用子过程来监视系统消息,并在消息到达目标过程前得到处理。

下面将介绍WINNDOWS HOOKS并且说明如何在WINDOWS 程序中使用它。

使用HOOK 将会降低系统效率,因为它增加了系统处量消息的工作量。建议在必要时才使用HOOK,并在消息处理完成后立即移去该HOOK。

HOOK链

WINDOWS提供了几种不同类型的HOOKS;不同的HOOK可以处理不同的消息。例如,WH_MOUSE HOOK用来监视鼠标消息。

WINDOWS为这几种HOOKS维护着各自的HOOK链。HOOK链是一个由应用程序定义的回调函数队列,当某种类型的消息发生时,WINDOWS向此种类型的HOOK链的第一个函数发送该消息,在第一函数处理完该消息后由该函数向链表中的下一个函数传递消息,依次向下。如果链中某个函数没有向下传送该消息,那么链表中后面的函数将得不到此消息。(对于某些类型的HOOK,不管HOOK链中的函数是否向下传递消息,与此类型HOOK联系的所有HOOK函数都会收到系统发送的消息)

 

===========================HOOK过程========================

为了拦截特定的消息,你可以使用SetWindowsHookEx函数在该类型的HOOK链中安装你自己的HOOK函数。该函数语法如下:

public function MyHook(nCode,wParam,iParam) as long

‘加入代码

end function

其中MyHook可以随便命名,其它不能变。该函数必须放在模块段。nCode指定HOOK类型。wParam,iParam的取值随nCode不同而不同,它代表了某种类型的HOOK的某个特定的动作。

SetWindowsHookEx总是将你的HOOK函数放置在HOOK链的顶端。你可以使用CallNextHookEx函数将系统消息传递给HOOK链中的下一个函数。

[注释]对于某些类型的HOOK,系统将向该类的所有HOOK函数发送消息,这时,HOOK函数中的CallNextHookEx语句将被忽略。 

全局HOOK函数可以拦截系统中所有线程的某个特定的消息(此时该HOOK函数必须放置在DLL中),局部HOOK函数可以拦截指定线程的某特定消息(此时该HOOK函数可以放置在DLL中,也可以放置在应用程序的模块段)。

[注释] 建议只在调试时使用全局HOOK函数。全局HOOK函数将降低系统效率,并且会同其它使用该类HOOK的应用程序产生冲突。

 

========================HOOK类型=================================

WH_CALLWNDPROC 和 WH_CALLWNDPROCRET HOOK

WH_CALLWNDPROC 和WH_CALLWNDPROCRET HOOK可以监视SendMessage发送的消息。系统在向窗体过程发送消息前,将调用WH_CALLWNDPROC;在窗体过程处理完该消息后系统将调用WH_CALLWNDPROCRET。

WH_CALLWNDPROCRET HOOK会向HOOK过程传送一个CWPRETSTRUCT结构的地址。该结构包含了窗体过程处理系统消息后的一些信息。

WH_CBT Hook

系统在激活,创建,消毁,最小化,最大化,移动,改变窗体前;在完成一条系统命令前;在从系统消息队列中移去鼠标或键盘事件前;在设置输入焦点前,或同步系统消息队列前,将调用WH_CBT HOOK。你可以在你的HOOK 过程拦截该类HOOK,并返回一个值,告诉系统,是否继续执行上面的操作。

WH_DEBUG HOOK

系统在调用与某种HOOK类型联系的HOOK过程前,将调用WH_DEBUG ,应用程序可以使用该HOOK决定是否让系统执行某种类型的HOOK。

WH_FOREGROUNDIDLE Hook

系统在空闲时调用该HOOK,在后台执行优先权较低的应用程序。

WH_GETMESSAGE Hook

WH_GETMESSAGE Hook使应用程序可以拦截GetMessage 或 PeekMessage的消息。应用程序使用WH_GETMESSAGE HOOK监视鼠标、键盘输入和发送到队列中的其它消息。

WH_JOURNALRECORD Hook

WH_JOURNALRECORD Hook使应用程序可以监视输入事件。典型地,应用程序使用该HOOK记录鼠标、键盘输入事件以供以后回放。该HOOK是全局HOOK,并且不能在指定线程中使用。

WH_JOURNALPLAYBACK Hook

` WH_JOURNALPLAYBACK Hook使应用程序可以向系统消息队列中插入消息。该HOOK可以回放以前由WH_JOURNALRECORD HOOK录制的鼠标、键盘输入事件。在WH_JOURNALPLAYBACK Hook安装到系统时,鼠标、键盘输入事件将被屏蔽。该HOOK同样是一个全局HOOK,不能在指定线程中使用。

WH_JOURNALPLAYBACK Hook返回一个时间暂停值,它告诉系统,在处理当前回放的消息时,系统等待百分之几秒。这使得此HOOK可以控制在回放时的时间事件。

WH_KEYBOARD Hook

WH_KEYBOARD Hook使应用程序可以监视由GetMessage和PeekMessage返回的WM_KEYDOWN 及WM_KEYUP消息。应用程序使用该HOOK监视发送到消息队列中的键盘输入。

WH_MOUSE Hook

WH_MOUSE Hook 使应用程序可以监视由GetMessage和PeekMessage返回的消息。应用程序使用该HOOK监视发送到消息队列中的鼠标输入。

WH_MSGFILTER and WH_SYSMSGFILTER Hooks

WH_MSGFILTER 和WH_SYSMSGFILTER Hooks使应用程序可以监视菜单、滚动条、消息框、对话框,当用户使用ALT+TAB或ALT+ESC来切换窗体时,该HOOK也可以拦截到消息。WH_MSGFILTER仅在应用程序内部监视菜单、滚动条、消息框、对话框,而WH_SYSMSGFILTER则可以在系统内监视所有应用程序的这些事件。

WH_SHELL Hook

一个SHELL程序可以使用WH_SHELL Hook来接收重要的信息。当一个SHELL程序被激活前或当前窗体被创建、消毁时,系统会调用WH_SHELL Hook过程。

 

=======================使用HOOK============================

安装、消毁HOOK过程

监视系统事件

 

安装、消毁HOOK过程

使用SetWindowsHookEx函数,指定一个HOOK类型,自己的HOOK过程是全局还是局部HOOK,同时给出HOOK过程的进入点,就可以轻松的安装你自己的HOOK过程。

为了安装一个全局HOOK过程,必须在应用程序外建立一个DLL,并将该HOOK函数封装到其中,应用程序在安装全局HOOK过程时必须先得到该DLL模块的句柄。将DLL名传递给LoadLibrary 函数,就会得到该DLL模块的句柄;得到该句柄 后,使用GetProcAddress函数可以得到HOOK过程的地址。最后,使用SetWindowsHookEx将HOOK过程的首址嵌入相应的HOOK链中,SetWindowsHookEx传递一个模块句柄,它为HOOK过程的进入点,线程标识符置为0,指出:该HOOK过程同系统中的所有线程关联。

以下是C写的例程,大家可以方便的转换为VB程序。

HOOKPROC hkprcSysMsg; 

static HINSTANCE hinstDLL; 

static HHOOK hhookSysMsg; 







 

hinstDLL = LoadLibrary((LPCTSTR) "c:\\windows\\sysmsg.dll"); 

hkprcSysMsg = (HOOKPROC)GetProcAddress(hinstDLL, "SysMessageProc"); 

hhookSysMsg = SetWindowsHookEx(WH_SYSMSGFILTER, 

hkprcSysMsg, hinstDLL, 0); 

 
Hook简介

2000-03-18· -·cww

 

  Hook这个东西有时令人又爱又怕,Hook是用来拦截系统某些信息之用,例如说,我们想 让系统不管在什麽地方只要按个Ctl-B便执行NotePad,或许您会使用Form的KeyPreview,设定为True,但在其他Process中按Ctl-B呢?那就没有用,这时就得设一个Keyboard Hook来拦截所有Key in的键;再如:MouseMove的Event只在该Form或Control上有效,如 果希望在Form的外面也能得知Mouse Move的信息,那只好使用Mouse Hook来栏截Mouse 的信息。再如:您想记录方才使用者的所有键盘动作或Mosue动作,以便录巨集,那就使用JournalRecordHook,如果想停止所有Mosue键盘的动作,而放(执行)巨集,那就使用JournalPlayBack Hook;Hook呢,可以是整个系统为范围(Remote Hook),即其他 Process的动作您也可以拦截,也可以是LocalHook,它的拦截范围只有Process本身。Remote Hook的Hook Function要在.Dll之中,Local Hook则在.Bas中。

在VB如何设定Hook呢?使用SetWindowsHookEx()

 

Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA"(ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long

 

 

idHook代表是何种Hook,有以下几种

Public Const WH_CALLWNDPROC = 4

Public Const WH_CALLWNDPROCRET = 12

Public Const WH_CBT = 5

Public Const WH_DEBUG = 9

Public Const WH_FOREGROUNDIDLE = 11

Public Const WH_GETMESSAGE = 3

Public Const WH_HARDWARE = 8

Public Const WH_JOURNALPLAYBACK = 1

Public Const WH_JOURNALRECORD = 0

Public Const WH_KEYBOARD = 2

Public Const WH_MOUSE = 7

Public Const WH_MSGFILTER = (-1)

Public Const WH_SHELL = 10

Public Const WH_SYSMSGFILTER = 6

 

lpfn代表Hook Function所在的Address,这是一个CallBack Fucnction,当挂上某个Hook时,我们便得定义一个Function来当作某个信息产生时,来处理它的Function,这个Hook Function有一定的参数格式

 

Private Function HookFunc(ByVal nCode As Long,ByVal wParam As Long, ByVal lParam As Long ) As Long

 

nCode 代表是什麽请况之下所产生的Hook,随Hook的不同而有不同组的可能值wParam lParam 传回值则随Hook的种类和nCode的值之不同而不同。

 

因这个参数是一个 Function的Address所以我们固定将Hook Function放在.Bas中,并以AddressOf HookFunc传入。至于Hook Function的名称我们可以任意给定,不一定叫HookFunc

 

hmod 代表.DLL的hInstance,如果是Local Hook,该值可以是Null(VB中可传0进去),而如果是Remote Hook,则可以使用GetModuleHandle(".dll名称")来传入。

 

dwThreadId 代表执行这个Hook的ThreadId,如果不设定是那个Thread来做,则传0(所以 一般来说,Remote Hook传0进去),而VB的Local Hook一般可传App.ThreadId进去

 

值回值 如果SetWindowsHookEx()成功,它会传回一个值,代表目前的Hook的Handle, 这个值要记录下来。

 

因为A程序可以有一个System Hook(Remote Hook),如KeyBoard Hook,而B程序也来设一 个Remote的KeyBoard Hook,那麽到底KeyBoard的信息谁所拦截?答案是,最後的那一个 所拦截,也就是说A先做keyboard Hook,而後B才做,那信息被B拦截,那A呢?就看B的 Hook Function如何做。如果B想让A的Hook Function也得这个信息,那B就得呼叫 CallNextHookEx()将这信息Pass给A,於是产生Hook的一个连线。如果B中不想Pass这信息给A,那就不要呼叫CallNextHookEx()。

 

Declare Function CallNextHookEx Lib "user32" Alias "CallNextHookEx" _

(ByVal hHook As Long, _

ByVal ncode As Long, _

ByVal wParam As Long, _

lParam As Any) As Long

 

hHook值是SetWindowsHookEx()的传回值,nCode, wParam, lParam则是Hook Procedure 中的三个参数。

 

最後是将这Hook去除掉,请呼叫UnHookWindowHookEx()

 

Declare Function UnhookWindowsHookEx Lib "user32" Alias "UnhookWindowsHookEx" _

(ByVal hHook As Long) As Long

 

hHook便是SetWindowsHookEx()的传回值。此时,以上例来说,B程序结束Hook,则换A可 以直接拦截信息。

 

 

KeyBoard Hook的范例

 

Hook Function的三个参数

 

 

nCode wParam lParam 传回值 

HC_ACTION或HC_NOREMOVE 表按键Virtual Key 与WM_KEYDOWN同 若信息要被处理传0 反之传1

 

 

Public hHook as Long

 

Public Sub UnHookKBD()

 

If hnexthookproc <> 0 Then

 

UnhookWindowsHookEx hHook

hHook = 0

 

End If

 

End Sub

 

 

Public Function EnableKBDHook()

 

If hHook <> 0 Then Exit Function

hhook = SetWindowsHookEx(WH_KEYBOARD, AddressOf MyKBHFunc, App.hInstance, App.ThreadId)

 

End Function

 

Public Function MyKBHFunc(ByVal iCode As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

 

MyKBHfunc = 0 '表示要处理这个信息

 

If wParam = vbKeySnapshot Then '侦测 有没有按到PrintScreen键

 

MyKBHFunc = 1 '在这个Hook便吃掉这个信息

 

End If

Call CallNextHookEx(hHook, iCode, wParam, lParam) '传给下一个Hook

 

End Function

 

  至於其他的 Hook的详细资料与nCode,wParam, lParam的意义,请查Win32 Help

                  如何获得密码窗口的内容------揭开Hook的面纱 

 

                                          Pan Ying(zero world)

 

  现在的不少程序都有取得密码窗口的内容的功能,你有没有想过是如何做到这一切的呢?我有一个同学就问过我这样一个问题,当时我也回答不出来,现在我研究了视窗的Hook函数之后总算有了一定的了解。下面就有我来揭开Hook函数的神秘面纱。 

 
  先来介绍Hook的含义: 

  Hook是指在程序正常运行中接受信息之前预先启动的函数,用来检查和修改传给该程序的信息。举例来说,当你在一个窗口上点击鼠标以后,首先接收信息的是相应的Hook函数,然后才传到相应的应用程序。 

 
  如何定义Hook函数: 

SetWindowsHookEx: 

  用来装入新的Hook函数,其参数列表为下: 

int idHook:装入Hook类型,有WH_MOUSE 等。 

HOOKPROC lpfn:要装入的Hook函数地址。 

HINSTANCE hMod:函数所在的进程,如果为全局Hook函数该函数必须在Dll中。 

DWORD dwThreadId:装入哪个进程,如果为全局,则为0。 

  返回相应Hook标识。 

HookProc: 

  您自定义的Hook函数,应具有的参数如下: 

int nCode:传入的信息类型。 

WPARAM wParam:短整型参数。 

LPARAM lParam:长整型参数。 

  要在函数中调用CallNextHookEx把信息传给下一个Hook函数。 

CallNextHookEx: 

  用于将信息传给下一个Hook函数,需要将得到的Hook标识和相应参数传入。 

UnhookWindowsHookEx: 

  用于将装入的Hook函数从Hook链表中剔除,应传入得到的Hook标识。 

下面我们来看一个例子: 

  例子原码的下载:HookTest.zip 本程序在Window98和Delphi5下通过。 

 一、调用LoadLibrary和GetProcAddress取得函数地址。 

  hinstDLL := LoadLibrary(LPCTSTR( 'hooktest.dll')); 

  @hkprcSysMsg:=GetProcAddress(hinstDLL, 'MouseProc'); 

 

二、用SetWindowsHookEx将得到的函数装入。 

  hhk:= SetWindowsHookEx(WH_MOUSE,@hkprcSysMsg,hinstDLL, 0); 

 

三、Windows会将函数装入到每一个进程中。 

 

四、每当鼠标点击,自定义的Hook函数会将点击的窗口标题传给程序的标题。 

if (nCode=HC_ACTION)and(WPARAM=WM_LBUTTONDOWN) 

 then 

  begin 

   MyMouse:=CPointer(lPARAM); 

   MyHandle2:=MyMouse^.hwnd; 

   GetMem(MyString,GetWindowTextlength(myhandle2)+1); 

   GetWindowText(MyHandle2,MyString,GetWindowTextlength(myhandle2)+1); 

   TempHandle:=myhandle2; 

   while (TempHandle<>0) do 

   begin 

    myHandle2:=TempHandle; 

    TempHandle:=GetParent(TempHandle); 

   end; 

   if (myhandle2<>0) 

   then 

    SetWindowText(myhandle2,MyString); 

   FreeMem(MyString); 

  end 

就这样完成了得到密码窗口内容的功能。 



关于 HOOK 
[ 作者: 陆麟   添加时间: 2001-6-2 0:04:44 ]
来源:lu0.126.com

大家讨论HOOK太多了.在网络中,概凡谈论到进程控制,十有八九最后会得到一句话:写HOOK. 
嘿嘿,知道HOOK运作机理的有几个呢?下面,本人就MSDN文档中没有写到的一点东西稍微讲几句. 

以下讲述乃针对全局HOOK而发. 

1.设置HOOK过程时返回的前一HOOK地址必须被保存到一个全局共享的内存地址段中.这个共享段的地址不是什么本进程的全局变量,而是所有进程都可以看见的变量.因为,进程级变量进在本进程内可见.当其他进程加载HOOK DLL时,HOOK DLL里的所有变量都会被RESET.这也就是说: 

HHOOK hk; 

//set and get HHOOK here 

return hk(); 

这样的描述是不能跳转到前一HOOK的.这一点,甚至在Jeffrey Richter的经典书籍<>里都描述错了. 

正确的做法是: 

#pragma data_seg("dt") 

HHOOK prehook=0; 

#pragma data_seg() 

然后到VC的LINK OPITION里加上: 

-SECTION:dt,RWS 

这样,prehook就被搞到系统中被共享了.记住,一定要给prehook初始化.否则,MS编译器的编译器会LINK错误. 

 

2.只有使用USER32.DLL的进程才会被INJECT.所以,HOOK并不是万能的.而且,用了USER32.DLL,也不一定会被INJECT.这里有个很好的例子就是整个OS启动中第一个被启动的WIN32进程:KERNEL32.DLL.大家很奇怪,KERNEL32.DLL是个DLL,怎么也会被作为进程加载?但是事实的确是这样的,顺便给大家再上一节98启动课吧.KERNEL32.DLL作为一个独立的进程,启动时加载了MSGSRV32.EXE.而MSGSRV32.EXE又启动了SPOOL32.EXE, SPOOL32.EXE启动了MPREXE.EXE.MPREXE.EXE可不能小看.我敢担保全中国没几个人真正知道它的作用的.MPREXE.EXE不仅是网络客户端部件启动的核心,更是WIN98的SERVICE的SCM.所有的WIN98的SERVICE都是由MPREXE.EXE启动的.WIN98的SERVICE都在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices里呆者哩.大家都傻眼了吧.:DDD 有一点很令人奇怪, 那就是如果MPREXE.EXE运作不正常,那么SHELL绝对起不来.SHELL却是有MSGSRV32.EXE启动的.看来MPREXE和MSGSRV32有一套内部沟通机制啊.有了SHELL,就什么都有了.其他的东西被SHELL启动就难说准了.反正80%的程序是由SHELL启动的.好了,WIN98启动暂且讲到这里.我们继续原先的话题.KERNEL32.DLL居然就无法用HOOK入侵.大家如果不信的话,就试试看吧. 

3.尽管使用USER32.DLL的进程会被INJECT. 但是这里还有一个技巧,那就是HOOK DLL是在进程第一次发出USER32调用的时候才被加载. 大家又目瞪口呆了吧.:) 这也就是说,在你发出USER32调用之前, HOOK DLL根本拿你没办法. 哇,真够幽默啊.:)))由于在启动HOOK前的进程绝对都是在调用GetMessage(...)/PeekMessage(...)中,那么HOOK DLL一下子满足了加载条件了. 

 
4.加载HOOK时,HMODULE一定要正确,否则,系统就无法正确加载HOOK过程.千万不要用0代替HMODULE,因为0代表的是EXE映象的HINSTANCE(其实就是HMODULE). 

好了.今天就写到这里.此文该算本主页里又一篇经典了吧.:) 

- 作者: sunyes 2005年01月20日, 星期四 10:27  回复(1) |  引用(0) 加入博采

Hook学习笔记

关键字     Hook、钩子、VC++、DLL 


一、基本概念:

    钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。

    钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。

二、运行机制:

1、钩子链表和钩子子程:

    每一个Hook都有一个与之相关联的指针列表,称之为钩子链表,由系统来维护。这个列表的指针指向指定的,应用程序定义的,被Hook子程调用的回调函数,也就是该钩子的各个处理子程。当与指定的Hook类型关联的消息发生时,系统就把这个消息传递到Hook子程。一些Hook子程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个Hook子程或者目的窗口。最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。

 Windows 并不要求钩子子程的卸载顺序一定得和安装顺序相反。每当有一个钩子被卸载,Windows 便释放其占用的内存,并更新整个Hook链表。如果程序安装了钩子,但是在尚未卸载钩子之前就结束了,那么系统会自动为它做卸载钩子的操作。

    钩子子程是一个应用程序定义的回调函数(CALLBACK Function),不能定义成某个类的成员函数,只能定义为普通的C函数。用以监视系统或某一特定类型的事件,这些事件可以是与某一特定线程关联的,也可以是系统中所有线程的事件。

    钩子子程必须按照以下的语法:
    LRESULT CALLBACK HookProc
 (
   int nCode, 
      WPARAM wParam, 
      LPARAM lParam
     );
HookProc是应用程序定义的名字。

nCode参数是Hook代码,Hook子程使用这个参数来确定任务。这个参数的值依赖于Hook类型,每一种Hook都有自己的Hook代码特征字符集。
wParam和lParam参数的值依赖于Hook代码,但是它们的典型值是包含了关于发送或者接收消息的信息。

2、钩子的安装与释放:

    使用API函数SetWindowsHookEx()把一个应用程序定义的钩子子程安装到钩子链表中。SetWindowsHookEx函数总是在Hook链的开头安装Hook子程。当指定类型的Hook监视的事件发生时,系统就调用与这个Hook关联的Hook链的开头的Hook子程。每一个Hook链中的Hook子程都决定是否把这个事件传递到下一个Hook子程。Hook子程传递事件到下一个Hook子程需要调用CallNextHookEx函数。
    
HHOOK SetWindowsHookEx( 
     int idHook,      // 钩子的类型,即它处理的消息类型
     HOOKPROC lpfn,   // 钩子子程的地址指针。如果dwThreadId参数为0
      // 或是一个由别的进程创建的线程的标识,
      // lpfn必须指向DLL中的钩子子程。
      // 除此以外,lpfn可以指向当前进程的一段钩子子程代码。
      // 钩子函数的入口地址,当钩子钩到任何消息后便调用这个函数。
     HINSTANCE hMod,  // 应用程序实例的句柄。标识包含lpfn所指的子程的DLL。
      // 如果dwThreadId 标识当前进程创建的一个线程,
      // 而且子程代码位于当前进程,hMod必须为NULL。
      // 可以很简单的设定其为本应用程序的实例句柄。
     DWORD dwThreadId // 与安装的钩子子程相关联的线程的标识符。
      // 如果为0,钩子子程与所有的线程关联,即为全局钩子。
                 ); 
  函数成功则返回钩子子程的句柄,失败返回NULL。

  以上所说的钩子子程与线程相关联是指在一钩子链表中发给该线程的消息同时发送给钩子子程,且被钩子子程先处理。

    在钩子子程中调用得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续传递,那么它必须调用另外一个SDK中的API函数CallNextHookEx来传递它,以执行钩子链表所指的下一个钩子子程。这个函数成功时返回钩子链中下一个钩子过程的返回值,返回值的类型依赖于钩子的类型。这个函数的原型如下:

LRESULT CallNextHookEx
   (
    HHOOK hhk;
    int nCode;
    WPARAM wParam;
    LPARAM lParam;
    );
   
hhk为当前钩子的句柄,由SetWindowsHookEx()函数返回。
NCode为传给钩子过程的事件代码。
wParam和lParam 分别是传给钩子子程的wParam值,其具体含义与钩子类型有关。
  
    钩子函数也可以通过直接返回TRUE来丢弃该消息,并阻止该消息的传递。否则的话,其他安装了钩子的应用程序将不会接收到钩子的通知而且还有可能产生不正确的结果。

    钩子在使用完之后需要用UnHookWindowsHookEx()卸载,否则会造成麻烦。释放钩子比较简单,UnHookWindowsHookEx()只有一个参数。函数原型如下:

UnHookWindowsHookEx
  (
  HHOOK hhk;
  );
函数成功返回TRUE,否则返回FALSE。

3、一些运行机制:

    在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的;而在Win32环境中,情况却发生了变化,DLL函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有。当进程在载入DLL时,操作系统自动把DLL地址映射到该进程的私有空间,也就是进程的虚拟地址空间,而且也复制该DLL的全局数据的一份拷贝到该进程空间。也就是说每个进程所拥有的相同的DLL的全局数据,它们的名称相同,但其值却并不一定是相同的,而且是互不干涉的。
 
 因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。在访问同一个Dll的各进程之间共享存储器是通过存储器映射文件技术实现的。也可以把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。必须给这些变量赋初值,否则编译器会把没有赋初始值的变量放在一个叫未被初始化的数据段中。

 #pragma data_seg预处理指令用于设置共享数据段。例如:
#pragma data_seg("SharedDataName")
HHOOK hHook=NULL;
#pragma data_seg()
 在#pragma data_seg("SharedDataName")和#pragma data_seg()之间的所有变量 将被访问该Dll的所有进程看到和共享。

    当进程隐式或显式调用一个动态库里的函数时,系统都要把这个动态库映射到这个进程的虚拟地址空间里(以下简称"地址空间")。这使得DLL成为进程的一部分,以这个进程的身份执行,使用这个进程的堆栈。

4、系统钩子与线程钩子:

    SetWindowsHookEx()函数的最后一个参数决定了此钩子是系统钩子还是线程钩子。
    
    线程勾子用于监视指定线程的事件消息。线程勾子一般在当前线程或者当前线程派生的线程内。
    
    系统勾子监视系统中的所有线程的事件消息。因为系统勾子会影响系统中所有的应用程序,所以勾子函数必须放在独立的动态链接库(DLL) 中。系统自动将包含"钩子回调函数"的DLL映射到受钩子函数影响的所有进程的地址空间中,即将这个DLL注入了那些进程。

几点说明:
 (1)如果对于同一事件(如鼠标消息)既安装了线程勾子又安装了系统勾子,那么系统会自动先调用线程勾子,然后调用系统勾子。 

 (2)对同一事件消息可安装多个勾子处理过程,这些勾子处理过程形成了勾子链。当前勾子处理结束后应把勾子信息传递给下一个勾子函数。 

 (3)勾子特别是系统勾子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装勾子,在使用完毕后要及时卸载。

三、钩子类型

    每一种类型的Hook可以使应用程序能够监视不同类型的系统消息处理机制。下面描述所有可以利用的Hook类型。

1、WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks

    WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks使你可以监视发送到窗口过程的消息。系统在消息发送到接收窗口过程之前调用WH_CALLWNDPROC Hook子程,并且在窗口过程处理完消息之后调用WH_CALLWNDPROCRET Hook子程。

    WH_CALLWNDPROCRET Hook传递指针到CWPRETSTRUCT结构,再传递到Hook子程。CWPRETSTRUCT结构包含了来自处理消息的窗口过程的返回值,同样也包括了与这个消息关联的消息参数。

2、WH_CBT Hook

    在以下事件之前,系统都会调用WH_CBT Hook子程,这些事件包括:
    1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件;
    2. 完成系统指令;
    3. 来自系统消息队列中的移动鼠标,键盘事件;
    4. 设置输入焦点事件;
    5. 同步系统消息队列事件。
    
    Hook子程的返回值确定系统是否允许或者防止这些操作中的一个。

3、WH_DEBUG Hook

    在系统调用系统中与其他Hook关联的Hook子程之前,系统会调用WH_DEBUG Hook子程。你可以使用这个Hook来决定是否允许系统调用与其他Hook关联的Hook子程。

4、WH_FOREGROUNDIDLE Hook

    当应用程序的前台线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE Hook执行低优先级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就会调用WH_FOREGROUNDIDLE Hook子程。

5、WH_GETMESSAGE Hook

    应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函数返回的消息。你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及其他发送到消息队列中的消息。

6、WH_JOURNALPLAYBACK Hook

    WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可以使用这个Hook回放通过使用WH_JOURNALRECORD Hook记录下来的连续的鼠标和键盘事件。只要WH_JOURNALPLAYBACK Hook已经安装,正常的鼠标和键盘事件就是无效的。WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定Hook一样使用。WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实时事件的回放。WH_JOURNALPLAYBACK是system-wide local hooks,它們不會被注射到任何行程位址空間。

7、WH_JOURNALRECORD Hook

    WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这个Hook记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook来回放。WH_JOURNALRECORD Hook是全局Hook,它不能象线程特定Hook一样使用。WH_JOURNALRECORD是system-wide local hooks,它們不會被注射到任何行程位址空間。

8、WH_KEYBOARD Hook

    在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and WM_KEYUP消息,这些消息通过GetMessage or PeekMessage function返回。可以使用这个Hook来监视输入到消息队列中的键盘消息。

9、WH_KEYBOARD_LL Hook

    WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。

10、WH_MOUSE Hook

    WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。使用这个Hook监视输入到消息队列中的鼠标消息。

11、WH_MOUSE_LL Hook

    WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。

12、WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks

    WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通过安装了Hook子程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook监视所有应用程序消息。
    
    WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间过滤消息,这等价于在主消息循环中过滤消息。
    
通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循环里一样。

13、WH_SHELL Hook

    外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook子程。
  WH_SHELL 共有5钟情況:
1. 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁;
2. 当Taskbar需要重画某个按钮;
3. 当系统需要显示关于Taskbar的一个程序的最小化形式;
4. 当目前的键盘布局状态改变;
5. 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)。

    按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接收WH_SHELL消息之前,应用程序必须调用SystemParametersInfo function注册它自己。

- 作者: sunyes 2005年01月20日, 星期四 10:24  回复(0) |  引用(0) 加入博采

网管的常用命令!

网络管理经常用到的命令


如果你玩过路由器的话,就知道路由器里面那些很好玩的命令缩写。
例如,"sh int" 的意思是 "show interface"。
现在 Windows 2000 也有了类似界面的工具,叫做 netsh。
我们在 Windows 2000 的 cmd shell 下,输入 netsh
就出来:netsh> 提示符,
输入 int ip 就显示:
interface ip>
然后输入 dump ,我们就可以看到当前系统的网络配置:

# ----------------------------------
# Interface IP Configuration
# ----------------------------------
pushd interface ip


# Interface IP Configuration for "Local Area Connection"

set address name = "Local Area Connection" source = static addr = 192.168.1.168
mask = 255.255.255.0
add address name = "Local Area Connection" addr = 192.1.1.111 mask = 255.255.255.0
set address name = "Local Area Connection" gateway = 192.168.1.100 gwmetric = 1
set dns name = "Local Area Connection" source = static addr = 202.96.209.5
set wins name = "Local Area Connection" source = static addr = none


popd
# End of interface IP configuration

上面介绍的是通过交互方式操作的一种办法。
我们可以直接输入命令:
"netsh interface ip add address "Local Area Connection" 10.0.0.2
255.0.0.0"
来添加 IP 地址。

如果不知道语法,不要紧的哦!
在提示符下,输入 ? 就可以找到答案了。方便不方便啊?
原来微软的东西里面,也有那么一些让人喜欢的玩意儿。可惜,之至者甚少啊!


Windows网络命令行程序
这部分包括:

使用 ipconfig /all 查看配置
使用 ipconfig /renew 刷新配置
使用 ipconfig 管理 DNS 和 DHCP 类别 ID
使用 Ping 测试连接
使用 Arp 解决硬件地址问题
使用 nbtstat 解决 NetBIOS 名称问题
使用 netstat 显示连接统计
使用 tracert 跟踪网络连接
使用 pathping 测试路由器
使用 ipconfig /all 查看配置
发现和解决 TCP/IP 网络问题时,先检查出现问题的计算机上的 TCP/IP 配置。可以
使用 ipconfig 命令获得主机配置信息,包括 IP 地址、子网掩码和默认网关。

注意

对于 Windows 95 和 Windows 98 的客户机,请使用 winipcfg 命令而不是 ipconfi
g 命令。
使用带 /all 选项的 ipconfig 命令时,将给出所有接口的详细配置报告,包括任何
已配置的串行端口。使用 ipconfig /all,可以将命令输出重定向到某个文件,并将
输出粘贴到其他文档中。也可以用该输出确认网络上每台计算机的 TCP/IP 配置,或
者进一步调查 TCP/IP 网络问题。

例如,如果计算机配置的 IP 地址与现有的 IP 地址重复,则子网掩码显示为 0.0.0
.0。

下面的范例是 ipconfig /all 命令输出,该计算机配置成使用 DHCP 服务器动态配置
TCP/IP,并使用 WINS 和 DNS 服务器解析名称。

Windows 2000 IP Configuration

Node Type.. . . . . . . . : Hybrid
IP Routing Enabled.. . . . : No
WINS Proxy Enabled.. . . . : No

Ethernet adapter Local Area Connection:

Host Name.. . . . . . . . : corp1.microsoft.com
DNS Servers . . . . . . . : 10.1.0.200
Description. . . . . . . : 3Com 3C90x Ethernet Adapter
Physical Address. . . . . : 00-60-08-3E-46-07
DHCP Enabled.. . . . . . . : Yes
Autoconfiguration Enabled.: Yes
IP Address. . . . . . . . . : 192.168.0.112
Subnet Mask. . . . . . . . : 255.255.0.0
Default Gateway. . . . . . : 192.168.0.1
DHCP Server. . . . . . . . : 10.1.0.50
Primary WINS Server. . . . : 10.1.0.101
Secondary WINS Server. . . : 10.1.0.102
Lease Obtained.. . . . . . : Wednesday, September 02, 1998 10:32:13 AM
Lease Expires.. . . . . . : Friday, September 18, 1998 10:32:13 AM


如果 TCP/IP 配置没有问题,下一步测试能够连接到 TCP/IP 网络上的其他主机。

使用 ipconfig /renew 刷新配置
解决 TCP/IP 网络问题时,先检查遇到问题的计算机上的 TCP/IP 配置。如果计算机
启用 DHCP 并使用 DHCP 服务器获得配置,请使用 ipconfig /renew 命令开始刷新租
约。

使用 ipconfig /renew 时,使用 DHCP 的计算机上的所有网卡(除了那些手动配置的
适配器)都尽量连接到 DHCP 服务器,更新现有配置或者获得新配置。

也可以使用带 /release 选项的 ipconfig 命令立即释放主机的当前 DHCP 配置。有
关 DHCP 和租用过程的详细信息,请参阅客户机如何获得配置。

注意

对于启用 DHCP 的 Windows 95 和 Windows 98 客户,请使用 winipcfg 命令的 rel
ease 和 renew 选项,而不是 ipconfig /release 和 ipconfig /renew 命令,手动
释放或更新客户的 IP 配置租约。
使用 ipconfig 管理 DNS 和 DHCP 类别 ID
也可以使用 ipconfig 命令:

显示或重置 DNS 缓存。
详细信息,请参阅使用 ipconfig 查看或重置客户解析程序缓存。

刷新已注册的 DNS 名称。
详细信息,请参阅使用 ipconfig 更新 DNS 客户注册。

显示适配器的 DHCP 类别 ID。
详细信息,请参阅显示客户机上的 DHCP 类别 ID 信息。

设置适配器的 DHCP 类别 ID。
详细信息,请参阅设置客户机上的 DHCP 类别 ID 信息。

使用 Ping 测试连接
Ping 命令有助于验证 IP 级的连通性。发现和解决问题时,可以使用 Ping 向目标主
机名或 IP 地址发送 ICMP 回应请求。需要验证主机能否连接到 TCP/IP 网络和网络
资源时,请使用 Ping。也可以使用 Ping 隔离网络硬件问题和不兼容配置。

通常最好先用 Ping 命令验证本地计算机和网络主机之间的路由是否存在,以及要连
接的网络主机的 IP 地址。Ping 目标主机的 IP 地址看它是否响应,如下:

ping IP_address

使用 Ping 时应该执行以下步骤:

Ping 环回地址验证是否在本地计算机上安装 TCP/IP 以及配置是否正确。
ping 127.0.0.1

Ping 本地计算机的 IP 地址验证是否正确地添加到网络。
ping IP_address_of_local_host

Ping 默认网关的 IP 地址验证默认网关是否运行以及能否与本地网络上的本地主机通
讯。
ping IP_address_of_default_gateway

Ping 远程主机的 IP 地址验证能否通过路由器通讯。
ping IP_address_of_remote_host

Ping 命令用 Windows 套接字样式的名称解析将计算机名解析成 IP 地址,所以如果
用地址成功,但是用名称 Ping 失败,则问题出在地址或名称解析上,而不是网络连
通性的问题。详细信息,请参阅使用 Arp 解决硬件地址问题。

如果在任何点上都无法成功地使用 Ping,请确认:

安装和配置 TCP/IP 之后重新启动计算机。
"Internet 协议 (TCP/IP) 属性"对话框"常规"选项卡上的本地计算机的 IP 地址
有效而且正确。
启用 IP 路由,并且路由器之间的链路是可用的。
您可以使用 Ping 命令的不同选项来指定要使用的数据包大小、要发送多少数据包、
是否记录用过的路由、要使用的生存时间 (TTL) 值以及是否设置"不分段"标志。可
以键入 ping -? 查看这些选项。

下例说明如何向 IP 地址 172.16.48.10 发送两个 Ping,每个都是 1,450 字节:

C:\>ping -n 2 -l 1450 172.16.48.10
Pinging 172.16.48.10 with 1450 bytes of data:

Reply from 172.16.48.10:bytes=1450 time<10ms TTL=32
Reply from 172.16.48.10:bytes=1450 time<10ms TTL=32

Ping statistics for 157.59.8.1:
Packets:Sent = 2, Received = 2, Lost = 0 (0% loss),
Approximate roundtrip times in milli-seconds:
Minimum = 0ms, Maximum = 10ms, Average = 2ms
默认情况下,在显示"请求超时"之前,Ping 等待 1,000 毫秒(1 秒)的时间让每
个响应返回。如果通过 Ping 探测的远程系统经过长时间延迟的链路,如卫星链路,
则响应可能会花更长的时间才能返回。可以使用 -w (等待)选项指定更长时间的超
时。

使用 Arp 解决硬件地址问题
"地址解析协议 (ARP)"允许主机查找同一物理网络上的主机的媒体访问控制地址,
如果给出后者的 IP 地址。为使 ARP 更加有效,每个计算机缓存 IP 到媒体访问控制
地址映射消除重复的 ARP 广播请求。

可以使用 arp 命令查看和修改本地计算机上的 ARP 表项。arp 命令对于查看 ARP 缓
存和解决地址解析问题非常有用。

详细信息,请参阅查看"地址解析协议 (ARP)"缓存和添加静态 ARP 缓存项目。

使用 nbtstat 解决 NetBIOS 名称问题
TCP/IP 上的 NetBIOS (NetBT) 将 NetBIOS 名称解析成 IP 地址。TCP/IP 为 NetBI
OS 名称解析提供了很多选项,包括本地缓存搜索、WINS 服务器查询、广播、DNS 服
务器查询以及 Lmhosts 和主机文件搜索。

Nbtstat 是解决 NetBIOS 名称解析问题的有用工具。可以使用 nbtstat 命令删除或
更正预加载的项目:

nbtstat -n 显示由服务器或重定向器之类的程序在系统上本地注册的名称。
nbtstat -c 显示 NetBIOS 名称缓存,包含其他计算机的名称对地址映射。
nbtstat -R 清除名称缓存,然后从 Lmhosts 文件重新加载。
nbtstat -RR 释放在 WINS 服务器上注册的 NetBIOS 名称,然后刷新它们的注册。
nbtstat -a name 对 name 指定的计算机执行 NetBIOS 适配器状态命令。适配器状态
命令将返回计算机的本地 NetBIOS 名称表,以及适配器的媒体访问控制地址。
nbtstat -S 列出当前的 NetBIOS 会话及其状态(包括统计),如下例所示:
NetBIOS connection table

Local name State In/out Remote Host Input Output
------------------------------------------------------------------
CORP1 <00> Connected Out CORPSUP1<20> 6MB 5MB
CORP1 <00> Connected Out CORPPRINT<20> 108KB 116KB
CORP1 <00> Connected Out CORPSRC1<20> 299KB 19KB
CORP1 <00> Connected Out CORPEMAIL1<20> 324KB 19KB
CORP1 <03> Listening
使用 netstat 显示连接统计
可以使用 netstat 命令显示协议统计信息和当前的 TCP/IP 连接。netstat -a 命令
将显示所有连接,而 netstat -r 显示路由表和活动连接。netstat -e 命令将显示
Ethernet 统计信息,而 netstat -s 显示每个协议的统计信息。如果使用 netstat
-n,则不能将地址和端口号转换成名称。下面是 netstat 的输出示例:

C:\>netstat -e
Interface Statistics

Received Sent
Bytes 3995837940 47224622
Unicast packets 120099 131015
Non-unicast packets 7579544 3823
Discards 0 0
Errors 0 0
Unknown protocols 363054211

C:\>netstat -a

Active Connections

Proto Local Address Foreign Address State
TCP CORP1:1572 172.16.48.10:nbsession ESTABLISHED
TCP CORP1:1589 172.16.48.10:nbsession ESTABLISHED
TCP CORP1:1606 172.16.105.245:nbsession ESTABLISHED
TCP CORP1:1632 172.16.48.213:nbsession ESTABLISHED
TCP CORP1:1659 172.16.48.169:nbsession ESTABLISHED
TCP CORP1:1714 172.16.48.203:nbsession ESTABLISHED
TCP CORP1:1719 172.16.48.36:nbsession ESTABLISHED
TCP CORP1:1241 172.16.48.101:nbsession ESTABLISHED
UDP CORP1:1025 *:*
UDP CORP1:snmp *:*
UDP CORP1:nbname *:*
UDP CORP1:nbdatagram *:*
UDP CORP1:nbname *:*
UDP CORP1:nbdatagram *:*

 

C:\>netstat -s
IP Statistics

Packets Received = 5378528
Received Header Errors = 738854
Received Address Errors = 23150
Datagrams Forwarded = 0
Unknown Protocols Received = 0
Received Packets Discarded = 0
Received Packets Delivered = 4616524
Output Requests = 132702
Routing Discards = 157
Discarded Output Packets = 0
Output Packet No Route = 0
Reassembly Required = 0
Reassembly Successful = 0
Reassembly Failures =
Datagrams Successfully Fragmented = 0
Datagrams Failing Fragmentation = 0
Fragments Created = 0

ICMP Statistics
Received Sent
Messages 693 4
Errors 0 0
Destination Unreachable 685 0
Time Exceeded 0 0
Parameter Problems 0 0
Source Quenches 0 0
Redirects 0 0
Echoes 4 0
Echo Replies 0 4
Timestamps 0 0
Timestamp Replies 0 0
Address Masks 0 0
Address Mask Replies 0 0

TCP Statistics

Active Opens = 597
Passive Opens = 135
Failed Connection Attempts = 107
Reset Connections = 91
Current Connections = 8
Segments Received = 106770
Segments Sent = 118431
Segments Retransmitted = 461

UDP Statistics

Datagrams Received = 4157136
No Ports = 351928
Receive Errors = 2
Datagrams Sent = 13809

使用 tracert 跟踪网络连接
Tracert(跟踪路由)是路由跟踪实用程序,用于确定 IP 数据报访问目标所采取的路
径。Tracert 命令用 IP 生存时间 (TTL) 字段和 ICMP 错误消息来确定从一个主机到
网络上其他主机的路由。

Tracert 工作原理
通过向目标发送不同 IP 生存时间 (TTL) 值的"Internet 控制消息协议 (ICMP)"回
应数据包,Tracert 诊断程序确定到目标所采取的路由。要求路径上的每个路由器在
转发数据包之前至少将数据包上的 TTL 递减 1。数据包上的 TTL 减为 0 时,路由器
应该将"ICMP 已超时"的消息发回源系统。

Tracert 先发送 TTL 为 1 的回应数据包,并在随后的每次发送过程将 TTL 递增 1,
直到目标响应或 TTL 达到最大值,从而确定路由。通过检查中间路由器发回的"ICM
P 已超时"的消息确定路由。某些路由器不经询问直接丢弃 TTL 过期的数据包,这在
Tracert 实用程序中看不到。

Tracert 命令按顺序打印出返回"ICMP 已超时"消息的路径中的近端路由器接口列表
。如果使用 -d 选项,则 Tracert 实用程序不在每个 IP 地址上查询 DNS。

在下例中,数据包必须通过两个路由器(10.0.0.1 和 192.168.0.1)才能到达主机
172.16.0.99。主机的默认网关是 10.0.0.1,192.168.0.0 网络上的路由器的 IP 地
址是 192.168.0.1。

C:\>tracert 172.16.0.99 -d
Tracing route to 172.16.0.99 over a maximum of 30 hops
1 2s 3s 2s 10,0.0,1
2 75 ms 83 ms 88 ms 192.168.0.1
3 73 ms 79 ms 93 ms 172.16.0.99
Trace complete.
用 tracert 解决问题
可以使用 tracert 命令确定数据包在网络上的停止位置。下例中,默认网关确定 19
2.168.10.99 主机没有有效路径。这可能是路由器配置的问题,或者是 192.168.10.
0 网络不存在(错误的 IP 地址)。

C:\>tracert 192.168.10.99

Tracing route to 192.168.10.99 over a maximum of 30 hops

1 10.0.0.1 reports:Destination net unreachable.

Trace complete.

Tracert 实用程序对于解决大网络问题非常有用,此时可以采取几条路径到达同一个
点。

Tracert 命令行选项
Tracert 命令支持多种选项,如下表所示。

tracert [-d] [-h maximum_hops] [-j host-list] [-w timeout] target_name

选项 描述
-d 指定不将 IP 地址解析到主机名称。
-h maximum_hops 指定跃点数以跟踪到称为 target_name 的主机的路由。
-j host-list 指定 Tracert 实用程序数据包所采用路径中的路由器接口列表。
-w timeout 等待 timeout 为每次回复所指定的毫秒数。
target_name 目标主机的名称或 IP 地址。

详细信息,请参阅使用 tracert 命令跟踪路径。

使用 pathping 测试路由器
pathping 命令是一个路由跟踪工具,它将 ping 和 tracert 命令的功能和这两个工
具所不提供的其他信息结合起来。pathping 命令在一段时间内将数据包发送到到达最
终目标的路径上的每个路由器,然后基于数据包的计算机结果从每个跃点返回。由于
命令显示数据包在任何给定路由器或链接上丢失的程度,因此可以很容易地确定可能
导致网络问题的路由器或链接。某些选项是可用的,如下表所示。

选项 名称 功能
-n Hostnames 不将地址解析成主机名。
-h Maximum hops 搜索目标的最大跃点数。
-g Host-list 沿着路由列表释放源路由。
-p Period 在 ping 之间等待的毫秒数。
-q Num_queries 每个跃点的查询数。
-w Time-out 为每次回复所等待的毫秒数。
-T Layer 2 tag 将第 2 层优先级标记(例如,对于 IEEE 802.1p)连接到数据包并
将它发送到路径中的每个网络设备。这有助于标识没有正确配置第 2 层优先级的网络
设备。-T 开关用于测试服务质量 (QoS) 连通性。
-R RSVP test Che检查以确定路径中的每个路由器是否支持"资源保留协议 (RSVP)"
,此协议允许主机为数据流保留一定量的带宽。 -R 开关用于测试服务质量 (QoS) 连
通性。

默认的跃点数是 30,并且超时前的默认等待时间是 3 秒。默认时间是 250 毫秒,并
且沿着路径对每个路由器进行查询的次数是 100。

以下是典型的 pathping 报告。跃点列表后所编辑的统计信息表明在每个独立路由器
上数据包丢失的情况。


D:\>pathping -n msw

Tracing route to msw [7.54.1.196]
over a maximum of 30 hops:
0 172.16.87.35
1 172.16.87.218
2 192.68.52.1
3 192.68.80.1
4 7.54.247.14
5 7.54.1.196

Computing statistics for 125 seconds...
Source to Here This Node/Link
Hop RTT Lost/Sent = Pct Lost/Sent = Pct Address
0 172.16.87.35
0/ 100 = 0% |
1 41ms 0/ 100 = 0% 0/ 100 = 0% 172.16.87.218
13/ 100 = 13% |
2 22ms 16/ 100 = 16% 3/ 100 = 3% 192.68.52.1
0/ 100 = 0% |
3 24ms 13/ 100 = 13% 0/ 100 = 0% 192.68.80.1
0/ 100 = 0% |
4 21ms 14/ 100 = 14% 1/ 100 = 1% 10.54.247.14
0/ 100 = 0% |
5 24ms 13/ 100 = 13% 0/ 100 = 0% 10.54.1.196

Trace complete.

当运行 pathping 时,在测试问题时首先查看路由的结果。此路径与 tracert 命令所
显示的路径相同。然后 pathping 命令对下一个 125 毫秒显示忙消息(此时间根据跃
点计数变化)。在此期间,pathping 从以前列出的所有路由器和它们之间的链接之间
收集信息。在此期间结束时,它显示测试结果。

最右边的两栏 This Node/Link Lost/Sent=Pct 和 Address 包含的信息最有用。172
.16.87.218(跃点 1)和 192.68.52.1(跃点 2)丢失 13% 的数据包。 所有其他链
接工作正常。在跃点 2 和 4 中的路由器也丢失寻址到它们的数据包(如 This Node
/Link 栏中所示),但是该丢失不会影响转发的路径。

对链接显示的丢失率(在最右边的栏中标记为 |)表明沿路径转发丢失的数据包。该
丢失表明链接阻塞。对路由器显示的丢失率(通过最右边栏中的 IP 地址显示)表明
这些路由器的 CPU 可能超负荷运行。这些阻塞的路由器可能也是端对端问题的一个因
素,尤其是在软件路由器转发数据包时。

- 作者: sunyes 2005年01月20日, 星期四 10:16  回复(0) |  引用(0) 加入博采

Net start 命令详解

Net start 命令详解


启动服务,或显示已启动服务的列表。两个或多个词组成的服务名,例如 Net Logon 或 Computer Browser,必须两边加引号。

net start [service]
参数

键入不带参数的 net start 显示正在运行服务的列表。
service
包括 alerter、client service for netware、clipbook server、content
index、comput
er browser、dhcp client、directory replicator、eventlog、ftp publishing
service、
hypermedia object manager、logical disk manager、lpdsvc、media services
managemen
t, messenger、Fax Service、Microsoft install server、net logon、network
dde、netw
ork dde dsdm、nt lm security support provider、ole、plug and
play、remote access
connection manager、remote access isnsap service、remote access
server、remote pr
ocedure call (rpc) locator、remote procedure call (rpc)
service、schedule、server
、simple tcp/ip services、site server ldap service、smartcard resource
manager、s
nmp、spooler、task scheduler、tcp/ip netbios helper、telephony
service、tracking
service、tracking (server) service、ups、Windows time service 和
workstation。
下面服务只有在 Windows 2000 上可用:file service for macintosh、gateway service
for netware、microsoft dhcp service、print service for
macintosh、windows interne
t name service。

Net start Alerter
启动"警报器"服务。"警报器"服务发送警告消息。
Net start Alerter
Net start Client Service for NetWare
启动"NetWare 客户服务"。该命令只有在安装了 NetWare 客户服务的情况下才能在 Wind
ows 2000 Professional 上使用。
net start "client service for netware"
Net start ClipBook Server
启动"剪贴簿服务器"服务。两个单词组成的服务名,例如 ClipBook Server,必须两边加
引号 ("。
net start "clipbook server"

Net start Computer Browser
启动"计算机浏览器"服务。
net start "computer browser"

Net start DHCP Client
启动"DHCP 客户"服务。该命令只有在安装了 TCP/IP 协议之后才可用。
net start "dhcp client"

Net start Directory Replicator
启动"目录复制程序"服务。"目录复制程序"服务将指定的文件复制到指定服务器上。两
个词组成的服务名,例如 Directory Replicator,必须两边加引号 ("。也可以用命令 net
start replicator 启动该服务。
net start "directory replicator"

Net start Eventlog
启动"事件日志"服务,该服务将事件记录在本地计算机上。必须在使用事件查看器查看记
录的事件之前启动该服务。
net start Eventlog

Net start File Server for Macintosh
启动 Macintosh 文件服务,允许 Macintosh 计算机使用共享文件。该命令只能在运行 Win
dows 2000 Server 的计算机上可用。
net start "file service for macintosh"
Net start FTP Publishing Service
启动 FTP 发布服务。该命令只有在安装了 Internet 信息服务后才可用。
net start "ftp publishing service"
Net start Gateway Service for NetWare
启动 NetWare 网关服务。该命令只有在安装了 NetWare 网关服务的情况下才能在 Windows
2000 Server 上可用。
net start "gateway service for netware"
Net start Lpdsvc
启动 TCP/IP 打印服务器服务。该命令只有在 UNIX 打印服务和 TCP/IP 协议安装后方可使
用。
net start lpdsvc

Net start Messenger
启动"信使"服务。"信使"?裨市砑扑慊?邮沼始??
net start messenger

Net start Microsoft DHCP Service
启动 Microsoft DHCP 服务。该命令只有在运行 Windows 2000 Server 并且已安装 TCP/IP
协议和 DHCP 服务的情况下才可用。
net start "microsoft dhcp service"

Net start Net Logon
启动"网络登录"服务。"网络登录"服务验证登录请求并控制复制用户帐户数据库域宽。
两个词组成的服务名,例如 Net Logon,必须两边加引号 ("。该服务也可以使用命令 net
start netlogon 启动。
net start "net logon"

Net start Network DDE
启动"网络 DDE"服务。
net start "network dde"
Net start NT LM Security Support Provider
启动"NT LM 安全支持提供程序"服务。该命令只有在安装了"NT LM 安全支持提供程序"
后才可用。
net start "nt lm security support provider"
Net start OLE
启动对象链接和嵌入服务。
net start ole
Net start Print Server for Macintosh
启动 Macintosh 打印服务器服务,允许从 Macintosh 计算机打印。该命令只能在运行 Win
dows 2000 Server 的计算机上可用。
net start "print server for macintosh"
Net start Remote Access Connection Manager
启动"远程访问连接管理器"服务。该命令只有在安装了"远程访问服务"后才可用。
net start "remote access connection manager"
Net start Remote Access ISNSAP Service
启动"远程访问 ISNSAP 服务"。该命令只有在安装了"远程访问服务"后才可用。
net start "remote access isnsap service"
Net start Remote Procedure Call (RPC) Locator
启动 RPC 定位器服务。"定位器"服务是 Microsoft Windows 2000 的 RPC 名称服务。
net start "remote procedure call (rpc) locator"

Net start Remote Procedure Call (RPC) Service
启动"远程过程调用 (RPC) 服务"。"远程过程调用 (RPC) 服务"是 Microsoft Windows
2000 的 RPC 子系统。RPC 子系统包括终结点映射器和其他各种 RPC 服务。
net start "remote procedure call (rpc) service"

Net start Schedule
启动"计划"服务。"计划"服务使计算机可以使用 at 命令在指定时间启动程序。
net start schedule

Net start Server
启动"服务器"服务。"服务器"服务使计算机可以共享网络上的资源。
net start server

Net start Simple TCP/IP Services
启动"简单 TCP/IP 服务"服务。该命令只有在安装了 TCP/IP 和"简单 TCP/IP 服务"后
才可以使用。
net start "simple tcp/ip services"
Net start Site Server LDAP Service
启动"Site Server LDAP 服务"。"Site Server LDAP 服务"在 Windows 2000 Active D

irectory 中发布 IP 多播会议。该命令只有在安装了"Site Server LDAP 服务"后才可以使
用。
net start "site server ldap service"

Net start SNMP
启动 SNMP 服务。SNMP 服务允许服务器向 TCP/IP 网络上的 SNMP 管理系统报告当前状态。
该命令只有在安装了 TCP/IP 和 SNMP 后才可以使用。
net start snmp

Net start Spooler
启动"后台打印程序"。
net start spooler
Net start TCP/IP NetBIOS Helper
在 TCP 服务上启用 Netbios 支持。该命令只有在安装了 TCP/IP 才可用。
net start "tcp/ip netbios helper"
Net start UPS
启动"不间断电源 (UPS)"服务
net start ups

Net start Windows Internet Name Service
启动"Windows Internet 命名服务"。该命令只有在安装了 TCP/IP 和"Windows Interne
t 命名服务"后在 Windows 2000 Servers 上才可以使用。
net start "windows internet name service"

Net start Workstation
启动"工作站"服务。"工作站"服务使计算机可以连接并使用网络资源。
net start workstation
Net start Schedule
有的地方称为"定时"服务,叫法不同,请大家注意了,其实是一回事!
Net start Telnet
启动telnet服务,打开23端口,有的情况下需先运行NTLM.exe。为什么?到榕G的说明里去找
吧!


net start workstation
打开NET USE
net start lanmanserver
打开 IPC 服务。
开FTP命令是:net start msftpsvc
Net start
启动服务,或显示已启动服务的列表。两个或多个词组成的服务名,例如 Net Logon 或 Co
mputer Browser,必须两边加引号 ("。
net start [service]
参数

键入不带参数的 net start 显示正在运行服务的列表。
service
包括 alerter、client service for netware、clipbook server、content
index、comput
er browser、dhcp client、directory replicator、eventlog、ftp publishing
service、
hypermedia object manager、logical disk manager、lpdsvc、media services
managemen
t, messenger、Fax Service、Microsoft install server、net logon、network
dde、netw
ork dde dsdm、nt lm security support provider、ole、plug and
play、remote access
connection manager、remote access isnsap service、remote access
server、remote pr
ocedure call (rpc) locator、remote procedure call (rpc)
service、schedule、server
、simple tcp/ip services、site server ldap service、smartcard resource
manager、s
nmp、spooler、task scheduler、tcp/ip netbios helper、telephony
service、tracking
service、tracking (server) service、ups、Windows time service 和
workstation。
下面服务只有在 Windows 2000 上可用:file service for macintosh、gateway service
for netware、microsoft dhcp service、print service for
macintosh、windows interne
t name service。

Net start Alerter
启动"警报器"服务。"警报器"服务发送警告消息。
Net start Alerter
Net start Client Service for NetWare
启动"NetWare 客户服务"。该命令只有在安装了 NetWare 客户服务的情况下才能在 Wind
ows 2000 Professional 上使用。
net start "client service for netware"
Net start ClipBook Server
启动"剪贴簿服务器"服务。两个单词组成的服务名,例如 ClipBook Server,必须两边加
引号 ("。
net start "clipbook server"

Net start Computer Browser
启动"计算机浏览器"服务。
net start "computer browser"

Net start DHCP Client
启动"DHCP 客户"服务。该命令只有在安装了 TCP/IP 协议之后才可用。
net start "dhcp client"

Net start Directory Replicator
启动"目录复制程序"服务。"目录复制程序"服务将指定的文件复制到指定服务器上。两
个词组成的服务名,例如 Directory Replicator,必须两边加引号 ("。也可以用命令 net
start replicator 启动该服务。
net start "directory replicator"

Net start Eventlog
启动"事件日志"服务,该服务将事件记录在本地计算机上。必须在使用事件查看器查看记
录的事件之前启动该服务。
net start Eventlog

Net start File Server for Macintosh
启动 Macintosh 文件服务,允许 Macintosh 计算机使用共享文件。该命令只能在运行 Win
dows 2000 Server 的计算机上可用。
net start "file service for macintosh"
Net start FTP Publishing Service
启动 FTP 发布服务。该命令只有在安装了 Internet 信息服务后才可用。
net start "ftp publishing service"
Net start Gateway Service for NetWare
启动 NetWare 网关服务。该命令只有在安装了 NetWare 网关服务的情况下才能在 Windows
2000 Server 上可用。
net start "gateway service for netware"
Net start Lpdsvc
启动 TCP/IP 打印服务器服务。该命令只有在 UNIX 打印服务和 TCP/IP 协议安装后方可使
用。
net start lpdsvc

Net start Messenger
启动"信使"服务。"信使"服务允许计算机接收邮件。
net start messenger

Net start Microsoft DHCP Service
启动 Microsoft DHCP 服务。该命令只有在运行 Windows 2000 Server 并且已安装 TCP/IP
协议和 DHCP 服务的情况下才可用。
net start "microsoft dhcp service"

Net start Net Logon
启动"网络登录"服务。"网络登录"服务验证登录请求并控制复制用户帐户数据库域宽。
两个词组成的服务名,例如 Net Logon,必须两边加引号 ("。该服务也可以使用命令 net
start netlogon 启动。
net start "net logon"

Net start Network DDE
启动"网络 DDE"服务。
net start "network dde"
Net start NT LM Security Support Provider
启动"NT LM 安全支持提供程序"服务。该命令只有在安装了"NT LM 安全支持提供程序"
后才可用。
net start "nt lm security support provider"
Net start OLE
启动对象链接和嵌入服务。
net start ole
Net start Print Server for Macintosh
启动 Macintosh 打印服务器服务,允许从 Macintosh 计算机打印。该命令只能在运行 Win
dows 2000 Server 的计算机上可用。
net start "print server for macintosh"
Net start Remote Access Connection Manager
启动"远程访问连接管理器"服务。该命令只有在安装了"远程访问服务"后才可用。
net start "remote access connection manager"
Net start Remote Access ISNSAP Service
启动"远程访问 ISNSAP 服务"。该命令只有在安装了"远程访问服务"后才可用。
net start "remote access isnsap service"
Net start Remote Procedure Call (RPC) Locator
启动 RPC 定位器服务。"定位器"服务是 Microsoft Windows 2000 的 RPC 名称服务。
net start "remote procedure call (rpc) locator"

Net start Remote Procedure Call (RPC) Service
启动"远程过程调用 (RPC) 服务"。"远程过程调用 (RPC) 服务"是 Microsoft Windows
2000 的 RPC 子系统。RPC 子系统包括终结点映射器和其他各种 RPC 服务。
net start "remote procedure call (rpc) service"

Net start Schedule
启动"计划"服务。"计划"服务使计算机可以使用 at 命令在指定时间启动程序。
net start schedule

Net start Server
启动"服务器"服务。"服务器"服务使计算机可以共享网络上的资源。
net start server

Net start Simple TCP/IP Services
启动"简单 TCP/IP 服务"服务。该命令只有在安装了 TCP/IP 和"简单 TCP/IP 服务"后
才可以使用。
net start "simple tcp/ip services"
Net start Site Server LDAP Service
启动"Site Server LDAP 服务"。"Site Server LDAP 服务"在 Windows 2000 Active D

irectory 中发布 IP 多播会议。该命令只有在安装了"Site Server LDAP 服务"后才可以使
用。
net start "site server ldap service"

Net start SNMP
启动 SNMP 服务。SNMP 服务允许服务器向 TCP/IP 网络上的 SNMP 管理系统报告当前状态。
该命令只有在安装了 TCP/IP 和 SNMP 后才可以使用。
net start snmp

Net start Spooler
启动"后台打印程序"。
net start spooler
Net start TCP/IP NetBIOS Helper
在 TCP 服务上启用 Netbios 支持。该命令只有在安装了 TCP/IP 才可用。
net start "tcp/ip netbios helper"
Net start UPS
启动"不间断电源 (UPS)"服务
net start ups

Net start Windows Internet Name Service
启动"Windows Internet 命名服务"。该命令只有在安装了 TCP/IP 和"Windows Interne
t 命名服务"后在 Windows 2000 Servers 上才可以使用。
net start "windows internet name service"

Net start Workstation
启动"工作站"服务。"工作站"服务使计算机可以连接并使用网络资源。
net start workstation
Net start Schedule
有的地方称为"定时"服务,叫法不同,请大家注意了,其实是一回事!
Net start Telnet
启动telnet服务,打开23端口,有的情况下需先运行NTLM.exe。为什么?到榕G的说明里去找
吧!
net start workstation
打开NET USE
net start lanmanserver
打开 IPC 服务。
net start msftpsvc
打开FTP服务

- 作者: sunyes 2005年01月20日, 星期四 10:13  回复(0) |  引用(0) 加入博采

nt服务开发

 
摘 要:利用一组WIN32 API函数将自主开发的服务器程序扩展为 NT的 一项后台服务,让NT把其当作系统服务自动加载,从而扩充NT服务器的后台功能,并结合一 个实例说明开发中应做的工作。
  关键词:Windows NT 后台服务 服务控制管理器


 
 
 在WINDOWS NT服务器中,NT的一些后台系统服务随着NT的启动而自动加载, 不需用户人工 干预,这种方式不仅简化了服务器启动过程,同时也使一些重要服务的启动更加安全可靠。 而且,在服务器启动以后,用户也可以利用控制面板中服务控制面板应用程序灵活地设置这 些后台服务的启动属性,方便了用户对NT后台服务的管理。笔者在开发基于NT服务器的应用 程 序时,发现利用一组WIN32 API 函数完全可以将自己开发的服务器程序扩展为NT的一项后台 服务,让NT把其当作系统服务自动加载,从而扩充NT服务器的后台服务功能。下面结合一个 实例,概述开发NT后台服务应用程序的方法和步骤。

1 自定义后台服务注册
  在NT操作系统中,所有的后台服务全都由服务控制管理器进行统一管理,这些后台服务的状 态数据都保存在服务控制管理器数据库中。所以要想创建一个新的后台服务,在应用程序的 主模块里应首先调用函数OpenSCManager打开该数据库,再调用函数CreateService在此数据 库中创建1个新的服务线程对象,并将该线程对象启动属性设置为随系统启动自动加载,这 样NT在重新启动时该线程就会由NT自动启动。完成这一步,仅仅实现了后台服务线程对象的 注册,还没有建立与服务控制管理器的联结。

//在服务控制管理器数据库中注册后台服务线程
void CmdRegisterService()
{
  SCHANDLE  schService;
  SCHANDLE  schSCManager;
  TCHAR szPath[512];
  //获得后台服务进程执行路径
  if(GetModuleFileName(NULL,szPath,512)==0)
  {tprintf(TEXT("Unable to install %s - %s\n"),
  TEXT("Service Example"), GetLastErrorText(szErr, 256));
   return;
  }
  //打开服务控制管理器数据库
  schSCManager=OpenSCManager(
         NULL,   //本地机器
         NULL,   //默认数据库
         SCMANAGERALLACCESS //访问权限
         );
  //在此数据库中创建后台服务线程对象
  if(schSCManager)
  { schService=CreateService(
    schSCManager, //服务控制管理器数据库
    TEXT("ServiceExample"), //后台服务名称
    TEXT("Service Example"),//在服务控制面板中显示的//服务名称
    SERVICEALLACCESS, //访问权限
    SERVICEWIN32OWNPROCESS, //服务类型
    SERVICEAUTOSTART, //启动类型,随系统自动//加载
    SERVICEERRORNORMAL,
    szPath,
    NULL,
    NULL,
TEXT(""),
NULL,  //本地系统帐号
NULL);// 没有口令
  //在这里可以创建多个后台服务线程对象,完成不同的
//后台任务
  if(schService)
   {tprintf(TEXT("%s installed.\n"),TEXT("Service Example")); 
    CloseServiceHandle(schService);
   }
   else
   {tprintf(TEXT("CreateService failed-%s\n"),
   GetLastErrorText(szErr, 256));
   }
   CloseServiceHandle(schSCManager);
  }
  else
   tprintf(TEXT("OpenSCManager failed - %s\n"),
GetLastErrorText(szErr,256));
}

2 定义后台服务线程入口点ServiceMain
  在此之前应该首先定义服务线程入口表(SERVICETABLEENTRY),它记录 了服务器应用程序 要执行的所有后台服务的入口点(在1个服务器应用程序中可同时加载多个后台服务线程)。 每一个后台服务线程都有自己的ServiceMain执行入口点。当服务控制管理器接收到启动后 台服务的命令后,它会首先将启动命令发送到服务控制分配器中,然后,服务控制分配器就 会按照服务线程入口表定义的对应关系启动1个新线程去执行指定的后台服务。在Servic eMain中应当调用RegisterServiceCtrlHandler先注册1个服务控制函数Handler,它负责接 收和处理服务控制管理器发出的命令,并通过调用SetServiceStatus重新设置后台服务状态 ,将被启动的后台服务状态信息回送到服务控制管理器中。这些操作主要是在后台服务线程 启动之前完成一些必要的初始化工作。ServiceMain及Handler是占位符,在程序中可以用不 同的名称定义自己的后台服务线程入口函数和服务控制处理函数。

//定义后台服务线程入口表
SERVICETABLEENTRY MyServiceTable[]=
{
   {
   TEXT("ServiceExample"),//后台服务线程的名称
   (LPSERVICEMAINFUNCTION)MyServiceMain//后台服//务线程入口点
   },
   {NULL, NULL }   //标志表的结束
};
//服务线程入口函数
void WINAPI MyServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
{
  //注册服务控制处理函数
sshStatusHandle=RegisterServiceCtrlHandler(TEXT("ServiceExample"), Servi ceControlHandler);
  if(!sshStatusHandle)
goto cleanup;
  //服务类型
  ssStatus.dwServiceType=SERVICEWIN32OWNPROCESS; 
  //指定的出错代码
  ssStatus.dwServiceSpecificExitCode=0;
  //启动自己定义的后台服务
  ServiceStart();
cleanup:
  if(sshStatusHandle)
(VOID)ReportStatusToSCMgr(
SERVICESTOPPED,dwErr,0);
  return;
}

//服务控制处理函数:负责接收和处理服务控制管理器发出的
//命令
VOID WINAPI ServiceControlHandler(DWORD dwCtrlCode)
{
  switch(dwCtrlCode)
  {
   //停止服务之前,应首先向服务控制管理器回送状态信息
   case SERVICECONTROLSTOP:
    ReportStatusToSCMgr(SERVICESTOPPENDING,NOE RROR, 0);
    ServiceStop();
    return;
   //更新服务状态
   case SERVICECONTROLINTERROGATE:
    break;
   default:
    break;
  }
  //向服务控制面板管理器回送状态信息
  ReportStatusToSCMgr(ssStatus.dwCurrentState, NOERROR, 0); 
}

//设置当前服务状态并将状态信息回送到服务控制管理器
BOOL ReportStatusToSCMgr(DWORD dwCurrentState,DWORD
dwWin32ExitCode,WORD dwWaitHint)
{
  static DWORD dwCheckPoint=1;
  BOOL fResult=TRUE;
  if(dwCurrentState==SERVICESTARTPENDING)
   ssStatus.dwControlsAccepted=0;
  else
   ssStatus.dwControlsAccepted=SERVICEACCEPTSTOP;
  //设置状态信息
  ssStatus.dwCurrentState=dwCurrentState;
  ssStatus.dwWin32ExitCode=dwWin32ExitCode;
  ssStatus.dwWaitHint=dwWaitHint;
  if((dwCurrentState==SERVICERUNNING )‖
   (dwCurrentState==SERVICESTOPPED))
   ssStatus.dwCheckPoint=0;
  else
   ssStatus.dwCheckPoint=dwCheckPoint++;
  //将状态信息回送到服务控制管理器
  if(!(fResult=SetServiceStatus(sshStatusHandle,&ssStatus))){
   AddToMessageLog(TEXT("SetServiceStatus"));//向NT事//件管理器报告出错消息
   }
   return fResult;
}

3 建立应用程序主线程与服务控制管理器的连接
  在创建了服务器应用程序的所有服务线程对象后,当NT重新启动加载该服务器进程或者从服 务控制面板中手工启动该服务进程时, 服务器应用程序主线程就会调用函数StartServiceCt rlDispatcher建立服务进程主线程与服务控制管理器的连接,使得主线程扮演起服务控制分 配器的角色,只有当所有的服务线程终止后,该主线程才会完成任务并返回。利用上述过程 建立起来的连接,服务控制管理器会将服务控制面板中用户对服务进程的控制命令(Start、 Terminate等)发送到主线程中,交给服务控制分配器处理。

//服务进程入口
voidCRTAPI1 main(int argc, char **argv)
{
  if((argc>1)&&((*argv[1]==‘-')‖(*argv[1]==‘/')) )
  {
   if(stricmp("register", argv[1]+1)==0)
    CmdRegisterService(); //注册后台服务线程对象
   else
    goto dispatch;
   exit(0);
  }
  dispatch:
   //启动服务控制分配器,建立主线程与服务控制面板的
//连接
   if(StartServiceCtrlDispatcher(MyServiceTable))
    AddToMessageLog(TEXT ("StartServiceCtrlDispatcher success."));
}

4 定义自己的后台服务实现主模块
  该模块放在服务线程入口点ServiceMain中,在服务线程初始化完毕以后,就可以启动自己 定义的后台服务。  

void ServiceStart()
{
  //向服务控制管理器报告服务正在启动
  if(!ReportStatusToSCMgr(
SERVICESTARTPENDING, //service state
NOERROR,
dwWaitHint))
return;
  //下面是线程的初始化代码(注意:初始化代码执行时间
//不应超过设定的nWaitHi nt,否则服务控制管理器会认
//为服务线程已经没有响应
  //向服务控制管理器报告服务已经启动
  if(!ReportStatusToSCMgr(
SERVICERUNNING,  //service state
NOERROR,   //exit code
0))    //wait hint
   return;
//开始执行服务
}
//停止后台服务线程
void ServiceStop()
{
  //添加结束服务线程代码
}

  在该示例中,后台服务线程在服务器上创建1个网络套接字,并在6002端口监听TCP/IP客 户 的连接请求。如果与新的客户建立连接后,首先向其发送欢迎消息,接下来,只要每收到客 户发送过来的消息,就会向其回送1个确认信息,直到连接断开以后重新进入监听状态。感 兴趣的读者可以将本程序在VC5.0中编译成可执行文件(在AppWizard中选择Win32 Console A pplication,工程名为ntservice),然后在WINDOWS NT SERVER4.0的控制台中输入"ntservi ce -register",再重新启动服务器,打开控制面板中的服务管理器,就可以看到Service Example已启动。如果要测试服务例程的响应功能,可以自己编写1个TCP/IP客户程序,并连 接 到该服务器的端口6002。本程序在VC5.0中编译连接,在中文WINDOWS NT SERVER4.0中调试 通过。

- 作者: sunyes 2005年01月20日, 星期四 10:09  回复(27) |  引用(0) 加入博采