klg1

来源:互联网 发布:oppo手机还原网络设置 编辑:程序博客网 时间:2024/06/05 00:58

  OAL(OEM Adaptation Layer)既OEM 适配层,从逻辑上讲位于Windows CE内核和硬件之间,从物理上讲OAL各个模块代码被编译后(.lib)和其它内核库链接到一起形成Windows CE的内核可执行文件nk.exe。Windows CE内核在OAL层暴露了大量的函数和全局变量,利用这些函数和全局变量OEM可以编写中断处理、RTC、电源管理、调试端口、通用I/O控制代码等。图1更直观地描述了OAL的结构。CE安装目录的子目录中包含了OAL的部分源码,大多数情况下开发者对OAL只要修改即可,甚至无需修改。通过阅读本篇文章,开发者能够了解OAL的结构、暴露的接口的功能,可以在此基础上实现甚至增强OAL的功能。

  因为OAL层代码大多数CE启动时系统初始化工作有关,所以本篇文章以CE的启动顺序为线索。其它OAL知识在下一篇文章中讲解。

  一、在Boot Loader解压CE内核镜像文件(nk.bin后开始跳转到StartUp(),StartUp函数属于OAL层,此时CE操作系统内核还没有运行。StartUp函数的功能主要有两个,一是初始化CPU为已知状态(known state),二是调用内核初始化函数(x86平台为KernelInitialize,其它平台为KernelStart)。初始化CPU工作因CPU的不同而不同,如果是ARM系列,包括设置CPU为管理员模式、禁止IRQ和FIQ、禁止MMU、清空指令和数据缓冲、检测启动原因、配置GPIO和内存控制器、初始化RTC、保存OEMAddressTable地址等。执行完毕后调用KernetStart。如果是x86系列,包括设置CPU为保护模式、初始化内存控制器、保存OEMAddressTable地址等。执行完毕后调用KernetInitialize。 

  二、内核初始化函数的功能也因CPU的不同而不同,不过有一些功能是相同的,如初始化串口(为了输出调试信息)、调用OEMInit函数等。对于x86系列,初始化工作除了上述的功能外还包括读取OEMAddressTable内容、确定分页大小、内核重定位、初始化中断分配表、初始化分页表、内存初始化和其它初始化。对于其它系列CPU请参考CE帮助文档。

  1. 串口调试:

  串口调试函数包括OEMInitDebugSerial、OEMReadDebugByte、OEMWriteDebugByte等。从OEMInitDebugSerial的源码可以看出,系统从BOOT_ARG_PTR_LOCATION为首地址的结构中判断当前连接的串口是哪个,然后配置这个串口。如果你的设备的串口I/O地址设置和CE默认的一致的话,就能在CE内核得到CPU控制权到启动完毕这段时间里通过串口得到调试信息。

  2. OEMInit

  一般在OEMInit中初始化所有外围的硬件、初始化系统时钟(system tick)和RTCreal time clock)、初始化KITLKernel Independent Transport Layer。例如I486平台的OEMinit函数,它先关联所有的IRQ和中断ID,然后初始化PCI总线、网络适配器、电源管理、PIC(可编程中断控制器)、系统时钟,最后检测是否有扩展内存。另外如果OEM要通过OAL暴露的函数指针或者全局变量来增强功能的话,就要在此函数中实现(在下面详细讲解)。

  3. 检测扩展内存

  我们都知道在config.bib配置文件中设置CE系统使用RAM总量(如果不知道请参考我的文章Platform Builder之旅系列),注意这个RAM总量不是总的物理内存的大小。PB编译的内核包含一个变量ulRAMEnd,将在config.bib中定义的RAM的起始地址 + RAM大小的和赋值给ulRAMEnd。在CE内核的启动过程中,ulRAMEnd的值赋值给全局变量MainMemoryEndAddress,CE内核通过访问MainMemoryEndAddress得到RAM的总量信息。假如基于CE的设备附加了RAM,而MainMemoryEndAddress的值没有包括这段附加的RAM,结果CE内核无法知道已经附加了RAM。为了让CE内核了解附加RAM的信息,OEM应该编写一个函数检测RAM的总量,并把总量值赋给MainMemoryEndAddress。OAL暴露了一个函数指针pNKEnumExtensionDRAM,OEM应该把编写好的函数地址赋给这个函数指针。如果OEM不准备自己编写内存检测函数的话也可以调用OEMGetExtensionDRAM。从帮助文档中看出OEMGetExtensionDRAM这个函数能够检测内存的总量,但是CE的针对X86 平台的源码中没有具体编写这个函数的实现代码(见%_WINCEROOT%\PUBLIC\COMMON\OAK\CSP\I486\OAL\cfwpc.c)。也就是说在X86平台上调用OEMGetExtensionDRAM是检测不到RAM的。如果OEM有兴趣编写检测RAM总量的函数,可以调用现成的函数IsDRAM。这个函数也保存在cfwpc.c中。

  三、内核初始化函数执行完毕后开始按如下步骤执行:

  1. 内核创建用于与filesys.exe同步的事件对象SYSTEM/FSReady,之后启动filesys.exe。启动filesys.exe的意义是让filesys.exe读取注册表数据

  2. 内核等待事件SYSTEM/FSReady被触发,这个事件是由filesys.exe在做完一系列工作后触发。这一系列的工作内容如下:

  2.1 先检测这是一次冷启动还是热启动,如果是冷启动,那么初始化对象存储内存区域。

  2.2 调用OEMIoControl函数,I/O控制代码为IOCTL_HAL_INIT_RTC,也就是初始化RTC

  2.3 初始化数据库子系统和API、文件系统API、消息队列API

  2.4 如果操作系统镜像(nk.bin)包括RAM文件系统,那么读取Initobj.dat文件内容后创建一个RAM文件系统

  2.5 初始化注册表(在内存中形成注册表)。

  2.6 如果此时device.exe没有启动,那么读取HKEY_LOCAL_MACHINE\System\StorageManager下“Dll”的值(这个值为存储管理器所在的.dll文件名)并加载到内存。加载之后创建一个线程专用于初始化存储管理器,初始化之后此线程结束。

  2.7 初始化NLS(national language support)。关于NLS请参见我的文章《CE下中文输入法编辑器》。

  2.8 为数据库引擎设置本地ID

  2.9 读取Initdb.ini文件,安装在对象存储中的数据库

  2.10 触发SYSTEM/FSReady事件,之后filesys.exe处于等待状态,等待内核发通知给它

  3. 此时注册表已经存在于内存当中,内核开始读取如下位置数据:

HKEY_LOCAL_MACHINE\Loader\SystemPath

HKEY_LOCAL_MACHINE\SYSTEM\OOM\cbLow and cpLow

HKEY_LOCAL_MACHINE\SYSTEM\KERNEL\InjectDLL

HKEY_LOCAL_MACHINE\MUI\Enable and SysLang

