已阅读5页,还剩12页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
/xuyuan77/archive/2008/03/28/1127941.htmlWindows服务编写综述作者:李朝中 摘要:几乎所有的操作系统在启动的时候都会启动一些不需要与用户交互的进程,这些进程在Windows中就被称作服务。它由服务程序、服务控制程序(,service control program)和服务控制管理器(CM,service control manager)三个组件构成。本文针对服务程序与服务控制程序的编写进行综合讲述。 关键词:Windows,服务,VC+1 服务介绍几乎所有的操作系统在启动的时候都会启动一些不需要与用户交互的进程,这些进程在Windows中就被称作服务。它通常用于实现客户服务器模式中的服务器方,如我们常见的eb服务IIS,当操作系统在启动后它就自动被运行,不管是否有人登陆到系统只要系统开启它就能得到运行。服务程序、服务控制程序(SCP,service control program)和服务控制管理器(SCM,service control manager)组成了Windows服务。我们可以通过服务控制程序操纵服务控制管理器来配置、启动、暂停、停止服务程序。其中服务程序和服务控制程序可以由我们自己来编写扩展,而服务控制管理器(windowssystem32servics.exe)则是操作系统内置的一个部件。首先我们来了解一下的工作情况,然后我们介绍服务程序的编写和服务控制时所涉及的使用。2 服务控制管理器SCM本身也是一个服务程序(windowssystem32servics.exe),作为windows的后台服务运行的。Winlogon在系统引导的早期会将SCM启动起来。SCM的服务入口函数首先创建一个初始化为无信号的同步事件对象(SvcCtrlEvent_A3752DX);接下来,它开始建立一个内部服务数据库,这个数据库要按事先规定好的一个顺序列出所有服务组,并记录与服务相关的详细信息;当这个数据库建立完成时SCM就开始按顺序启动那些启动方式为自动的服务,如果有服务要动行于指定用户账户中时还要调用LSASS,如果服务启动失败则会被放入一个名为ScFailedDrivers的列表中。当这些工作都完成后,SCM将同步事件对象SvcCtrlEvent_A3752DX置为有信号状态;并做好系统停机的准备。当系统要关机时会向Windows子系统进程Csrss发送一个消息,以便调用Csrss的停机例程。Csrss会对所有活动的进程循环通知系统正在停机。对于除SCM以外的每一个系统进程如果没有返回退出的响应Csrss 都会等待由HKEY_USER.DEFAULTControl PanelDesktopWaitToKillAppTimeout指定的毫秒数(我的系统中是20000,也就是20秒),然后知通下一个进程结束。当遇到SCM时也会通知SCM进程系统正在停机,但不同的是会使用一个专用的超时间隔值(SCM在系统初始化时要向Csrss登记,于是Csrss就将SCM的进程ID保存了下来Csrss也就是通过个ID来识别SCM的)。这个专用的超时间隔位于HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlWaitToKillServiceTimeout中,有趣的是默认值也是20秒。这在段时间里SCM要通知所有在初始化时(服务程序自身初始化)请求SCM通知自己系统停机的服务,SCM有一个停机处理器负责这项工作。通知下达后SCM就等待服务退出,服务在接收到停机消息后会返回给SCM一个等待时间,SCM跟踪所有服务返回等待时间找出其中的最大值,当这个最大值达到后,如果有一个服务或多个服务又告诉SCM它们正在处理停机工作,那么SCM还会循环等待下去,但Csrss也再等待SCM当Csrss等待超时后,会继续后面的停机工作最终完成停机。这就要求服务程序要在Csrss等待SCM的这段时内完成自己的停机处理工作,否则服务就没机会在系统停机前完成自己的关闭工作了。3 服务程序Windows服务程序其实并不神秘,它只是遵循特定规则编写的一个程序。只要遵循这个特定的规则与服务控制管理器正确的交互,就可实现我们的服务程序。而我们只要能实现一个简单的服务程序,设计一个能处理复杂业务的服务也并非难事,因为从结构上看两者并没有太大的区别。只要遵循与SCM交互的规则,设计服务程序与设计普通的应用程序几乎没什么区别。3.1 程序结构概要 服务程序的与普通应用程序一样也需要一个主函数(main())作为程序的入口,与之不同的是作为一个服务程序它需要在主函数(main())中立即调用StartServiceCtrlDispatcher来注册一个服务的入口函数(erverMain(DWORD argc,LPTSTR *argv),当然这个名字可自由命名)。StartServiceCtrlDispatcher函数的原型是: BOOL StartServiceCtrlDispatcher(LPSERVICE_TABLE_ENTRY lpServiceStartTable);它的参数是一个指向SERVICE_TABLE_ENTRY的指针;SERVICE_TABLE_ENTRY结构有两个域;第一个域存储服务的内部名称,第二个域是服务入口函数的指针。这个函数完成后,就要可以服务启动的时候调用服务的入口函数。 例如:管理员在服务管理器启动一个服务,SCM就会在一个单独的线程中调用服务注册的入口函数。这时我们在服务的这个入口函数中必须调用RegisterServiceCtrlHandler完成Handler函数的注册,这个函数用来接收和处理SCM的控制消息。下面列出ander要处理的控制消息和RegisterServiceCtrlHandler的函数原型:VOID WINAPI Handler(DWORD fdwControl / 请求控制消息代码);控制消息宏定义说明SERVICE_CONTROL_STOP要服务停止SERVICE_CONTROL_PAUSE要服务暂停SERVICE_CONTROL_CONTINUE要服务继续SERVICE_CONTROL_INTERROGATE要服务马上报告它的状态SERVICE_CONTROL_SHUTDOWN告诉服务即将关机SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(LPCTSTR lpServiceName, / 服务的内部名称LPHANDLER_FUNCTION lpHandlerProc / Handler函数的地址); RegisterServiceCtrlHandler调用完成后我们就可以开始我们的业务处理的初始化工作。初始化完成后向SCM报告服务开始运行(SERVICE_RUNNING)的消息。如果erverMain(DWORD argc,LPTSTR *argv)函数退出服务也就停止了。下面让我总结一下实现服务程序的步骤:()在main()调用StartServiceCtrlDispatcher来注册一个服务的入口函数;()在ServerMain(DWORD argc,LPTSTR *argv)中调用RegisterServiceCtrlHandler注册Handler函数。()完成业务处理程序的初始化工作,如果初始化时间较长要实时向报告当前正在启动()初始化完毕,报告服务正在运行;开始业务处理工作。3.2 程序实例分析()main()函数int main(int argc, char* argv) SERVICE_TABLE_ENTRY serviceTable= SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)ServiceMain NULL,NULL ; BOOL success; success = StartServiceCtrlDispatcher(serviceTable); if (!success) ErrorHandler(In StartServiceCtrlDispatcher,GetLastError(); return 0;StartServiceCtrlDispatcher的参数必须是一个以NULL结尾的数组指针,我们可以在一个程序文件中注册多个服务实例,只在把所要注册的服务名和服务入口函数地址写到数组中即可,在我们调用CreateService创建服务时要把dwServiceType 参数设为共享进程(SERVICE_WIN32_SHARE_PROCESS);不过当要创建独立进程的服务时(dwServiceType 参数为SERVICE_WIN32_OWN_PROCESS时)在这里就只能注册一个服务实例。()服务入口函数ServerMain()VOID ServiceMain(DWORD argc,LPTSTR *argv) BOOL success; StatusHandler= RegisterServiceCtrlHandler(SERVICE_NAME,(LPHANDLER_FUNCTION)Handler); if (!serviceStatusHandler) return; success = ReportStatus (SERVICE_START_PENDING, NO_ERROR,0,1,5000); if (!success) return; endEvent = CreateEvent(0,TRUE,FALSE,0); if (!endEvent) return; success = ReportStatus (SERVICE_START_PENDING, NO_ERROR,0,2,5000); if (!success) return; /init parameter start RecvParam(argc,argv); /init parameter end success = ReportStatus (SERVICE_START_PENDING, NO_ERROR,0,3,5000); if (!success) return; success = InitService(); if (!success) return; success = ReportStatus (SERVICE_RUNNING,NO_ERROR,0,0,0); if (!success) return; WaitForSingleObject(endEvent,INFINITE); RegisterServiceCtrlHandler完成Handler函数的注册( Handler函数的具体实现我们在第三小节中介绍),它的第一个参数是调用CreateService创建服务时lpServiceName指向的名服务名称,每二个参数是Handler函数的地址;函数名可以自由命名。ReportStatus是向报告服务当前状态的一个自定义函数。它内部调用SetServiceStatus向报告服务的当状态,此函数有两个参数第一个就是RegisterServiceCtrlHandler完成时返回的SERVICE_STATUS_HANDLE,第二个参数是一个SERVICE_STATUS变量的指针,它指示了服务当前的状态信息;当注册完Handler函数后向SCM报告一下自己当前的状态(正在启动)。接着创建endEvent事件对像,它是当我们收到的退出控制代码时通知服务主函数退出的,大家可看ServiceMain的最后一句。下面又是向报告自己正在启动,当初始化所花费的时间非常短时这样做并不是必须的,但如果很长就必须这样做。RecvParam(argc,argv)使用了ServiceMain函数的两个参数,大家可以看出ServiceMain和main有着一样的形参;说明ServiceMain和main一样可以接收配置参数,稍后我们会在服务控制程序的编写中给大家介绍如何给服务配置参数。InitService()完成我们的业务初始化工作并开始业务处理。最后报告服务启动完成,等待endEvent事件退出服务。下面我们再来看一下控制消息的处理。()SCM控制消息处理(Handler函数)VOID Handler(DWORD controlCode) DWORD currentState = 0; BOOL success; switch(controlCode) case SERVICE_CONTROL_STOP: success=ReportStatus(SERVICE_STOP_PENDING,NO_ERROR,0,1,5000); CloseTask(); success=ReportStatus(SERVICE_STOPPED,NO_ERROR,0,0,0); return; case SERVICE_CONTROL_PAUSE: if (runningService&!pauseService) success=ReportStatus(SERVICE_PAUSE_PENDING,NO_ERROR,0,1,1000); pauseService=TRUE; ServicePause(); currentState=SERVICE_PAUSED; break; case SERVICE_CONTROL_CONTINUE: if (runningService&pauseService) success = ReportStatus(SERVICE_CONTINUE_PENDING, NO_ERROR,0,1,1000); pauseService = FALSE; ServiceContinue(); currentState=SERVICE_RUNNING; break; case SERVICE_CONTROL_INTERROGATE:/检索更新状态的时 break; case SERVICE_CONTROL_SHUTDOWN:/告诉服务即将关机 success=ReportStatus(SERVICE_STOP_PENDING,NO_ERROR,0,1,5000); CloseTask(); return; default: break; ReportStatus(currentState,NO_ERROR,0,0,0);Handler只有一个参数就是传来的控制消息代码;这里处理的了停止,暂停,继续,更新,关机五个控制消息。但并不是这五个消息都会向服务发送,要在向服务报告状时向报告自己可以响应的控制消息,只要设置SERVICE_STATUS结构中的dwControlsAccepted域即可,它对应的值有:SERVICE_ACCEPT_STOP,SERVICE_ACCEPT_PAUSE_CONTINUE,SERVICE_ACCEPT_SHUTDOWN,当要设置多个时只要把宏相或()传给dwControlsAccepted域即可。在响应控制消息时也要注意及时报告服务当前的状态信息,否则会认为服务响应超时出错了。()服务的安装与卸载 服务程序编写完成并编译通过后,还要安装注册到操作系统中,这样它才会出现在管理工具服务,那个管理器里面。API给我们提供了一个函数来实现我们注册服务的功能; SC_HANDLE CreateService(SC_HANDLE hSCManager,/ 服务控制管理器的句柄 LPCTSTR lpServiceName, / 指向服务的内部名称LPCTSTR lpServiceName, / 指向服务的显示名称DWORD dwDesiredAccess, / 服务的访问类型DWORD dwServiceType, / 服务的类型DWORD dwStartType, / 服务的启动方式(自动,手动,禁用)DWORD dwErrorControl,/ 错误控制方式LPCTSTR lpBinaryPathName,/ 服务程序的路径LPCTSTR lpLoadOrderGroup,/ 服务组的名称 LPDWORD lpdwTagId, / 服务的标签号LPCTSTR lpDependencies,/ 服务依赖的服务或组名LPCTSTR lpServiceStartName, / 服务的启动帐户LPCTSTR lpPassword / 服务启动帐户的密码);hSCManager:这是函数的第一个参数SCM的句柄。它要调用OpenSCManager来获得,稍后我们会讲它怎么调用方法。lpServiceName和lpServiceName:分别的服务的名称和服务的显示名称,服务是显示名称就服务管理器中看到的那个服务名。则是服务在SCM中注册的名称,比如调用OpenService打开服务时就会用到它。dwDesiredAccess:标出服务同意请求的访问,可以是下面任意任值:SERVICE_ALL_ACCESSSERVICE_CHANGE_CONFIGSERVICE_ENUMERATE_DEPENDENTSSERVICE_INTERROGATESERVICE_PAUSE_CONTINUESERVICE_QUERY_CONFIGSERVICE_QUERY_STATUSSERVICE_STARTSERVICE_STOPSERVICE_USER_DEFINED_CONTROL我们可以指定一个或多个,如果有多个的话要用或符号()联结起来。dwServiceType:注册服务的类型,它必须是下面的值:SERVICE_WIN32_OWN_PROCESSSERVICE_WIN32_SHARE_PROCESSSERVICE_KERNEL_DRIVERSERVICE_FILE_SYSTEM_DRIVER如果指定的是SERVICE_WIN32_OWN_PROCESS类型的服务还可以加上SERVICE_WIN32_OWN_PROCESS(允许用户桌面交互),我们这里介绍的服务只能注册为SERVICE_WIN32_OWN_PROCESS或SERVICE_WIN32_SHARE_PROCESS;另两种类型是驱动级的服务用的,有兴趣大家可查看相关资料。dwStartType:服务的启动类型SERVICE_BOOT_START、SERVICE_SYSTEM_START、SERVICE_AUTO_START、SERVICE_DEMAND_START、SERVICE_DISABLED。分别为前两种类型仅对驱动程序用效,所在我们这里所说的这类服务能后三种(自动,手动,禁用)。dwErrorControl:服务的错误控制标记SERVICE_ERROR_IGNORE:忽略所有错误SERVICE_ERROR_NORMAL:正常报告服务返回的错误SERVICE_ERROR_SEVERE:当服务返回错误出现时,如果最后已知好控制集(最后已知好控制集:是系统最后一次成功引导时使用的服务注册表配置)尚未使用,则重新引导进入最后已知好控制集,否则重新引导。SERVICE_ERROR_CRITICAL:当服务返回错误出现时,如果最后已知好控制集尚未使用,则重新引导进入最后已知好控制集,否则蓝屏崩溃。lpBinaryPathName:服务程序的文件路径lpLoadOrderGroup:服务所属的组lpdwTagId:在组中的唯一标识lpDependencies:服务所依赖的其它组和服务lpServiceStartName和lpPassword:服务由哪个用户启动,也即服务运行在哪个用户权限下,分别指定用户名和密码. 下再说两重要的函数:OpenSCManager和CloseServiceHandle给出它们的原型SC_HANDLE OpenSCManager(LPCTSTR lpMachineName,/ 机器名,打开本机的SCM时可为NULLLPCTSTR lpDatabaseName,/ 指向SCM数据库的名字可为NULLDWORD dwDesiredAccess / 访问权限类型如:SC_MANAGER_ALL_ACCESS等);BOOL CloseServiceHandle(SC_HANDLE hSCObject / 服务控制句柄 ); 这果列出一段注册服务的代码供大家参考:SC_HANDLE newService,scm;BOOL success = FALSE;SERVICE_STATUS status;scm = OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE|SC_MANAGER_CREATE_SERVICE); if (!scm) OUT_DEBUG(OpenSCManager ERROR!); return false; newService = CreateService(scm,pszServiceName,pszDisplayName, SERVICE_ALL_ACCESS|SERVICE_STOP,SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START,SERVICE_ERROR_NORMAL,pszServicePath, 0,0,0,0,0); if (!newService) OUT_DEBUG(CreateService ERROR!); CloseServiceHandle(scm); return false; CloseServiceHandle(newService);CloseServiceHandle(scm);return true; 删除服务时调用DeleteService;它只有一个参数(服务句柄)。我们可分四步完成1)、打开SCM句柄。2)、打开要删除的服务。3)、检查当前服务的状态确保服务已经停止。4)、删除服务并关闭所有打开的句柄。下面是一段删除服务的程序。SC_HANDLE Service,scm; SERVICE_STATUS status; BOOL success; if (pszServiceName=NULL) return false; scm = OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE); if (!scm) coutOpenSCManager ERROR:GetLastError()endl; CloseServiceHandle(scm); return false; Service = OpenService(scm,pszServiceName,SERVICE_ALL_ACCESS|DELETE); if (!Service) coutOpenService ERROR:GetLastError()endl; CloseServiceHandle(Service); CloseServiceHandle(scm); return false; success = QueryServiceStatus(Service,&status); if (!success) coutQueryServiceStatus ERROR:GetLastError()endl; CloseServiceHandle(Service); CloseServiceHandle(scm); return false; if (status.dwCurrentState!=SERVICE_STOPPED) success = ControlService(Service,SERVICE_CONTROL_STOP,&status); if (!success) coutControlService ERROR:GetLastError()endl; CloseServiceHandle(Service); CloseServiceHandle(scm); return false; success = DeleteService(Service); if (!success) coutDeleteService ERROR:GetLastError()endl; CloseServiceHandle(Service); CloseServiceHandle(scm); return false; CloseServiceHandle(Service); CloseServiceHandle(scm); return true;4 服务控制程序4.1 服务控制程序概要在上面我们了解服务程序的编写,现在我们来看一下控制服务时会用到的几个常用API。服务控制程序的编写与标准的Windows应用程序无异,它要用到服务管理函数,如:OpenSCManager、OpenService、QueryServiceConfig、StartService、QueryServiceStatus、ControlService等;它都在系统的advapi32.dll中实现。在使用SCM的函数时,SCP必须要首先调用OpenSCManager函数,打开一个通向SCM的通道。调用这个函数的时候,SCP还必须指定它想要执行的动作类型;也就是我们上一节所提到的dwDesiredAccess参数它的取值:SC_MANAGER_ALL_ACCESS、SC_MANAGER_CREATE_SERVICE、SC_MANAGER_ENUMERATE_SERVICE、 SC_MANAGER_QUERY_LOCK_STATUS、SC_MANAGER_ENUMERATE_SERVICE 、SC_MANAGER_QUERY_LOCK_STATUS、 SC_MANAGER_LOCK、SC_MANAGER_CONNECT。例如:我们要枚举当前所有的服务就必须给dwDesiredAccess参数指定SC_MANAGER_ENUMERATE_SERVICE;同时也可以指定其它的值,当指写多个值时我们要把它用按位或(|)符号连接起来。与此同时我们在调用OpenService时也必须告知SCM我们要对服务进行的动作;它有三个参数,最后一个参数dwDesiredAccess指出要对服务进行的操作,这些操作的标记与CreateService中的dwDesiredAccess参数标记值一样。当我们以SERVICE_ALL_ACCESS访问权限打开服务后就可以对它进行配置(QueryServiceConfig)、控制(ControlService)、查询状态(QueryServiceStatus)、设置状态(SetServiceStatus)、删除(DeleteService)等所有访问操作。下面我们可以来看两服务控制的实例。4.2 枚举服务我们先来看EnumDependentStatus函数原型:BOOL EnumServicesStatus (SC_HANDLE hService, / SCM控制句柄DWORD dwServiceType, /要枚举服务还有驱动DWORD dwServiceState, / 要枚举什么状态的服务LPENUM_SERVICE_STATUS lpServices,/ 存储枚举出服务的内存地址DWORD cbBufSize, / lpServices指向内存区大小LPDWORD pcbBytesNeeded,/实际需要的内存大小LPDWORD lpServicesReturned /返回枚举到服务年个数LPDWORD lpResumeHandle /指向下一个有效的入口);dwServiceState参数由:SERVICE_ACTIVE、SERVICE_INACTIVE、SERVICE_STATE_ALL三种值分别枚举当前活动、不活动、全部的服务。函数会返回一个ENUM_SERVICE_STATUS的数组,ENUM_SERVICE_STATUS有三个域分别指出服务的服务名、显示名和当前状态一个指针;我们可以根据服务名打开枚举出的服务,以得到它更加详细的信息。下面具体让我们看一段程序的例子。/清空服务信息队列DeletItemAll();LPENUM_SERVICE_STATUS st=NULL;st=NULL;DWORD ret=0;DWORD size=0;ServiceInfo info;SC_HANDLE sc=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);SC_HANDLE sh;char* szInfo1024*8;DWORD dwSize=1024*8;CString str;/第一次调用来得到需要多大的内存区EnumServicesStatus(sc,SERVICE_WIN32,SERVICE_STATE_ALL,st,size,&size,&ret,NULL);/申请需要的内存st=(LPENUM_SERVICE_STATUS)LocalAlloc(LPTR,size);EnumServicesStatus(sc,SERVICE_WIN32,SERVICE_STATE_ALL,st,size,&size,&ret,NULL);/开始记录枚举出服务的信息for(DWORD i=0;ilpDescription); /得到服务的启动账户名 ZeroMemory(szInfo,dwSize); dwSize=1024*8; QueryServiceConfig(sh,(LPQUERY_SERVICE_CONFIG)szInfo,dwSize,&dwSize); info.LoginUser.Format(%s,(LPQUERY_SERVICE_CONFIG)szInfo)-lpServiceStartName); CloseServiceHandle(sh); /添加到信息队列中 ItemAdd(&info);CloseServiceHandle(sc);return TRUE;上面程序中用到了两个查询服务当前配置的函数QueryServiceConfig2和QueryServiceConfig。它们有所不同是QueryServiceConfig2可以通过设置第二个参数是SERVICE_CONFIG_DESCRIPTION还是SERVICE_CONFIG_FAILURE_ACTIONS来得到服务的描述信息和失败的活动;而QueryServiceConfig则查询返回一个QUERY_SERVICE_CONFIG结构,这个结构存储了服务的类型、启动类型、错误控制标记、服务文件所在路径、显示名等信息详细可以查看MSDN。与这个两函数相对应还有两个配置函数ChangeServiceConfig2和ChangeServiceConfig。它们的具体使用方法我们来看下面的这段程序。4.3 配置服务在3.2中我们举了一个创建服务的程序片段,其中我们只是创建服务并未设置服务描述信息,启动服务的操作。下面的程序片段给出了示例:bool RegterService(char* pszServiceName, char* pszDisplayName, char* pszServicePath, char* pszDescription) SC_HANDLE newService,scm; BOOL success = FALSE; SERVICE_STATUS status; SERVICE_DESCRIPTION description; if (pszDisplayName=NULL&pszServiceName=NULL&pszServicePath=NULL) return false; description.lpDescription=pszDescription; scm = OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE|SC_MANAGER_CREATE_SERVICE); if (!scm) OUT_DEBUG(OpenSCManager ERROR!); return false; newService = CreateService(scm,pszServiceName,pszDisplayName, SERVICE_ALL_ACCESS|SERVICE_STOP,SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START,SERVICE_ERROR_NORMAL,pszServicePath, 0,0,0,0,0); if (!newService) OUT_DEBUG(CreateService ERROR!); CloseServiceHandle(scm); return false; if (description.lpDescription!=NULL) success=ChangeServiceConfig2(newService, SERVICE_CONFIG_DESCRIPTION, &description); success = QueryServiceStatus(newService,&status); if (!success) coutQueryServiceStatus ERROR:GetLastError()endl; CloseServiceHandle(newService); CloseServiceHandle(scm); return false; if (status.dwCurrentState!=SERVICE_RUNNING) success = StartService(newService,NULL,NULL); if (!success) coutControlService ERROR:GetLastError()endl; CloseServiceHandle(newService); CloseServiceHandle(scm); return false; C
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025钢材买卖委托合同
- 2025年短视频内容合作分成合同协议
- 2025买卖办公楼合同范本
- 2025年短视频内容创作合作协议(原创)
- 2025合同协议融资租赁合约样本
- 2025医疗机构医生劳动合同书
- 2025车库买卖合同书模板
- 2025短期雇佣劳务合同
- 2025企业办公空间租赁合同模板
- 2025年土地使用权转让合同 建设用地使用权出让合同书
- 重症病人营养指南解读
- 标书投标年某某医院食堂承包投标书
- GB/T 4134-2003金锭
- GB/T 26862-2011电力系统同步相量测量装置检测规范
- 6-第六讲(关联规则分析)课件
- DB32∕T 4117-2021 保温装饰板外墙外保温系统技术规程
- 2022年医学专题-南非农药登记090919
- 业主大会业主委员会备案表
- 惠普云教室用户操作手册
- 个人及企业征信查询授权书模板
- 高考语文二轮复习:标点符号 课件117张
评论
0/150
提交评论