HKEY_CURRENT_USER\MUI\CurLang

  4. 内核设置低内存处理(out of memory)。低内存处理是指当前可用的内存非常少时,内核所做的解决方案(CE帮助文档中有详细说明)。

  5. 内核在做好了上述工作后通知filesys.exe,由filesys.exe做其余工作。filesys.exe所做的工作内容如下:

  5.1 读取HKEY_LOCAL_MACHINE\System\Events 下包含的所有事件对象名称并一一创建

  5.2 读取HKEY_LOCAL_MACHINE\Init 下包括的所有应用程序名称并一一启动。如果device.exe在列表中并且此时它已经启动了,那么触发SYSTEM/BOOTPHASE2事件,这会使device.exe重新读取注册表数据来完成最后的驱动程序初始化。

5.3 初始化时间区域(time zone)。

 WinCE应用程序的开发相对桌面Windows应用程序的开发有一些特点,如下:

    1. UNICODE编码。WinCE中的应用程序只能使用UNICODE编码,桌面系统则支持UNICODE和ANSI码。在移植PC端程序到设备上时需要注意这一点。

    2.SDK。SDK即软件开发支持包,软件开发都少不了这个,但在WinCE应用程序的开发中尤为重要。因为WinCE系统本身是一个非标的操作系统,它的组件特性和可裁剪性决定了不同的系统支持的API是不同的。而桌面系统相对标准,SDK的作用就弱化了。WinCE中的SDK由系统开发人员在编译完系统后,通过Platform Builder导出。应用程序的开发人员安装此SDK,并编写应用程序,最终将应用程序下载到目标平台上运行测试。一般来说,SDK是应用程序和操作系统之间的纽带,但他们之间也并不是完全一一对应的。譬如,在硬件和操作系统都没调试好时,我们可以先用标准的SDK或者自己定制一个模拟器的SDK进行应用程序的开发,等硬件和系统调试完成后再做联调。应用程序基于新的SDK编译一下,甚至无需重新编译也可运行。当然,一个应用程序在别的设备上跑得很好,但到另外一个设备上却不能工作也是很正常的。就像很多WM上的应用程序在WinCE中不能跑一样,虽然内核相同,但系统不同,支持的API也是不同的。

    最后说说开发语言,WinCE应用程序的开发有Win32MFCManaged等几种方式。对于开发者来说,选择使用哪一个主要看效能,开发的效能和运行的效能。根据能量守恒定律,开发效能和运行效能应该是一个此消彼长的关系。呵呵,跟能量守恒定律有关系么?勉强找个有力证据吧。托管代码的开发效率很高,但执行效率相对就低了。这在物资还不是极大丰富的嵌入式系统上,就显得尤为突出,实时性也得不到保证。MFC是基于Window32的一个基础类库,封装了很多Win32的API,方便开发者使用,但它也是有缺点的,似乎也没再更新。Win32是这三者中最底层的一个,编译出的程序小,没有额外的包袱,运行起来快,所以开发的难度自然就大了,代码量也很大。我们在开发应用程序时应根据实际情况选择更合适的。 

世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。

可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是 Unicode,就像它的名字都表示的,这是一种所有符号的编码

历史上存在两个试图独立设计 Unicode 的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO 开发了 ISO 10646 项目,Unicode 协会开发了 Unicode 项目。

在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从 Unicode2.0 开始,Unicode 项目采用了与 ISO 10646-1 相同的字库和字码。

目前两个项目仍都存在,并独立地公布各自的标准。Unicode 协会现在的最新版本是2005年的 Unicode 4.1.0。ISO 的最新标准是 10646-3:2003。

Unicode 是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E00表示汉字"一"。具体的符号对应表,可以查询 unicode.org,或者专门的汉字对应表。

Unicode的问题

需要注意的是,Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储

比如,汉字"一"的 unicode 是十六进制数4E00,转换成二进制数足足有15位(100111000000000),也就是说这个符号的表示至少需要2个字节。而表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

这里就有两个的问题,一个是,如何才能区别 unicode ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储空间来说是极大的浪费,文本文件的大小会因此大出二三倍,这是难以接受的。

它们造成的直接结果是:出现了unicode 的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示 unicode 。另外 unicode 在很长一段时间内无法推广,直到互联网的出现。

网络上流行的utf-8就是unicode编码的一类应用.

如何查询 Unicode 编码

在 Windows 系统下,你可以在运行栏输入 "eudcedit.exe"调用 TrueType 造字程序,在其中的窗口--参照页,在"代码"栏输入 Unicode 编码可以查找到相应的字符;在"形状"栏输入字符则可以查找到相应的 Unicode 编码 。

Debug   和   Release  编译方式的本质区别   Debug   通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。Release  称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。

1 Windows CE驱动介绍

    驱动程序是介于操作系统和设备之间的一个代码层,它的主要作用是为操作系统提供一个接口,以操作不同的硬件,包括物理的和虚拟的设备。虽然驱动程序有很多种,但从编程的角度来看,无非是往一个固定的框架中添加相应的代码。这里的框架指的是一个接口,面向操作系统。代码实现的宗旨是,在正确的时间往正确的寄存器中写正确的值

    驱动程序的分类,从不同的角度有不同的分法。拿串口驱动来说,你可以说它是一个分层驱动,你也可以说它是一个流驱动,你还可以说它是开机时自动加载的驱动……这似乎有点乱。如果你也这么认为,那建议往下看。如果这些你都了如指掌,那就不浪费时间了,当然,您愿意找茬,我会很感谢!

    先说本地驱动(Native Drivers)和流驱动(Stream Drivers)。WinCE下的驱动都可以归类到这两个里面,二者必居其一。这是从驱动程序提供给操作系统的接口来区分的。流驱动为操作系统提供了流接口函数,如XXX_Init()、XXX_Open()、XXX_Read()、XXX_Write()、XXX_Close()等等。这一类的驱动Device Manager来管理,它调用ActivateDeviceEx()函数来加载流驱动。ActivateDeviceEx()的参数是注册表中相应的键,用来设定加载流驱动的属性,如Index、Order、Prefix等等。流驱动的注册表配置信息一般存放在[HKEY_LOCAL_MACHINE\Drivers\BuiltIn]下。流驱动加载成功后,应用程序通过调用CreateFile()ReadFile()WirteFile()等来访问流驱动的设备。流驱动可以动态管理,驱动调试助手就是用来帮助调试这一类驱动的。

与流驱动相反,本地驱动提供给操作系统的不是标准的流接口,而是事先约定好的特定接口。不同的设备,接口也不一样。WinCE中,常见的本地驱动有LCD显示驱动、触摸屏驱动、鼠标和键盘驱动及打印机驱动等。可以看到,本地驱动主要是人机界面相关的驱动。它们GWES管理,在系统启动时加载。他们在注册表中也有各自相应的配置信息。如键鼠的注册表配置如下:

[HKEY_LOCAL_MACHINE"System"CurrentControlSet"Control"Layouts"00000409]

"LayoutFile"="kbdmouse.dll"

"LayoutText"="US"

"PS2_AT"="kbdmouse.dll"

"Matrix"="kbdmouse.dll"

本地驱动由操作系统调用,应用程序不能访问。对于这类驱动,驱动调试助手是无能为力的,只能老老实实的编译、下载、验证。

WinCE驱动中经常会听到MDD(Model Device Driver)和PDD(PlatformDependent Driver)的概念,这是从驱动代码实现的结构来区分的。WinCE的驱动可以是单层的,也可以是PDD+MDD。这没有硬性规定,一个驱动程序可以采用分层结构,也可以采用单层结构。一般来说,单层结构的驱动执行效率更高,而分层结构的驱动方便代码维护和移植。拿串口驱动来说,完全可以采用单层结构。而把它分为PDD和MDD,作为一般的开发者,我们只需实现PDD层就可以了,MDD层由微软实现。这样,驱动开发的工作量少很多,而代码的可靠性则有了更好的保证。至于采用哪一种结构的驱动,主要看你的需求。

WinCE 6.0引入了内核态驱动和用户态驱动的概念。在WinCE5.0及先前的版本中,驱动工作在用户态。从代码方面看,内核态驱动和用户态驱动没太大差别。如果驱动中没有采用什么特别的技术,内核态驱动和用户态驱动甚至是二进制兼容的。内核态驱动被加载到内核空间,用户态驱动被加载到特定的用户进程空间中。从执行效率来看,内核态的驱动效率比用户态的驱动高。从稳定性方面考虑,用户态的驱动不会对系统产生致命影响,而内核态的驱动相对危险。同样,采用哪一种类型的驱动,也是看你的需求。

从驱动加载的时间来看,可分为两种:系统启动时加载和需要时加载。一般来说本地驱动都是在启动时加载的,所以这里说的主要是流驱动。如果想要驱动在系统启动时加载,只需将它的注册表配置信息放到[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\]下,如[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\Battery],系统启动时,Device Manager会自动加载它。需要时加载,顾名思义,就是想加载就加载,想卸载就卸载,很灵活。这里很有必要说一下USB设备的驱动加载,如USB摄像头驱动,它也属于需要时加载的驱动。从驱动的接口来看,它属于流驱动,但相对普通的流驱动,它增加了几个函数:USBDeviceAttach()、USBInstallDriver()、USBUnInstallDriver()等。USB摄像头驱动的加载在USBDeviceAttach()中完成。所以,它无须,也不能,用驱动调试助手加载。需要时加载的驱动还有一个作用,在无法修改系统的情况下,应用程序中动态加载该驱动,以完成对硬件的操作。

综上所述,WinCE驱动的分类,主要有以下几种分法:

按驱动接口分,可分为本地驱动和流驱动;

按驱动结构分,可分为单层驱动和分层驱动;

按驱动加载的空间分,可分为内核态驱动和用户态驱动;

按驱动加载的时间分,可分为启动时加载和需要时加载两种。

此文档主要针对流接口驱动进行详细介绍,结合我在6410开发板上实现的PWM驱动来详细介绍如何在Windows CE中编写一个流接口驱动。

2 流接口驱动介绍

在计算机系统中,很多硬件设备都在不断的制造或使用二进制制数据,这些设备可以被抽象成流式设备。流式设备的最典型例子是串口。我们在使用串口的时候,二进制数据会像流水一样,从一台设备经过串口线流到另外一台设备上。应用程序使用文件 API对设备进行访问文件 API都会被操作系统转发到FileSys.exe进程中FileSys.exe发现是对设备的操作,然后就会把执行交给设备管理器处理设备管理器会根据具体的请求,调用不同的流式接口驱动程序中暴露的接口。最终,驱动程序会负责与硬件交互

2.1 流式接口函数

通过前面的内容我们知道,应用程序通过操作系统对流式接口驱动程序进行访问。对于操作系统而言,最基本的文件操作原语有 open, close, read,write, seek 五个,分别用来对文件进行打开、关闭、读取、写入和移动文件指针操作。但是对于驱动程序,仅有这五个基本的操作原语显然是不够的,驱动程序还需要加载/卸载等附加操作。因此,Windows CE中定义的流式接口函数有 12有些函数是直接与某个文件操作 API对应的,而有些函数是为了某些特殊的目的,例如电源管理函数。流式接口函数的列表如下:

函数名称

XXX_Colse

驱动程序关闭时,应用程序通过CloseHandler()函数调用这个函数

XXX_Deinit

当设备管理器卸载一个驱动程序时调用这个函数

XXX_ Init

当设备管理器初始化一个具体的设备式调用这个函数

XXX_IOControl

应用程序通过DeviceControl()函数可以调用这个函数

XXX_Open

打开一个设备驱动程序时,应用程序通过CreateFile()函数调用这个函数

XXX_PowerDown

在系统挂起前调用这个函数

XXX_PowerUp

在系统重新启动时调用这个函数

XXX_Read

在一个设备驱动程序处于打开状态时,由应用程序通过ReadFile()函数调用这个函数

XXX­_Seek

对设备的数据指针进行操作时,由应用程序通过SetFilePointer()函数调用这个函数

XXX_Write      

在一个设备驱动程序处于打开状态时,由应用程序通过WriteFile()调用这个函数

XXX_PreClose

通知驱动程序把打开的句柄设置为无效,而避免某些竞态。

XXX_PreDeinit

通知程序把设备句柄设置为无效,而避免某些竞态。

其中 XXX 是驱动程序的设备名称,例如对于串口驱动程序,串口的设备名称是 COM,因此,XXX_Open 在串口当中就被替换成了 COM_Open。驱动程序的设备名称我们可以在注册表中指定。

2.2 流接口函数具体介绍

下面具体来介绍上面的接口函数

1、  XXX_Init 与 XXX_Deinit

XXX_Init该函数在DllEntry后被调用. 但不是被应用程序调用,而是在一定的情况下被设备管理器调用. 那么"一定情况下"是指什么呢. 是指当用户开始用一个设备时,设备管理器调用该函数初始化设备. 当要实现多个流接口驱动的实例时(串口是个典型的例子, COMx就是COM的多个实例),XXX_Init应该被调用多次。该函数要返回设备句柄来为其它的接口函数能使用,XXX_Deinit是设备被卸载时调用。函数声明如下:

// 初始化设备,在设备被加载的时候调用,返回设备的上下文句柄

DWORD XXX_Init(

LPCTSTR pContext,     // 字符串,指向注册表中记录活动驱动程序的键

LPCVOID lpvBusContext  //ActivateDeviceEx函数的第四个参数,VOID 指针

);

// 释放设备,在设备被卸载的时候调用,返回设备卸载是否成功

BOOL XXX_Deinit(

DWORD hDeviceContext         //XXX_Init函数返回的设备上下文

);

2、 XXX_Open 与 XXX_Close

XXX_Open与 XXX_Close分别在用户调用CreateFileCloseHandle时被调用. XXX_Open的参数hDeviceContext是由XXX_Init返回的值.是一个指向设备context的句柄. 另外, 用CreateFile打开设备时要用”XXX1:”的形式. 后面的数字根据注册表active下的键值确定。这两个函数的声明如下:

// 打开设备进行读写,返回设备的打开上下文

DWORD XXX_Open(

DWORD hDeviceContext,    // 设备上下文,由 XXX_Init 函数创建

DWORD AccessCode,        // 设备的访问模式,从 CreateFile 函数传入

DWORD ShareMode          // 设备的共享模式,从 CreateFile 函数传入

// 关闭设备,返回设备关闭是否成功

BOOL XXX_Close(

DWORD hOpenContext       //设备的打开上下文,由XXX_Open 函数返回

);

通常,在XXX_Open 函数中,需要为设备申请资源,一般是一些用于管理设备的数据结。而在 XXX_Close 函数中,这些数据结构可以被释放。

3、XXX_Read、XXX_Write 与XXX_Seek

对于设备的主要操作都是通过 ReadFile()WriteFile()SetFilePointer()函数进行的,他们负责对设备进行读、写和移动当前指针。在流式接口驱动层面,XXX_Read、XXX_Write与XXX_Seek 三个函数就提供了对这些操作的支持。这三个函数的声明如下所示:

// 从设备中读取数据,返回 0 表示文件结束,返回-1 表示失败,返回读取的字节数表示成功

DWORDXXX_Read(

DWORD hOpenContext,// XXX_Open 返回的设备打开上下文

LPVOID pBuffer, // 输出,缓冲区的指针,读取的数据会被放在该缓冲区内

DWORDCount// 要读取的字节数

);

// 向设备中写入数据,返回-1 表示失败,返回写入的字节数表示成功

DWORDXXX_Write(

DWORD hOpenContext,// XXX_Open 返回的设备打开上下文

LPCVOID pBuffer, // 输入,指向要写入设备的数据的缓冲

DWORDCount// 缓冲中的数据的字节数

);

// 移动设备中的数据指针,返回数据的新指针位置,-1 表示失败

DWORDXXX_Seek(

DWORD hOpenContext,// XXX_Open 返回的设备打开上下文

longAmount, // 要移动的距离,负数表示前移,正数表示后移

WORDType// 移动的相对位置,有FILE_BEGIN、FILE_CURRENT 和FILE_END

);

4、XXX_IOControl

XXX_IOControl是当用户DeviceIOControl定义要完成的操作时, 系统用该函数. dwCode是操作码,特定于不同的设备.可通过头文件传给应用程序. 这两个参数是必须的, 其它几个可为NULL, pBufIn输入buffer, pBufOut是输出buffer. 输入输出是相对设备的, pdwActualOut是实际输出的字节数. 这个函数的功能比较强大,为什么这么说呢, 因为它的上层DeviceIOControl比较强大, 事实上,DeviceIOControl可以完成几乎所有的操作其中就包括读写操作,函数的原形如下:

// 向驱动程序发送控制命令

BOOL XXX_IOControl(

DWORD hOpenContext, // 由 XXX_Open 返回的设备打开上下文

DWORD dwCode, // 要发送的控制码,一个 32 位无符号数

PBYTE pBufIn, // 输入,指向输入缓冲区的指针

DWORD dwLenIn, // 输入缓冲区的长度

PBYTE pBufOut, // 输出,指向输出缓冲区的指针

DWORD dwLenOut, // 输出缓冲区的最大长度

PDWORD pdwActualOut// 输出,设备实际输出的字节数

);

对于 XXX_IOControl 函数,在流式接口中应用非常广泛,一些不适合于像文件一样进行读写的设备,都可以通过 XXX_IOControl 函数来控制。

5、 XXX_PowerUp 与 XXX_PowerDown

XXX_PowerDown与XXX_PowerUp这两个函数, 是在系统掉电与上电时执行的, 所以很显然,你不能调用它. 它是在内核模式下执行的. 为了避免死机, 这两个函数最好什么都不要做,尽快返回。

6、XXX_PreClose 与 XXX_PreDeinit

这两个函数是在Windows CE 5.0 中新引入的函数,目的是为了防止多线程操作时可能引发的一些竞态(Race Condition)。这两个函数都是可选的,但是如果驱动实现了其中的一个,那么也必须实现另外一个,否则驱动就无法被加载。它们的函数原形如下:

BOOLXXX_PreClose(

DWORDhOpenContext// 设备的打开上下文

);

BOOLXXX_PreDeinit(

DWORDhDeviceContext // 设备的上下文

);

设备管理器在对设备进行管理的时候,对于对 XXX_Init、XXX_Deinit、XXX_Open 和

XXX_Close 的调用,设备管理器内部会维持一个全局的 Critical Section 来保证操作的原子性,但是出于效率的考虑,在调用 XXX_Read、XXX_Write、XXX_Seek 与 XXX_IOControl 的时候,并没有对这些调用加锁,这就导致了引发竞态的可能。

设想这样一种情况,系统中有多个线程同时访问某个驱动程序,当某个线程调用了XXX_Read 来读取打开的设备的时候,另外一个线程调用了 XXX_Close 关闭了打开的设备,因为正如前文所述,设备管理器对 XXX_Read 的调用是没有 Critical Section 的保护的,因此有可能调用 XXX_Read 的线程在没有执行完之前就被执行 XXX_Close 的线程抢占,并且关闭了设备,那么当 XXX_Read 的线程重新占有处理器的时候,它所要读取的设备已经被关闭了,这极有可能导致驱动程序崩溃。同样的问题也发生在设备卸载的时候。如图所示:

添加了这两个函数之后,就可以有效防止前面的情况发生。XXX_PreClose 在设备管理器调用 XXX_Close 之前被调用,在这个函数里,驱动程序需要唤醒在等待对设备进行操作的线程,并且释放申请的资源。这时,还会把 hOpenContext 设置为无效,因此后面的对设备的访问都会直接返回失败,而不会使整个驱动程序崩溃。同样 XXX_PreDeinit 在设备管理器调用 XXX_Deinit 之前被调用,可以防止应用程序打开一个已经被卸载的设备

2.3 流接口函数调用示例

Windows CE里任何暴露了流式接口函数的驱动程序都可以被称作流式接口驱动程序(也就是在驱动程序的 DLL 中把这些函数作为 DLL的导出函数)。在流式接口驱动程序中,驱动程序负责把外设抽象成一个文件,而应用程序则可以使用操作系统提供的文件 API 对外设进行访问。举例来说,串口驱动程序是典型的流式驱动,如果应用程序希望打开串口,则可以通过如下的语句:

ANDLE hComm;                                            

hComm = CreateFile (TEXT("COM1:"), GENERIC_READ |GENERIC_WRITE,

0, NULL, OPEN_EXISTING, 0, NULL);          

if (hComm == INVALID_HANDLE_VALUE)                        

// error opening port; abort                                        

CreateFile()函数的第一个参数不再是文件名称,而是驱动程序的名字,在这个例子中为“COM1”。同样的道理如果需要向串口写入数据,只需要使用 WriteFile()函数,代码如下:

INT rc;

DWORD cBytes;

BYTE ch;

ch = TEXT ('a');

rc = WriteFile(hSer,&ch, 1, &cBytes, NULL);

当然如果想要读取串口的数据可以调用WriteFile()函数。

2.4 流驱动加载过程

下面是流驱动加载过程

(1)加载驱动。在当系统启动时设备管理器搜寻注册HKEY_LOCAL_MACHINE\Driver\BuiltIn键下面的子键,并逐一加载子键下的每个驱动,此过程叫BusEnum。

(2)设备管理器从注册表的dll键值获取驱动程序所在的DLL文件名

(3)设备管理器调用LoadDriver()函数DLL加载到自己的虚拟地址空间内。

(4)设备管理器在注册表的HKEY_LOCAL_MACHINE\Driver\Active下面,记录所有已经加载的驱动程序[2]。

(5)设备管理器调用驱动中的XXX_Init()函数。

(6)在XXX_Init()中,通常对硬件进行一些基本的初始化操作。通过以上6步,流接口驱动被成功加载。

(7)应用程序使用该设备。首先它调用CreateFile()打开设备。CreateFile()是在FileSys.exe中实现的。但是FileSys.exe只作简单判断,如果发现打开的设备驱动程序而不是一个文件,那么就重新把主动权交还给设备管理器。

(8)设备管理器调用驱动程序中的XXX_Open()函数打开设备。在XXX_Open()中,驱动程序可能会对硬件进行一些额外的初始化工作,使硬件进入工作状态。

(9)XXX_Open()函数把打开设备的结果返回给设备管理器。

(10)设备管理器把XXX_Open()返回的结果,再返回给应用程序的CreateFile()函数调用。通过7-10步,设备已被成功打开,至此就可以对设备进行读写和控制操作。

(11)应用程序使用第7步CreateFile()函数返回的句柄作为 ReadFile() / WriteFile()的第一个参数,向设备发送读请求。同样ReadFile()/WriteFile()要经过FileSys.exe转发给设备管理器

(12)设备管理器调用驱动程序中的XXX_Read() /XXX_Write() 函数,读取设备的数据信息或向设备写信息。

(13)在流驱动程序中,XXX_Read() / XXX_Write() 函数可与硬件交互,从硬件中读取必要的信息或向硬件写必要的信息。然后返回给设备管理器,再返回给应用程序。

当应用程序不再使用该设备时,它可调用CloseHandle()将设备关闭。当系统不再使用设备时,应用程序可调用DeactivateDevice()函数把该驱动程序卸载。

2.5 PWM流式接口驱动的实现

    至此,我以我在6410 开发板上实现的PWM驱动程序为例,介绍如何实现流式接口驱动程序。

实现流式接口驱动程序通常只需四个步骤

1、为流式接口驱动程序选择一个前缀。

2、实现流式接口驱动 DLL 所必需的接口函数。

3、编写 DLL 的导出函数定义文件.DEF。

4、为驱动程序配置注册表

正如前文介绍,应用程序通常需要通过设备的名称来对驱动程序进行访问。这里我们采用由三个大写的英文字母,然后加一个 0 9 之间的数字构成的传统方式命名。PWM驱动的前缀定义为“PWM”。下面就需要为步进电机编写代码了,这一步可以在 Platform Builder 或者 eMbeddedVisual C++或者Visual Studio 2005 中进行。Windows CE 的驱动程序就是一个用户态的 DLL,因此,任何可以编写Windows CE DLL 的工具都可以用来开发驱动程序。我们以使用 PlatformBuilder 为例,介绍开发驱动程序的过程。

1、我使用的是Windows CE 6.0的开发环境,首先要到我们进入选定的BSP的驱动文件夹中创建一个自己的驱动文件夹

2、进入设备驱动的目录C:\WINCE600\PLATFORM\SMDK6410\SRC\DRIVERS,创建一个名为My_PWM的文件夹。

3、打开当前路径下的dirs文件并添加我们刚创建的文件夹名称,这样在编译驱动的时候我们的驱动程序就可以编译到系统中去了,如图2.5所示:

4、进My_PWM文件夹,创建一个.cpp文件,命名为MyPWMDriver。此文件为驱动程序的源文件,主要用于实现标准的流接口函数。因为驱动的前缀被定义为“PWM”,因此在流式驱动程序中就必须实现一些 PWM打头的函数。首先在MyPWMDriver.cpp中添加必要的头文件:

#include<ceddk.h>

#include<nkintr.h>

#include<pm.h>

#include<DrvLib.h>

#include<s3c6410_gpio.h>

#include<s3c6410_base_regs.h>

#include<s3c6410_pwm.h>

#include"pmplatform.h"

#include"Pkfuncs.h"

#include"ioctl_cfg.h"

#define Start 2;                         //宏定义,用于启动PWM输出

#define Stop  3;                       //宏定义,用于关闭PWM输出

然后,增加 GPIO 端口寄存器和PWM寄存器的声明

staticvolatile S3C6410_GPIO_REG * g_pGPIOReg = NULL;        //指向IO地址块的指针

staticvolatile S3C6410_PWM_REG * g_pPWMReg = NULL;       //指向定时控制器块的指针

接下来要先实现驱动程序的入口函数DllEntry,这个函数是动态链接库的入口,每个动态链接库都需要输出这个函数,它只在动态库被加载和卸载时被调用,也就是设备管理器调用LoadLibrary而引起它被装入内存和调用UnloadLibrary将其从内存释放时被调用,因而它是每个动态链接库最早被调用的函数,一般用它做一些全局变量的初始化。函数实现如下:

BOOLWINAPI

DllEntry(HANDLE   hinstDLL,

           DWORD dwReason,

           LPVOID /* lpvReserved*/)

{

    switch(dwReason)

    {

    case DLL_PROCESS_ATTACH:

      DEBUGREGISTER((HINSTANCE)hinstDLL);

       return TRUE;

    case DLL_THREAD_ATTACH:

       break;

    case DLL_THREAD_DETACH:

       break;

    case DLL_PROCESS_DETACH:

       break;

#ifdefUNDER_CE

    case DLL_PROCESS_EXITING:

       break;

    case DLL_SYSTEM_STARTED:

       break;

#endif

    }

    return TRUE;

}

下面要就要具体的实现几个流式接口了。首先,是 PWM_Init 和 PWM_Deinit 两个函数。这两个函数在驱动程序加载和卸载的时候被调用,在这两个函数中通常放置一些初始化工作代码,我们在这两个函数里面做一些相关的物理寄存器映射工作。

DWORDPWM_Init(DWORD dwContext)

{

   RETAILMSG(1,(TEXT("My_PWM_Init----\r\n")));

Virtual_Alloc(); //映射物理寄存器到虚拟空间,因为WinCE使用的是虚拟地址,

//此函数也是在当前文件实现

    PWMInit();       // 配置PWM端口

    return TRUE;

}

BOOLPWM_Deinit(DWORD hDeviceContext)

{

    BOOL bRet = TRUE;

    if (g_pGPIOReg) {

      DrvLib_UnmapIoSpace((PVOID)g_pGPIOReg); //释放虚拟地址空间

    }

    if (g_pPWMReg) {

      DrvLib_UnmapIoSpace((PVOID)g_pPWMReg); //释放虚拟地址空间

    }

    g_pGPIOReg = NULL;

    g_pPWMReg = NULL;

   RETAILMSG(1,(TEXT("USERPWM:PWM_Deinit\r\n")));

    return TRUE;

}

接下来是PWM_IOControl()函数,此函数也是本驱动的关键函数,由应用程序的DeviceIOControl()调用,实现指令和数据的传递:

BOOL(DWORD hOpenContext,

                DWORD dwCode,

                PBYTE pBufIn,

                DWORD dwLenIn,

                PBYTE pBufOut,

                DWORD dwLenOut,

                PDWORDpdwActualOut)

{

    unsigned char temp[4];                 //用于数据接受的临时数组

    DWORD Freq,dutyfactor;

   RETAILMSG(1,(TEXT("My_PWM_IOControl----\r\n")));

    temp[0] = pBufIn[0];                   //数据接收

    temp[1] = pBufIn[1];

    temp[2] = pBufIn[2];

    temp[3] = pBufIn[3];

    //memcpy(&temp,&pBufIn, dwLenIn);

    Freq = temp[3]*256 +temp[2];          //获取应用程序传递过来的频率值

    dutyfactor = temp[1]*256 +temp[0];    //获取应用程序传递过来的占空比的//

    switch(dwCode)

    {

    case 2:

      {    

PWMSet(Freq,dutyfactor );     //此函数在本文件中实现,用于根据所获得

//的值设置相应的控制寄存器,以实现我们

//所需要的PWM输出

       break;

       }

    case 3:

       PWMStop();                      //关闭PWM输出

       break;

    default:

       break;   

    }

   RETAILMSG(1,(TEXT("PWM:Ioctl code =0x%x\r\n"), dwCode));

    return TRUE;

}

剩余的几个流式接口函数不进行任何实质性的操作,只是实现了一个框架,仅仅输出一些调试信息,以便观查调用:

DWORD PWM_Open(DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode)

{

   RETAILMSG(1,(TEXT("USERPWM:PWM_Open\r\n")));

    return TRUE;

}

//-------------------------------------------------------------------------

BOOL PWM_Close(DWORD hOpenContext)

{

   RETAILMSG(1,(TEXT("USERPWM:PWM_Close\r\n")));

    return TRUE;

}

//-------------------------------------------------------------------------

Void PWM_PowerDown(DWORD hDeviceContext)

{

   RETAILMSG(1,(TEXT("USERPWM:PWM_PowerDown\r\n")));

}

//-------------------------------------------------------------------------voidPWM_PowerUp(DWORDhDeviceContext)

{

   RETAILMSG(1,(TEXT("USERPWM:PWM_PowerUp\r\n")));

}

//-------------------------------------------------------------------------

DWORD PWM_Read(DWORD hOpenContext, LPVOID pBuffer, DWORD Count)

{

    RETAILMSG(1,(TEXT("USERPWM:PWM_Read\r\n")));

    return TRUE;

}

//-------------------------------------------------------------------------

DWORD PWM_Seek(DWORD hOpenContext, long Amount, DWORD Type)

{

   RETAILMSG(1,(TEXT("USERPWM:PWM_Seek\r\n")));

    return 0;

}

//-------------------------------------------------------------------------

DWORDPWM_Write(DWORD hOpenContext, LPCVOID pSourceBytes, DWORDNumberOfBytes)

{

   RETAILMSG(1,(TEXT("USERPWM:PWM_Write\r\n")));

    return 0;

}

这样,所有流式接口驱动程序的导出函数就实现完毕了。但是现在还不能进行编译。我们知道,如果要在DLL 中导出一个函数,有两种方法,一种是使用编译器扩展关键字__declspec(dllexport),如果采用这种关键字,还要注意 C++编译器会对函数名称进行修饰,因此还要加上 extern C”。另外一种更为简便的方式是使用.DEF文件

2.6 编写 DLL 的导出函数定义文件.DEF

.DEF 文件定义了 DLL的导出函数列表。在My_PWM文件夹中插入一个文本文件,命

名为MyPWMDriver.def,然后在该文件中输入如下内容:

LIBRARY pwm

EXPORTS

    PWM_Close

    PWM_Deinit

    PWM_Init

    PWM_IOControl

    PWM_Open

    PWM_PowerDown

    PWM_PowerUp

    PWM_Read

    PWM_Seek

    PWM_Write

添加Makefile文件

    在此文件夹下新建一个记事本文件添加如下内容:

!INCLUDE$(_MAKEENVROOT)\makefile.def

然后重命名为makefile即可。

添加Source文件

   在此文件夹下新建一个记事本文件并添加如下内容:

SYNCHRONIZE_DRAIN=1

RELEASETYPE=PLATFORM

TARGETNAME=MyPWMDriver

TARGETTYPE=DYNLINK

SOURCES=MyPWMDriver.cpp

TARGETLIBS=\

  $(_COMMONSDKROOT)\lib\$(_CPUINDPATH)\coredll.lib    \

  $(_TARGETPLATROOT)\lib\$(_CPUINDPATH)\DriverLib.lib

INCLUDES_PATH=$(_TARGETPLATROOT)\src\drivers\MyPWMDriver

    然后另存为source即可。Source文件将会告诉编译器要编译的源文件有哪些,要连接的库文件有哪些,以及生成的文件类型和文件名

    至此,我们的驱动程序编写完毕,接下来我们还需要修改一下配置文件和注册表,让系统加载我们的驱动程序。

修改配置文件和注册表

1、修改配置文件

打开platform.bib文件,在文件中加入如下代码:

$(_FLATRELEASEDIR)\           NK   SH

这一步的工作是把前面生成的dll加进wince的系统内核中。

2、修改注册表

打开platform.reg文件,在文件中加入如下代码:

[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\MyPWMDriver]

  "Prefix"="PWM"

  "Dll"="MyPWMDriver.dll"

  "Order"="200"

然后要做的工作就是编译我们的驱动程序到内核中,并验证我们的驱动是否有效,接下来就是一系列的调试工作。

那么驱动是什么呢?驱动的英文就是Driver,简单的说来驱动程序就是用来向操作系统提供一个访问、使用硬件设备的接口,实现操作系统和系统中所有的硬件设备的之间的通信程序,它能告诉系统硬件设备所包含的功能,并且在软件系统要实现某个功能时,调动硬件并使硬件用最有效的方式来完成它。

说的形象一点,驱动程序就是软件与硬件之间的“传令兵”,这个环节可是大大的重要,一旦出现了问题,那么软件提出的要求就要无人响应,而硬件却空有一身力气但无从发挥,那种状况下朋友们会发现自己那本来性能强大且多姿多彩的电脑竟然如同一洼死水,什么都做不来了。因此有人说驱动是硬件的灵魂,可毫不为过。

DLL文件Dynamic Link Library 又称“应用程序拓展”,是软件文件类型。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。一个应用程序可使用多个DLL文件,一个DLL文件也可能被不同的应用程序使用,这样的DLL文件被称为共享DLL文件。在 Windows操作系统中,每个程序都可以使用该 DLL 中包含的功能来实现“打开”对话框。这有助于促进代码重用和内存的有效使用。

通过使用 DLL,程序可以实现模块化,由相对独立的组件组成。例如,一个记账程序可以按模块来销售。可以在运行时将各个模块加载到主程序中(如果安装了相应模块)。因为模块是彼此独立的,所以程序的加载速度更快,而且模块只在相应的功能被请求时才加载。

     此外,可以更为容易地将更新应用于各个模块,而不会影响该程序的其他部分。例如,您可能具有一个工资计算程序,而税率每年都会更改。当这些更改被隔离到 DLL 中以后,您无需重新生成或安装整个程序就可以应用更新。Windows操作系统中的一些作为 DLL 实现的文件

·ActiveX 控件 (.ocx) 文件

ActiveX控件的一个示例是日历控件,它使您可以从日历中选择日期。

·控制面板 (.cpl) 文件

.cpl 文件的一个示例是位于控制面板中的项。每个项都是一个专用 DLL。

·设备驱动程序(.drv) 文件

设备驱动程序的一个示例是控制打印到打印机的打印机驱动程序

一、 使用较少的资源

当多个程序使用同一个函数库时,DLL 可以减少在磁盘和物理内存中加载的代码的重复量。这不仅可以大大影响在前台运行的程序,而且可以大大影响其他在 Windows操作系统上运行的程序。

二、 推广模块式体系结构

DLL 有助于促进模块式程序的开发。这可以帮助您开发要求提供多个语言版本的大型程序或要求具有模块式体系结构的程序。模块式程序的一个示例是具有多个可以在运行时动态加载的模块的计帐程序。

三、 简化部署和安装

当 DLL 中的函数需要更新或修复时,部署和安装 DLL 不要求重新建立程序与该 DLL 的链接。此外,如果多个程序使用同一个 DLL,那么多个程序都将从该更新或修复中获益。当您使用定期更新或修复的第三方 DLL 时,此问题可能会更频繁地出现。

1、如何了解某应用程序使用哪些DLL文件

右键单击该应用程序并选择快捷菜单中的“快速查看”命令,在随后出现的“快速查看”窗口的“引入表”一栏中你将看到其使用DLL文件的情况。

2、如何知道DLL文件被几个程序使用

运行Regedit,进入HKEY_LOCAL_MACHINESoftwareMicrosrftWindowsCurrentVersionSharedDlls子键查看,其右边窗口中就显示了所有DLL文件及其相关数据,其中数据右边小括号内的数字就说明了被几个程序使用,(2)表示被两个程序使用,(0)则表示无程序使用,可以将其删除。

3、如何解决DLL文件丢失的情况

有时在卸载文件时会提醒你删除某个DLL文件可能会影响其他应用程序的运行。所以当你卸载软件时,就有可能误删共享的DLL文件。一旦出现了丢失DLL文件的情况,如果你能确定其名称,可以在Sysbckup(系统备份文件夹)中找到该DLL文件,将其复制到System文件夹中。如果这样不行,在电脑启动时又总是出现“***dll文件丢失……”的提示框,你可以在“开始/运行”中运行Msconfig,进入系统配置实用程序对话框以后,单击选择“System.ini”标签,找出提示丢失的DLL文件,使其不被选中,这样开机时就不会出现错误提示了。

rundll的功能是以命令列的方式呼叫Windows的动态链接库。

Rundll32.exe与Rundll.exe的区别就在于前者是用于32位的链结库,后者是用于16位的链结库。rundll32.exe是专门用来调用dll文件的程序。

如果用的是Win98,rundll32.exe一般存在于Windows目录下;

如果用的WinXP、Win7,rundll32.exe一般存在于Windows\System32目录下。

若是在其它目录,就可能是一个木马程序,它会伪装成rundll32.exe。

4链接方法

当您在应用程序中加载 DLL,可以使用两种链接方法来调用导出的 DLL 函数。这两种链接方法是加载时动态链接和运行时动态链接。

在运行时动态链接中,应用程序调用 LoadLibrary 函数或 LoadLibraryEx 函数以在运行时加载 DLL。成功加载 DLL 后,可以使用 GetProcAddress 函数获得要调用的导出的 DLL 函数的地址。在使用运行时动态链接时,无需使用导入库文件。

Win32 DLL的特点

Win32 DLL与 Win16 DLL有很大的区别,这主要是由操作系统的设计思想决定的。一方面,在Win16 DLL中程序入口点函数和出口点函数(LibMain和WEP)是分别实现的;而在Win32 DLL中却由同一函数DLLMain来实现。无论何时,当一个进程或线程载入和卸载DLL时,都要调用该函数,它的原型是

BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason, LPVOIDlpvReserved);

其中,第一个参数表示DLL的实例句柄;第三个参数系统保留;这里主要介绍一下第二个参数,它有四个可能的值:DLL_PROCESS_ATTACH(进程载入),DLL_THREAD_ATTACH(线程载入),DLL_THREAD_DETACH(线程卸载),DLL_PROCESS_DETACH(进程卸载),在DLLMain函数中可以对传递进来的这个参数的值进行判别,并根据不同的参数值对DLL进行必要的初始化或清理工作。举个例子来说,当有一个进程载入一个DLL时,系统分派给DLL的第二个参数为DLL_PROCESS_ATTACH,这时,你可以根据这个参数初始化特定的数据。另一方面,在Win16环境下,所有应用程序都在同一地址空间;而在Win32环境下,所有应用程序都有自己的私有空间,每个进程的空间都是相互独立的,这减少了应用程序间的相互影响,但同时也增加了编程的难度。大家知道,在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的;而在Win32环境中,情况却发生了变化,当进程在载入DLL时,系统自动把DLL地址映射到该进程的私有空间,而且也复制该DLL的全局数据的一份拷贝到该进程空间,也就是说每个进程所拥有的相同的DLL的全局数据其值却并不一定是相同的。因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。亦即把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。

5故障排除

可以使用多个工具来帮助您解决 DLL 问题。以下是其中的部分工具。

Dependency Walker

Dependency Walker 工具可以递归扫描以寻找程序所使用的所有依赖 DLL。当您在 Dependency Walker 中打开程序时,Dependency Walker 会执行下列检查:

·Dependency Walker 检查是否丢失 DLL。

·Dependency Walker 检查是否存在无效的程序文件或 DLL。

·Dependency Walker 检查导入函数和导出函数是否匹配。

·Dependency Walker 检查是否存在循环依赖性错误。

·Dependency Walker 检查是否存在由于针对另一不同操作系统而无效的模块。

通过使用 Dependency Walker,您可以记录程序使用的所有 DLL。这可能有助于避免和更正将来可能发生的 DLL 问题。当您安装 Microsoft VisualStudio 6.0 时,Dependency Walker 将位于以下目录中:

drive\Program Files\Microsoft Visual Studio\Common\Tools

DLL Universal Problem Solver

DLL Universal Problem Solver (DUPS) 工具用于审核、比较、记录和显示 DLL 信息。下表说明了组成 DUPS 工具的实用工具:

·Dlister.exe:该实用工具枚举计算机中的所有 DLL,并且将此信息记录到一个文本文件或数据库文件中。

·Dcomp.exe:该实用工具比较在两个文本文件中列出的 DLL,并产生包含差异的第三个文本文件。

·Dtxt2DB.exe:该实用工具将通过使用 Dlister.exe 实用工具和 Dcomp.exe 实用工具创建的文本文件加载到 dllHell数据库中。

·DlgDtxt2DB.exe:该实用工具提供 Dtxt2DB.exe 实用工具的图形用户界面(GUI) 版本。

DLL影响

6文件修复

1、用Windows系统盘功能进行文件修复;

2、若在此之前有一键备份过,可以重新还原;

3、从网上下载系统文件然后覆盖到原文件夹里;

硬件接口为电脑等的信息机器的硬件之间通信时的物理连接器形状、发送接收信号的方法(协议)等等的规格。主要可分为并行链接的和比特序列链接的。序列链接者相比起并行链接者,多得多使用同一电线作为信号控制线和电源供应线。个人电脑领域,因并行链接向更高传输速度的发展遇到瓶项,而在向各接口的序列链接方式迁移(参看总线)。

泛用可热插拔(可以在机器电源开着时插拔)者

串口USB IEEE 1394以太网(100BASE 为止)ExpressCardeSATA并口以太网(1000BASE-T)

一般不可热插拔的普及接口,可能已有支持热插拔的实用产品。

并行SCSI IDEPCI序列PCI-ExpressSerial ATA

通常认为曾经泛用的遗产设备(旧世代的接口)。不包括PC卡中可热插拔的那些。

并行ISA并行端口(IEEE 1284 、 Centronics 规格兼容)PC卡序列PS/2RS-232非泛用、用途受限者序列MIDI - 电子乐器的控制并行GP-IB - 测量仪器的控制其他电源插座

软件接口[编辑]

软件间通信时传递消息(message)的规格。

API进程间通信电脑网络

接口 (程序设计) - 程序编写或设计的方法论中程序组件功能的抽象化物。

用户接口[编辑]

用户接口 - 人类与机器、设备、计算机程序或其他复杂工具交互的中介物的聚合。常用于电脑系统和电子设备文脉。

人机界面 - 机械系统、交通工具或工业设备的用户接口有时会指称为人机界面(Human-Machine Interface ,缩写为HMI)。

DLL:程序编制一般需经编辑、编译、连接、加载和运行几个步骤。在我们的应用中,有一些公共代码是需要反复使用,就把这些代码编译为“库”文件;在连接步骤中,连接器将从库文件取得所需的代码,复制到生成的可执行文件中。这种库称为静态库,其特点是可执行文件中包含了库代码的一份完整拷贝;缺点就是被多次使用就会有多份冗余拷贝。

为了克服这个缺点可以采用动态连接库。这个时候连接器仅仅是在可执行文件中打上标志,说明需要使用哪些动态连接库;当运行程序时,加载器根据这些标志把所需的动态连接库加载到内存。

另外在当前的编程环境中,一般都提供方法让程序在运行的时候把某个特定的动态连接库加载并运行,也可以将其卸载(例如Win32的LoadLibrary()&FreeLibrary()和Posix的dlopen()&dlclose())。这个功能被广泛地用于在程序运行时刻更新某些功能模块或者是程序外观。

API函数包含在Windows系统目录下的动态连接库文件中。Windows API是一套用来控制Windows的各个部件的外观和行为的预先定义的Windows函数。用户的每个动作都会引发一个或几个函数的运行以告诉Windows发生了什么。这在某种程度上很像Windows的天然代码。而其他的语言只是提供一种能自动而且更容易的访问API的方法。当你点击窗体上的一个按钮时,Windows会发送一个消息给窗体,VB获取这个调用并经过分析后生成一个特定事件。

更易理解来说:Windows系统除了协调应用程序的执行、内存的分配、系统资源的管理外,同时他也是一个很大的服务中心。调用这个服务中心的各种服务(每一种服务就是一个函数)可以帮助应用程序达到开启视窗、描绘图形和使用周边设备等目的,由于这些函数服务的对象是应用程序,所以称之为Application ProgrammingInterface,简称API 函数。WIN32API也就是MicrosoftWindows32位平台的应用程序编程接口

凡是在 Windows工作环境底下执行的应用程序,都可以调用Windows API。

首先,BSP(板级支持包,Board Support Packet)是一个支持特定标准开发板(SDB,Standed DevelopmentBoard)硬件的WinCE软件集成包,主要包括Boot Loader程序,OAL程序和板载硬件驱动程序

一个目标板的BSP开发主要有以下几个大的流程:

1.建立BootLoader,用来下载映像,启动系统。

2.编写OAL程序,用来引导系统核心映像和初始化、管理硬件。

3.为新的硬件编写硬件驱动。

4.设置平台配置文件,便于Platform Builder编译系统。

其中,Boot Loader 就是在操作系统内核运行之前运行的一段小程序,大家应该都很熟悉,或许以后还会再详细说一下,不明白的同学就去百度知道一下吧,而OALOEM适配层,OEMAdaptation Layer),它是BSP驱动的一部分,作用是让WinCEOEM的硬件上运行起来

l 、NOR的读速度比NAND稍快一些。

2、 NAND的写入速度比NOR快很多。

3 、NAND的4ms擦除速度远比NOR的5s快。

4 、大多数写入操作需要先进行擦除操作。

5 、NAND的擦除单元更小,相应的擦除电路更少。

此外,NAND的实际应用方式要比NOR复杂的多。NOR可以直接使用,并可在上面直接运行代码;而NAND需要I/O接口,因此使用时需要驱动程序。不过当今流行的操作系统对NAND结构的Flash都有支持。此外,Linux内核也提供了对NAND结构的Flash的支持。

用户配置文件就是在用户登录电脑时,或是用户在使用软件时,软件系统为用户所要加载所需环境的设置和文件的集合。它包括所有用户专用的配置设置,如程序项目、屏幕颜色、网络连接、打印机连接、鼠标设置及窗口的大小和位置等。

VC中如何彻底删除一个类     

在VC环境中进行编程时,有时需要将某个类删除掉,但在项目的ClassView中又却不能通过右键点击这个类直接删除,而需要到FileView中逐个删除*.h 和 *.cpp文件,但是工程目录中仍保留有这个类的文件及相关信息。    

通过搜索找到如下能够彻底删除一个类的方法:   

 1,打开工程,在FileView中删除这个类的相关 *.cpp *.h(用左击鼠标选中再按键盘的DELETE键就行了)    

2, 关闭工程,再删除工程目录中的*.cpp *.h文件,然后删除保留有这个类相关信息的*.ncb *.clw文件。    

  3, 重新打开工程,激活ClassWizard,出现提示框,点确定后,在弹出的对话框中单击Add   all,  Ok.     

  注:  *.dsp(DeveloperStudio Project):是VC++的工程配置文件,比如说你的工程包含哪个文件,你的编译选项是什么等等,编译的时候是按照.dsp的配置来的。

 *.dsw(DeveloperStudio Workspace):是工作区文件,用来配置工程文件的。它可以指向一个或多个.dsp文件。

  *.clw:是ClassWizard信息文件,实际上是INI文件的格式,有兴趣可以研究一下.有时候ClassWizard出问题,手工修改CLW文件可以解决.如果此文件不存在的话,每次用ClassWizard的时候会提示你是否重建。

 *.rc 称为资源文件, 其中包含了应用程序中用到的所有的windows资源, 要指出的一点是rc文件可以直接在VC集成环境中以可视化的方法进行编辑和修改。

 *.ncb:无编译浏览文件(no compile browser)。当自动完成功能出问题时可以删除此文件,build后会自动生成。

 *.c:源代码文件,按C语言用法编译处理。

 *.cpp:源代码文件,按C++语法编译处理。

 *.h是头文件,一般用作声明和全局定义。

0 0
原创粉丝点击