WINCE 常见问题解答 v0.1
作者:付林林 windowsce@tom.com
z 在 mediaplayer 全屏播放的时候,我可以用键盘上的某一个键调节声音大小,现在我想在屏幕上显示调节的结果就跟 我们看电视一样能出来一些标记。当声音变大在屏幕上就增多,当声音变小的时候就减少
得到播放窗口的 DC,然后在上面显示一个不同颜色的矩形或者其它形状。
z 在 ce 自制平台中如何添加自己的驱动? cec 文件和 reg,bib 文件有何区别?看混了,不知道如何才能让驱动注册到平 台中。另外, reg 和 bib 是我在编写驱动必须要自己写的么
CE 的驱动分为本机驱动和流驱动,本机驱动你可以修改源码(在%_WINCEROOT%\PUBLIC\COMMON\OAK\DRIVERS),然后 build 驱动源
码,之后用 PB 编译内核。流驱动是以 DLL 的形式被系统程序加载的。加载流驱动要在注册表中添加信息,这和 cec 文件没关。如果你想把 此驱动 DLL 和注册信息作为一个整体给别人使用,那么就要做一个 cec 文件,PB 能够读取 cec 文件内容并加载到内核工程中。cec 文件相 当于安装程序。
z 我安装的是 evc4.0 ,我开发的系统要显示中文比如姓名什么的。在编辑的时候是中文显示,但是到了输出时,显示的 都是乱码。不知道应该怎么去解决 EVC的模拟器不支持中文。所以显示出来的都是乱码。1、你可以只显示英文,调试结束后在改界面为中文。2、安装PB,PB的模拟器支持中文。
z CStatic 及其控件的文字无法垂直居中显示,虽然在 evc 的资源编辑器中可设置,但是一旦运行,却无法垂直居中显示
这种情况正常,而且除此之外还不能右对齐。
z 获取设备 ID 的种类有哪些
除了手机模块外,还有 CF 卡有序列号、硬盘有 ID 号、网卡有 MAC 号等。这几种方法数获取手机模块 ID 最简单。
z 在应用程序中,如何向修改本机的 ip 地址等网络参数,并使之立即生效?
网络设置保存在注册表中,位置[HKEY_LOCAL_MACHINE\Comm\网卡名称\Parms\TcpIp],例如常见的 CS8900 网卡设置:
[HKEY_LOCAL_MACHINE\Comm\CS89001\Parms\TcpIp]
"EnableDHCP"=dword:0
"DefaultGateway"="192.168.0.1"
"DNS"="111.111.111.111"
"UseZeroBroadcast"=dword:0
"IpAddress"="192.168.0.2"
"Subnetmask"="255.255.255.0"
设置之后要生效有两种办法:一种热启动,调用 KernelIoControl(IOCTL_HAL_REBOOT, NULL, 0, NULL, 0, NULL),热启动时间很短暂;另 外一种调用 DevieceIoControl API,传递 IOCTL=IOCTL_NDIS_REBIND_ADAPTER。
z 如何向控制面板中那样,修改系统声音的音量
调用 API waveOutSetVolume(HWAVEOUT, dwVolume ),一般参数 1 为 0。在[HKEY_CURRENT_USER\ControlPanel\Volume]下是系统声音的注册表设置。
z 在应用程序中如何实现 jpg 、 gif 图片的显示
有几种办法:
1、在 MSDN 中搜索标题为"Windows CE .NET Technical Frequently Asked Questions"的文档,其中有一个问题"How can I display JPEG, GIF, and other graphics files?",下面就是答案。
2、如果安装了 Windows CE 5.0,一个例子源码位于 WINCE500\PUBLIC\GDIEX\SDK\SAMPLES\SIMPLE。
3、用 IWebBrowser 组件实现。
z 在应用程序中如何实现系统待机
void GwesPowerOffSystem(void);
z 在 WINCE 下如何实现键盘钩子
我 写 了 一 个 简 单 的 例 子 , 把 其 中 主 要 部 分 截 取 出 来 放 到 了 我 的 FTP 里 。 用 户 名 以 及 密 码 均 为 winceuser , 地 址 是
ftp://211.95.73.26/fllsoft@sina.com/SourceCode/ 用钩子禁止系统键.rar。
z 在 WINCE 中如何得到网卡 MAC 地址
事实证明,获得物理网卡的 MAC 地址并没有被统一成一个 API 或者 IOCTL,如果网卡驱动程序没有提供接口的话只能直接访问寄存器获得。
读者可以参考目录 WINCE500\PUBLIC\COMMON\OAK\DRIVERS\NETCARD 里的一些驱动源码。
z Windows XP Embedded 和 Windows CE 有何区别
简单地说 Windows XP Embedded 采用 Windows XP 内核,只能运行在 x86 处理器上,优点是能够运行 PC 上现有的应用软件,缺点是授权费太高,标价¥900 元;Windows CE 采用 Windows CE 内核,能够运行在多种处理器上,如 x86、ARM、SHX、MIPS 等,优点是授权费低, 最低 Core 版标价¥30 元。缺点是需要单独开发应用软件、定制内核,甚至开发 BSP、Driver。
z wince 下只是把调制解调器的驱动挂接在了 com1 ,如何将器驱动挂接在 com2 上?
1、在 HLM\drivers\buildin\com2\unimodem 下复制和 com1 一样的数据
2、在 HLM\ExtModems\HayesCompat 下改写 Port 为 COM2:,再改写 FriendlyName 为"Hayes Compatible 在 COM2:"。
z 如何定制自己的外壳
1、先开发一个外壳软件,假设名称为 MyShell.exe
2、删除注册表[HKEY_LOCAL_MACHINE\init]下如下一行:
"Launch50"="explorer.exe"
3、在注册表[HKEY_LOCAL_MACHINE\init]下添加如下一行:
"Launch50"="MyShell.exe"
上述的"LaunchXX"中的 XX 为序列数,内核依据这个序列数按由小到大的顺序来分别执行所有子键列出的应用程序,具体数值应该为多少请参考帮助文档的说明。
如果原来的内核中添加了标准外壳(standard shell)组件,或者添加了其它组件而这些组件需要依赖标准外壳,那么在 PB 中是无法删除标 准外壳组件的,解决办法一是保留 explorer.exe 在内核中,二是删除依赖标准外壳的组件。
z 我原来的工程是 x86 版本的,编译选项只有 x86 ,我如何能够编译 ARM 版本的
两种办法:
1、用 EVC 新建一个工程的时候,建议复选"CPUs"列表,这样发生了这种事情也能够轻易通过选择"WCE Configuration"工具栏中的 CPU列表来编译不同 CPU 版本的软件
2、如果打开工程后 CPU 列表中只有 x86,而此时已经安装了 ARM 版本的 SDK,那么单击 EVC 菜单"build"-"configurations",然后单 击"add"按钮来添加 CPU。
z 通常情况下 WINCE 采用串口 1 作为调试时输出信息用途,要正式出产品前如何去掉串口 1 的调试功能
正常情况下串口 1 只有在编译 debug 版本的内核时才在 BootLoader 中初始化串口 1 用于输出信息,而编译 release 版本会跳过此代码。而有些 BSP 设计成没有宏定义,也就是说无论什么版本都会在 BootLoader 中初始化串口 1,这样造成 WINCE 启动后串口 1 无法被应用程序 使用。对于这种情况只能在 BootLoader 源码中删除初始化代码,如 OEMInitDebugSerial。
z 基于 wince 的应用程序能建成 console project 么? 不能
z ARM 系统外扩一片 512K RAM ,驱动程序经过映射可以使用这一段 RAM 。 1 、应用系统如何使用这一段 RAM ?要加一层驱动吗? 2 、如果要将这一段 RAM 当作 RAM 盘存储系统,该如何作呢?应该要加文件系统吧,如何加呢?
解答这个问题前,先要说一下 WINCE 的地址映射机制。对于包含 MMU(存储器管理单元)的处理器来说,如 ARM 和 x86,WINCE 要求OEM 在定制内核的时候填写一个虚拟地址与物理地址映射关系的表,称为 OEMAddressTable,在这个表中定义了所有物理设备的起始物理 地址,对应的起始虚拟地址,地址空间大小,RAM 就包括在其中(如果是 x86 平台还要求 RAM 起始虚拟地址从 0x80000000 开始)。如果 有多片 RAM,应该在 OEMAddressTable 中将它们定义在一起,使之地址连续。对于非 OEM 的开发者来说,他们拿到的是定制好的内核,不 能做任何修改,如果在产品中外扩一片 RAM,只能通过 API 函数通知操作系统增加一条虚拟地址与物理地址映射关系表项。相关 API 函数 有两个,分别是 CreateStaticMapping 和 VirtualCopy。它们的相同之处是都用于建立物理地址和虚拟地址的映射关系。它们的不同之处是 CreateStaticMapping 映射的虚拟地址范围在 0xC400 0000 到 0xE000 0000 之间,这个范围只能由内核访问,一般用于 ISR 访问,因为 ISR 只能访问静态映射的虚拟地址空间,不能用VirtualCopy。VirtualCopy 通常和 VirtualAlloc 配合使用,映射的虚拟地址空间在 0x8000 0000 以下,一般用于驱动程序和应用程序访问。
1、应用程序要访问这片 RAM,和驱动程序访问方法一样,调用 VirtualAlloc 和 VirtualCopy。
2、可以做一个流驱动程序专门用于读写这片 RAM,这样所有应用程序就可以通过调用流驱动接口函数来访问,非要加文件系统也是可行的, 通过修改注册表就可以做到,但是麻烦一些。
z 我怎么能在 PB 左边的定制平台加进我的驱动呢?
两种办法:
1、在 platform.bib 或者 project.bib 的 MODULES 部分添加一条语句,例如:
MyDriver.dll C:\Driver\MyDriver.dll NK SH
这样编译内核的时候就会把你的驱动 DLL 文件添加到内核中,如果有注册表需要设置,在 platform.reg 或者 project.reg 中添加注册表内容。
2、通过制作.cec 文件来添加驱动,制作.cec 文件的优点是只需制作一次,以后就可以通过将.cec 文件导入到 PB 的 Catalog中,象 PB 自带的 feature 一样通过菜单"Add to OS Design"添加到左边的内核工程中。
z WINCE 有没有相对路径概念?如果没有如何得到当前模块的路径?
1、WINCE 没有相对路径概念,只有绝对路径,所以凡是涉及到路径均为绝对路径。
2、调用 API GetModuleFileName,传递一个模块的实例句柄就能够得到模块的绝对路径。
z 怎样让 POCKET WORD 打开 *.dat 格式(里面都是数据)的文件 ?
两种办法:
1、调用 API ShellExecuteEx,在结构体 SHELLEXECUTEINFO 中添加.dat 文件的路径。
2、调用 API CreateProcess,在第二个参数中设置.dat 文件的路径。
z x86 Rom Boot Loader 真的可以实现吗?它确实能代替 BIOS 启动计算机?
Rom Boot 被设计存放在 Flash/EEPROM 中,也就是原来 BIOS 的位置,这样当上电后 CPU 到固定地址执行代码,也就是执行了 Rom Boot的代码,它对整个硬件系统进行初始化和检测,并且支持通过网卡从远程机器上下载 nk.bin 或者从本地 IDE/ATA 硬盘的活动分区中寻找nk.bin 文件加载。Rom Boot 的优点就是引导并且加载速度快,而且它自身完成了所有的操作,这样就不用 BIOS、MSDOS,更不用 Loadcepc了。
z 对于 x86 Rom Boot Loader ,如何 Build 得到 Romboot.rom?
1、在 PB 中打开一个内核工程(x86 的)
2、单击 PB 菜单"Build"-"Open Build Release Directory"
3、用 cd 命令进入 %_WINCEROOT%\Platform\Geode\Romboot
4、build
z 如何设置、更改显示分辨率
能否设置、更改显示分辨率由显示驱动程序决定,而没有统一的标准。例如 CEPC,在启动的时候可以通过设置 loadcepc.exe 的参数 /L 来决定 WINCE 启动后的显示分辨率,这是由于显示驱动"VGA Linear Framebuffer"支持,而 Geode 可以通过在定制内核时修改注册表项来 决定 WINCE 启动后的显示分辨率。
z 几个硬件使用同一个 IRQ ,那么发生中断的时候系统怎么判断到底是哪一个硬件发生的中断呢? ISR 里面又应该怎么 控制呢 ?
Windows CE 支持多个设备中断共享一个 IRQ,当一个共享 IRQ 发生时,CE 内核的异常处理程序检测设备特定的寄存器,因为大多数设备都有一个单独的寄存器用于表示设备的活动状态,所以通过遍历共享这个 IRQ 的所有设备的寄存器就可以判断哪个设备发生中断。nk.exe 加载一个 giisr.dll,这个.dll 是微软提供的,它其实是第一个可安装 ISR。默认 CE 内核就是调用这个 dll 来检测寄存器状态的,当然 OEM 可 以编写自己的.dll。
CE 内部有一个 ISR 链,也就是可安装 ISR。因为 CE 允许 OEM 添加自己的 ISR 处理程序,所以 ISR 被设计成一个链表。排在前面的 ISR 比 后面的 ISR 优先处理中断,如果当前 ISR 能够处理当前中断,那就返回中断 ID 由 IST 处理或者返回 SYSINTR_NOP,如果当前 ISR 不能够 处理当前中断,那就返回 SYSINTR_CHAIN 让下一个 ISR 处理。
z 请问在 wince 中如何在内核中增加一个与 \windows 同级的目录?
在 platform.dat 或者 project.dat 中添加语句。例如要创建根目录下子目录 Program Files,语句如下:
root:-Directory("Program Files")
z 文件格式如下所示,我想把每行的 4 个值读到 4 个变量中,用 EVC 如何编程?
第一行: 460.000, | 3384672.357342, | 521268.972763 |
第二行: 475.117, | 3384663.772419, | 521281.415271 |
伪代码如下:
FILE *stream;
stream = _wfopen(L"\\a.txt", L"r+");
if( stream == NULL )
return;
fseek(stream, 0L, SEEK_SET );
while( !feof( stream ) )
{
fwscanf(stream, L"%s", WCHAR1); fwscanf(stream, L"%f", float1); fwscanf(stream, L"%f", float2); fwscanf(stream, L"%f", float3);
}
fclose( stream );
z GWES 组件的功能有哪些?
GWES 不仅负责 GDI、窗口、消息,还负责管理本机设备驱动程序,负责加载显示、键盘鼠标、触摸屏驱动程序,而且 GWES 本身包含电源、LED 驱动程序。
z 如何在 PB 中预先设定好存储内存和程序内存的大小,我想多划分一些空间给程序内存?
两种办法:
1、在定制内核时在 config.bib 文件中设置 FSRAMPERCENT = number,具体 number 可参考标题为"FSRAMPERCENT "的帮助文档。这种 办法是修改内核的设置,所有一直有效。
2、在应用程序中调用 API SetSystemMemoryDivision,如果函数返回 SYSMEM_CHANGED 表示成功,如果返回 SYSMEM_MUSTREBOOT表示需要热启动才能有效。这种办法需要每次启动后调用 API 才有效。
z 如何取消鼠标光标?
通过取消 SYS 变量来实现此目的,在 PB 命令行下键入"set SYSGEN_CURSOR=",然后回车确认。
z EVC 下调用 TextOut 如何编译会出错?
类似这样的问题很多,这是因为 EVC 的帮助文档内容有错误。可能 EVC 的帮助文档内容是从桌面 Windows 帮助文档复制过来的,所以很多 API 函数还有例子代码都有错误,例如帮助文档中包含一个 API 函数的说明,但是实际编译的时候提示没有这个 API,有的例子代码采用ANSI 字符串,而 WINCE 的 API 都是宽字符版本,造成直接复制过来编译失败。因为 MFC for WINCE 的 CDC 类中没有 TextOut 成员函数,所以编译会出错,可以用其它类成员函数 ExtTextOut 或者 DrawText 替换。
z 我如何将我的 dll 软件让现有的 ce 系统认可?尽管我也知道应该使用 signfile.exe 程序进行签名,但是我并不知道那个 ce 系统认可的签名应该是啥
如果你说的 WINCE 系统内核已经加入了签名认证机制,那么没有私钥对你的 DLL 文件签名肯定是无法运行在此内核中的,一般签名密钥的密钥长度都是 1024 位,很难破解。
z 如果查看 WINCE 注册表中的内容?
两种办法:
1、建立同步后,用 EVC 自带的工具"Remote Registry Editor"打开查看。
2、从网上下载注册表查看工具,放到 WINCE 设备中。
z 调用 directshow 出现链接错误,如何解决?
player.obj : error LNK2001: unresolved external symbol _IID_IVideoWindow player.obj : error LNK2001: unresolved external symbol _IID_IMediaControl
这是因为链接器没有找到合适的.lib 文件。两种办法:
1、在 EVC 菜单 Tools-options-directories 里把 library files 的路径重新调整一下。如果你只安装了 EVC 自带的 Standard SDK 而没有其它SDK,可以指定 WINCE 目录中的.lib 文件路径,例如 D:\WINCE500\PUBLIC\DIRECTX\OAK\LIB\X86\RETAIL。注意 CPU 的类型。
2、安装 SDK,前提是导出 SDK 的 PB 内核工程必须包括 DirectShow 或者其它组件。
z 在 PB 的 config.bib 文件中," IMGFLASH "表示什么意思呢?
表示能够刷 NK 到 ROM 中,具体请查看标题为"IMG Environment Variables"的帮助文档。
z x86 平台如何映射各种地址空间?如何编写中断服务例程?
如果是 x86 平台,可以调用 HalTranslateBusAddress 转换物理总线地址到物理系统地址,调用 HalTranslateSystemAddress 转换物理系统地址到逻辑总线地址,也可以不调用这两个函数,因为 x86 平台除 32 位物理地址外还有 16 位的 IO 地址空间,对于 16 位的 IO 地址空间,可以 直接调用 WRITE_PORT_UCHAR 或者 READ_PORT_UCHAR 等函数直接读写端口。对于 32 位物理地址可以调用 VirtualAlloc 和 VirtualCopy 来映射。这样做思路清晰,简单明了。
在 x86 平台要实现 ISR,有如下几个步骤(以 Geode BSP 为例):
1、用 SETUP_INTERRUPT_MAP 宏关联 SYSINTR 和 IRQ。以"SYSINTR_"为前缀的常量由内核使用,用于唯一标识发生中断的硬件,又 称为中断 ID。在 Nkintr.h 文件中预定义了一些 SYSINTR,OEM 可以在 Oalintr.h 文件中自定义 SYSINTR。
2、用 HookInterrupt 函数关联硬件中断号和 ISR。这里提到的硬件中断号为物理中断号,IRQ 为逻辑中断号。在 InitPICs 函数的最后调用了HookInterrupt 函数,如下:
for (i = 64; i < 80; i++)
HookInterrupt(i, (void *)PeRPISR); ///用 ISR 关联 16 个中断号
3、调用 InterruptInitialize 函数关联 SYSINTR 和 IST 创建的事件对象,也是 IST 等待的事件对象。详细内容请参考 Geode BSP 源码。
在 x86 平台要实现可安装 ISR,先调用 LoadIntChainHandler 函数注册在注册表中指定的 ISR DLL,然后填充 GIISR_INFO 结构体并调用KernelLibIoControl 函数将此结构体传递给可安装 ISR。详细内容请参考 WINCE 帮助文档或者我著的《Windows CE 下驱动开发基础》。
z 修改了 WINCE 自带的驱动程序后如何编译?如果是自己开发的驱动程序如何编译?
1、分为 IDE 方式和命令行方式。
IDE 方式的编译很简单,以 PB5.0 为例,打开定制内核的工程,在左边的"workspace"-"FileView"中找到你已经修改了的目录,然后单 击右键弹出菜单,在菜单中选择"Build and Sysgen Current Project",这样 PB 就会编译指定的目录中的项目源码文件,然后执行 sysgen 命令
根据 source 文件中的内容生成目标文件并复制到当前内核工程目录下。
命令行方式的编译需要打开"Build OS"-"Open Release Directory",以 cd 命令进入你已经修改的驱动程序目录中,然后键入"build -cfs", 然后键入"sysgen -p 项目名称",一般项目名称为 source 文件中的"TARGETNAME"。
2、如果想完全自己开发驱动程序,建议直接采用 EVC 或者 PB 来编写编译。
z 开发 PCI 设备驱动时, InterruptInitialize 函数的第一参数是否是 PCI 卡配置空间信息中的 InterruptLine 参数? 是否需要在 HKLM\Drivers\BuiltIn\PCI\Template 加一個自己的 subkey ,并填写相应内容?如何填?
1、InterruptInitialize 的第一参数是 IRQ,也就是逻辑中断号,而不是物理中断号,InterruptLine 是指物理中断号
2、需要在 template 下加自己的 PCI 设备的信息,例如:
[HKEY_LOCAL_MACHINE\Drivers\PCI\Template\Serial]
"Dll"="Com16550.Dll"
"Class"=dword:07
"SubClass"=dword:00
"ProgIF"=dword:02
"VendorID"=multi_sz:"0AF0","B320","B320"
"DeviceID"=multi_sz:"0020","0300","0302"
"Prefix"="COM"
而这些信息就来自于你执行 pcienum.exe 的结果。
z 编译器报错: error C2065: 'CFileFind' : undeclared identifier ,如何解决?
MFC for WINCE 版本没有 CFileFind 类,所以要查找文件只能调用 API FindFirstFile 和 FindNextFile。
z 如何设置 WINCE 系统字体、字号?如何设置自己开发的软件的字体、字号?
1、系统字体通过注册表设置。如下:
[HKEY_LOCAL_MACHINE\System\GDI\SysFnt] ///系统字体
Wt=420
Ht=18
Nm=Arial
[HKEY_LOCAL_MACHINE\System\GWE\Menu\BarFnt] ///菜单栏字体
[HKEY_LOCAL_MACHINE\System\GWE\Menu\PopFnt] ///弹出窗口字体
[HKEY_LOCAL_MACHINE\System\GWE\Menu] ///菜单字体
HKEY_LOCAL_MACHINE\System\GWE\Button ///按钮字体
2、创建字体时把字体高度参数设置大点就可以了。如 CFont::CreateFont(nHeight,...),也可以在 LOGFONT 结构中设置字体高度或者字体种 类。如果是控件,调用控件的 SetFont 成员函数。如果是直接画,在 OnPaint 响应函数中调用 SelectObject 选字体到 DC。
z nk.bin 和 nk.nb0 有什么区别?
这里提到的 bin 是一种二进制镜像格式,以片断(section)为单位组织数据,每个片断都包括一个头,头里指定了起始地址,长度,校验值。
Platform Builder 调用工具将 WINCE 内核所有文件以 bin 格式合并成一个文件,默认文件名为 nk.bin。BootLoader 又以同样的格式将 nk.bin 分解成多个文件放到 RAM 中。可以在命令行中键入"viewbin nk.bin"来查看 bin 文件中具体包括了哪些内容。键入 Cvrtbin 命令转换.bin 格 式文件为.sre 格式或者.abx 格式。
nb0 格式是原始的二进制镜像,它不包括头,一般情况下将内核下载到设备的 RAM 中运行都采用 nb0 格式。要生成 nbx 格式的文件,需要 在相关.bib 文件中确定如下值:ROMSTART、ROMWIDTH、ROMSIZE。
z 在不采用硬件计时器的情况下如何创建更精确的计时器?最精确周期能否达到 1 毫秒?
对于精确值的要求不同,所采用的办法不同。以下阐述几种办法。
1、在单线程中循环调用 API Sleep 函数,Sleep 函数精确程度为如果 Sleep(N),那么实际睡眠时间在 N 到 N+1 毫秒之间。而且还要注意调用Sleep 的线程优先级的问题。如果任务过多并且此线程优先级低,那误差就更大些。
2、调用 API QueryPerformanceCounter 函数,举例如下:
LARGE_INTEGER liFrequency;
if (QueryPerformanceFrequency(&liFrequency)) // 查询系统时钟的频率,这里将返回 1000
{
liFrequency.QuadPart /= 1000; LARGE_INTEGER liTimeOut;
if (QueryPerformanceCounter(&liTimeOut)) //得到截至到当前累计发生的系统时钟中断次数
{
liTimeOut.QuadPart += liFrequency.QuadPart; ///计算下一秒到来时总的中断次数是多少
LARGE_INTEGER liCurrent;
do
{
QueryPerformanceCounter(&liCurrent); // 循环查询累计的的中断次数
} while (liCurrent.QuadPart < liTimeOut.QuadPart); ///到达下一秒
}
}
调用QueryPerformanceCounter同调 用Sleep在本质上都是一样的,都是在单线程中无限循环等到周期一到执行任务, 相比较QueryPerformanceCounter 要比 Sleep 更精确些,越精确就越要求线程的优先级,保障线程能够正常得到处理器。
3、以上办法难以保证周期精确到 1 毫秒并且 WINCE 系统稳定地运行,所以要从中断入手。以 x86 平台为例,先在 Timer.c 中将默认的SetTimer0(TIMER_COUNT)中的 TIMER_COUNT /=2,SetTimer0 函数负责设置系统时钟的频率,默认 1 毫秒发生一次中断,如果除以 2 就是 0.5 毫秒发生一次中断。然后在 fwpc.c 文件中修改 ISR 函数 PeRPISR,因为原来默认是 1 毫秒发生一次中断,在处理 INTR_TIMER0 时系 统负责累计计数、管理线程的调度,返回相应的 SYSINTR 值,而我们没有办法再添加代码返回自己定义的 SYSINTR 值,所以现在要修改 原来的处理代码,例如设置一个 BOOL 型变量,TRUE 就执行原来默认的代码,而 FALSE 就返回我们自己定义的 SYSINTR 值,这样即不 影响原来的 ISR 处理,又加入了我们的中断响应代码。ISR 返回我们定义的 SYSINTR 后 WINCE 内核激活相对应的 EVENT 事件,我们就可 以在我们编写的 IST 里处理任务了。
z flash 中存放了 BootLoader 和内核镜像,如何把剩余 flash 部分划分为一个存储区域供应用程序读写?
以 WINCE 提供的驱动(FAT 文件系统和 MSFLASH 驱动)来举例说明。如果采用默认 common.reg 中的注册表设置,那么 MSFLASH 驱动默认把整个 flash 作为存储区域来读写,这不符合问题的要求,所以必须告诉 MSFLASH 驱动程序可供读写的区域的起始地址和长度。以下 是一个注册表例子:
[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\FASLD]
"Dll"="fasld.dll" ///实际 Flash 存储器的驱动程序
"Order"=dword:2 ///该驱动程序相对于其它驱动程序的加载顺序
"Prefix"="DSK" ///前缀
"Ioctl"=dword:4 ///IOCTL 码,设备管理器加载驱动的时候调用 IOControl 函数,传递这个 IOCTL 码。
"Profile"="MSFlash" ///Profile 名称,也就是[HLM\System\StorageManager\Profiles\MSFlash]
///当设备管理器加载此驱动程序的同时发送通知给系统,IClass(GUID)的值表明这是一个存储设备的驱动程序。
"IClass"="{A4E7EDDA-E575-4252-9D6B-4195D48BB865}"
"MemBase"=dword:00000000 ///Flash 中可供读写区域的起始物理地址,也就是 Flash 的首地址+偏移量
"MemLen"=dword:00000000 ///Flash 中可供读写区域的长度
[HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\MSFlash]
"DefaultFileSystem"="FATFS" ///MSFlash 驱动默认采用的文件系统
"PartitionDriver"="mspart.dll" ///采用的分区驱动程序
"MountAsRoot"=dword:1 ///此目录作为文件系统的根目录
"Folder"="NOR Flash" ///目录名称
"Name"="FLASH Disk Block Device" ///Flash 驱动名称
"PartitionDriverName"="MSPART" ///分区驱动名称
"AutoMount"=dword:1 ///自动装载检测到的分区
"AutoPart"=dword:1 ///自动分区
"AutoFormat"=dword:1 ///自动格式化分区
[HKEY_LOCAL_MACHINE\System\StorageManager\AutoLoad\MSFlash]
"DriverPath"="Drivers\\BuiltIn\\FASLD" ///Flash 驱动在注册表中的位置
"LoadFlags"=dword:1 ///这个值可以被设置为 0、1、2。1 表示同步加载,其它表示异步加载
"Order"=dword:0
[HKEY_LOCAL_MACHINE\System\StorageManager\FATFS]
"FriendlyName"="FAT FileSystem" ///文件系统名称
"Dll"="fatfsd.dll" ///文件系统驱动程序
"Flags"=dword:00000064 ///标志,详见帮助文档
"Paging"=dword:1 ///是否分页
"EnableCache"=dword:1 ///是否允许缓存数据
"CacheSize"=dword:0 ///指定缓存大小,0 表示默认
z 驱动程序如何发通知给应用程序?
这里介绍一下常见的两种办法。
1、驱动程序调用 API SendNotifyMessage,发送特定的消息给应用程序,这就要求应用程序要有消息循环机制并且要事先做好消息的处理。
参数 1 为窗口句柄,可以设置 HWND_BROADCAST 表示广播消息。要注意的是不要在参数中传递指针(虚拟地址),因为执行驱动程序的 线程和应用程序并不在同一个进程空间中。解决办法可以利用内存映射文件技术,比如在驱动程序中创建一个内存映射文件对象,申请一块 物理内存,然后把对象名称和内存长度传递给应用程序,应用程序打开同名的内存映射文件对象,读取里面的数据。对象名称可以事先协定 好,也可以通过注册表来传递,内存长度是 32 位值,通过消息参数就可以传递,也可以通过注册表来传递。另外一种解决办法是在定制内 核时候预留一块物理内存,这样驱动程序和应用程序都可以通过 VirtualAlloc 和 VirtualCopy 来映射到同一块物理内存,其原理同内存映射文件技术一样,但是这块物理内存不具备通用性。最后一个办法是应用程序事先将一个缓冲区地址传递给驱动程序,驱动程序调用 MapPtrToProcess 映射应用程序传递过来的地址,当驱动程序调用 SendNotifyMessage 后应用程序可以直接到该地址中读取数据。 设备管理器就是调用此函数广播 WM_DEVICECHANGE 消息的。另外 WINCE 的一个例子程序 RNAApp 在拨号连接建立的时候也是调用这 个函数广播 WM_NETCONNECT 消息的。
2、驱动程序调用 API CeEventHasOccurred 指明一个事件 A 发生,在此之前应用程序调用 API CeRunAppAtEvent 将驱动程序指明的 A 事件 和一个应用程序名称相关联,或者和一个事件 B 相关联。这样当 A 事件发生时,如果指明和一个应用程序名称关联,那这个应用程序就会 被启动。如果指明了和一个事件 B 相关联,那么等待事件 B 的线程将被激活。如果想了解当前系统内部所有驱动程序支持哪些类似事件 A 的事件,调用 API CeNotifyPublic_FilterEvent,在该 API 的帮助文档里也列举了常见的事件,例如 NOTIFICATION_EVENT_NET_CONNECT和 NOTIFICATION_EVENT_NET_DISCONNECT。
z EVC 创建的工程名称如果用中文就出错,该怎么办?
用 EVC 创建的工程名称如果为中文将导致资源文件打不开和编译出错,可以改资源文件名称为英文,再编辑.rc 文件中的资源文件名称。但建议尽量不要用中文为工程名称。
作为习惯,应该在 EVC 创建一个工程后,立刻在"project"-"settings"中设置资源的语言属性,然后在"resource view"中设置每个资源 的语言属性,这些工作做完后再修改资源就没有问题了。有人询问对话框的标题为乱码,其原因就是在没有修改语言属性的情况下设置标题 为中文。
z WinCE 下如何读写几百兆的大文件呢 ? 使用内存映射文件吗 ?
一般嵌入式设备配备 128MB 物理内存就算顶级的了,所以要读写几百 MB 的文件用内存映射文件技术是最好的选择了。映射文件之后读数据是非常容易的,要注意的是写数据,内存映射方面的 API 没有提供改变文件长度的功能,所以要在关闭映射文件对象后用文件 API 改变 文件长度。
z 请问如何改系统调度的默认时间片值?
更改 schedule.c 文件中的 dwDefaultThreadQuantum 变量,然后重新编译该文件并 SYSGEN。调用 API CeGetThreadQuantum 就知道更改是否生效。
z 如何让系统加载自己写的驱动程序?
两种办法:
1、在[HKEY_LOCAL_MACHINE\Drivers\BuiltIn]下添加注册键。
2、在应用程序中调用 ActivateDeviceEx。
z 在一些文件中用分号来表示注释,例如下面的内容
; @CESYSGEN IF SERVERS_MODULES_HTTPD
; @CESYSGEN ENDIF
在" CESYSGEN... "前加了" @ ",有没有什么特别的含义?
在 WINCE 的一些文件中,用";"作为注释并在注释文字中用@CESYSGEN 作为标记,后面接条件语句。Cefilter.exe 工具负责按照条件来筛选文件内容,所以不要轻易地删除包含@CESYSGEN 的注释语句。
z 通过串口建立 ActiveSync 联接 , 串口线用三线的可以吗 ?
不可以,因为用串口同步时要用到其余口的状态。
z WINCE 是否支持 MAPI ?
不支持。WINCE 自带的 pmail.exe 软件也不是很好用。建议自开发邮件收发软件。如果需要购买 WINCE 下邮件收发软件可以联系我。
z 如何旋转屏幕显示的内容?
例子代码如下(前提是显示驱动程序支持旋转):
DEVMODE devmode = {0};
devmode.dmSize = sizeof(DEVMODE);
devmode.dmDisplayOrientation = DMDO_90; ///垂直模式
devmode.dmFields = DM_DISPLAYORIENTATION;
ChangeDisplaySettingsEx(NULL, &devmode, NULL, 0, NULL); ///改变显示的设置
CRect rcWorkArea(0, 0, 320, 240); ///整个屏幕尺寸
///设置客户区大小并广播消息,这样所有软件也就随之更改显示
SystemParametersInfo(SPI_SETWORKAREA, 0, (void*)&rcWorkArea, SPIF_SENDCHANGE);
z 请问如何修改字形缓存的容量?
[HKEY_LOCAL_MACHINE\System\GDI\GLYPHCACHE]
"limit"=dword:0400
z 如何得到从 WINCE 启动开始到现在的时间?
调用 API GetTickCount,得到的值为 32 位整数,单位为毫秒。
z 如何调用 WINCE 的软键盘?
调用 API SipShowIM(SIPF_ON),前提是内核加入了软键盘组件。
z 基于 HIVE 的注册表,如何在系统关闭前保存注册表的数据到文件 system.hv ?
调用 API RegFlushKey 函数。
z 使用 VirtualAlloc 和 VirtualCopy 的时候需要注意哪些事项?
1、VirtualAlloc 的作用是申请虚拟地址空间,这肯定不是最终的目的,最终目的可能是申请物理内存、映射寄存器、提交文件等。没有一个目的会在意虚拟地址空间的位置,所以尽量传递参数 1 为 0,也就是让 WINCE 自动分配虚拟地址空间。VirtualAlloc 分配地址空间实际上是以 64KB 为单位,所以要指定申请的虚拟空间的首地址的话,参数 1 应该为 64KB 的整数倍,申请的长度也应该为 64KB 的整数倍,即使你 不需要那么大。
2、VirtualCopy 的主要作用是映射物理地址空间,如果参数 2 为物理地址,那么最后一个参数要添加 PAGE_PHYSICAL,参数 2 必须是 256 的整数倍。如果参数 2 为虚拟地址(0x80000000 以上),那么最后一个参数就不要添加 PAGE_PHYSICAL,WINCE 内核会根据这个虚拟地 址找到对应的物理地址。
z 驱动程序和应用程序之间传递数据时何时调用 MapPtrToProcess ?
因为设备管理器负责加载驱动程序 DLL,这意味着当应用程序调用驱动程序接口函数的时候,WINCE 内核会将调用驱动程序接口函数的线 程转移到设备管理器的进程空间然后执行具体的驱动程序代码,应用程序和设备管理器处于两个进程空间,这就造成设备管理器无法访问应 用程序传递的指针(虚拟地址),所以当我们在应用程序中传递指针给流驱动程序接口函数时,WINCE 内核从中作了一个地址映射,例如 ReadFile、WriteFile、DeviceIoControl 函数的参数凡是指针都经过了映射才传递给驱动程序,所以很多驱动程序开发者并不了解其中的奥秘 就可以编程了。但是如果参数是一个指向一个结构体的指针,而结构体里包括一个或多个指针,那么 WINCE 内核并不负责映射,所以就需要开发者 在驱 动程序接 口函 数中调用 API 函数 MapPtrToProcess 来映射地址。 例 如: pPointer_retval = MapPtrToProcess(pPointer, GetCallerProcess());
z 如何判断可插拔的设备是否存在?
1、通过查找注册表的值。凡是由 API ActivateDeviceEx 加载的驱动程序都在[HKEY_LOCAL_MACHINE\Drivers\Active]键下有注册键,通过查找"name"或者其它键值就能够找到。设备管理器就调用这个 API。如果是 PCI 设备,在注册表[HLM\Drivers\BuiltIn\PCI\Instance]下查找 关键字,例如[HLM\Drivers\BuiltIn\PCI\Instance\WaveDev1],说明音频驱动已经加载。
2、调用驱动程序接口函数,根据返回值或者执行结果来判断。
z 如何做到通过串口过来的一个信号启动自己开发的应用程序?
创建一个线程负责等待串口过来的信号,调用 API SetCommMask 设置要等待的信号种类,具体可以等待的信号种类参见参数 2 的说明。然后再调用 API WaitCommEvent 函数等待这个信号,接收之后再调用 API CreateProcess 启动应用程序。
z 在 WINCE 中如何只能启动应用程序的一个实例?
常用的两种办法:
1、如果应用程序实例创建了窗口,可通过 API FindWindow 函数通过窗口类名和窗口标题名称来查找,前提是系统内不会出现窗口名称重复 的情况。
2、应用程序初始化的时候创建一个事件或互斥等内核对象,因为内核对象是由内核创建,名称在系统内唯一。
z 能不能自己编辑一个数字签名文件导入到手机上,这样就可以用这个签名签自己的程序了?
WINCE 的内核签名机制的用途是限制非法的可执行模块 EXE、DLL 等在设备上运行。要求内核的加载模块用公钥验证请求加载的 EXE、
DLL 的签名是否合法,而这个公钥是在定制内核的时候加进去的,所以除内核的定制者以外的人无法修改这个验证机制。
z 我按照版主的文章《加密 WINCE 系统》里操作,提示错误如下:
Error 80090016 during CryptSignHash 1! Error signing hash
这是因为传递了无效的钥容器名称,使CryptoAPI调用失败。应该在使用signfile工具之前创建一个钥容器,在桌面Windows中调用API CryptAcquireContext 创建一个指定名称的钥容器,接着再创建一个签名密钥对,这时再使用 signfile 工具就可以了。我在文章里写成-kfulinlin是因为我创建钥容器的时候没有指定名称,系统就采用当前登录的用户名为容器名。
z 编译错误: CVTRES : fatal error CVT1102: out of memory; 42 bytes required ?
多数情况下出现这种错误是因 EVC 的 bug 而起,应该在安装 EVC 之后就立刻安装 EVC 的 SP 补丁。另外为了避开 BUG,使用 EVC 编程应该养成一些习惯,比如定期备份工程所有文件,每次编译时采用 Clean + Rebuild All,正调试时不要关闭模拟器等等。
z 在 WINCE 下是否能够得到某一进程使用的物理内存总量?
目前没发现有这样一个 API 能够得到指定进程使用的物理内存总量。只有 GlobalMemoryStatus 能够得到整个系统使用的物理内存总量。
z 应用程序如何控制 lcd 的亮度?如何获得电池的电量?
从常见的平台如 Geode、三星 ARM 系列来看,的确在驱动方面没有统一的控制 LCD 或者其它种类屏幕亮度的接口函数,所以只能根据具体平台提供的接口来做。从帮助文档来看微软的带有 DirectDraw 功能的显示驱动程序的确有标准的增加亮度的接口函数,关于背景光参见标 题为"Enabling a Backlight"的帮助文档。
获得电池电量有标准的接口函数 GetSystemPowerStatusEx,前提是驱动程序和硬件都要支持。
z WINCE 的 socket 函数好像不支持发送 / 接收超时?
是的,最早版本的 WINCE 支持选项 SO_RCVTIMEO、SO_SNDTIMEO,后来却不支持了。
z WINCE 下如何设置窗口最大化和最小化?
WINCE 的帮助文档在介绍 API ShowWindow 函数的参数时指出 SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, SW_SHOWDEFAULT,
SW_SHOWMAXIMIZED, SW_SHOWMINIMIZED, SW_SHOWMINNOACTIVE 都不被支持,但实际上并不完全是这样,具体来说:
SW_MAXIMIZE 比原来窗口大,但不是最大化 SW_MINIMIZE 编译成功,但是不起作用 SW_SHOWMAXIMIZED 最大化
SW_SHOWMINIMIZED 编译出错 SW_RESTORE 能恢复 SW_SHOWDEFAULT 编译出错 SW_SHOWMINNOACTIVE 编译出错 SW_HIDE 能够隐藏
z 如何用程序调用控制面板的触摸屏校对程序?
两种办法:
1、调用 API TouchCalibrate 函数
2、调用 CreateProcess,参数 1 为 L"\\windows\\ctlpnl.exe",参数 2 为 L"cplmain.cpl,9"。
z 如何获得 U 盘或者其它类型的存储器总容量和剩余可用容量?
调用 API GetStoreInfo 得到扇区数、每扇区字节数,相乘即是总容量。调用 API GetDiskFreeSpaceEx 得到剩余可用容量。
z 三星 2440 头文件定义 #define IIC_BASE 0xB1400000 // 54000000 , datasheet 是 54000000 ,那么怎么转成
0xB1400000 ?
物理地址映射方法分为两种,一种静态映射另一种为动态映射。在 OEMAddressTable 中定义了物理地址与虚拟地址的映射关系属于静态映
射,用 VirtualCopy 映射属于动态映射,采用哪种办法都可以。问题中提到的属于静态映射,2440 的 BSP 在 map.a 文件中定义了 IIC 控制寄 存器的物理起始地址和对应的虚拟地址如下:
DCD 0x91400000, 0x54000000, 1 ;
在 OEMAddressTable 中定义的虚拟地址范围在 0x8000 0000-0x9FFF FFFF,这部分可缓存,适合内核程序和应用程序使用,同时 WINCE内核在 0xA000 0000-0xBFFF FFFF 中映射了另一份,指向了同样的物理地址,这部分不可缓存,适合驱动程序使用。三星 ARM 处理器带有 L1 级高速缓存,可缓存会提高执行效率。对于特殊的设备寄存器适合映射到不可缓存的虚拟地址。
当驱动程序调用 VirtualCopy 对 0xB1400000 地址读写时,WINCE 自动将这个地址减去 0x2000 0000,也就是 0x91400000,对应的物理地址 就是 0x54000000,也就是 IIC 控制寄存器的物理起始地址。
z 基于 RAM 的注册表如何保存数据?
调用 API RegCopyFile 备份注册表。调用 API RegRestoreFile 恢复注册表,然后调用 KernelIoControl 热启动使恢复生效。
z 如何隐藏和显示 winCE 下标准外壳的任务栏?
HANDLE hTaskBar = FindWindow(L"HHTaskBar", NULL); ShowWindow(hTaskBar, SW_HIDE);
ShowWindow(hTaskBar, SW_SHOWNORMAL);
z 如果能让 WINCE 的 IE 浏览器播放 flash 动画?
播放 flash 需要 Macromedia Flash Player SDK,参见 http://www.adobe.com/products/flashplayer_sdk/ 。这和 real player 相似,都需要 WINCE 平台的 SDK,都需要申请。
z WINCE 下内核模式和用户模式有什么区别?
为了使读者能够详细了解 WINCE 的地址映射原理还有两种模式,在这里我分几个部分说明:
1、WINCE 内核 nk.exe 的任务是管理操作系统核心功能。按照 OEMAddressTable 的映射要求,所有物理地址都映射到 0x80000000 以上,所 以对于内核程序 nk.exe 和内核模式下的线程来说,只要访问 0x80000000 以上的有效虚拟地址经 MMU 就能够访问物理地址,无需再映射是 内核模式的一个特点。内核模式的第二个特点是没有地址访问限制,内核模式线程可以访问任何有效虚拟地址,所谓有效虚拟地址是指有实 际事物对应。
2、用户模式线程只能访问 0x80000000 以下的虚拟地址空间,WINCE6.0 之前版本的内核为每个进程划分 32MB 的地址空间,在不调用特殊 函数的情况下不能相互访问,这样的设计使得 WINCE 系统更安全、更稳定,限制访问地址是用户模式的第一个特点。第二个特点就是需要 多一层映射,如果线程要访问物理内存的话需要先映射到 0x80000000 以上,再经 MMU 访问物理内存地址。
WINCE 的线程具有转移性(参考 API GetCallerProcess 的说明,有一个很好的例子),当应用程序的线程调用 API 或者调用驱动程序接口函 数时,该线程会转移到gwes.exe、device.exe、filesys.exe 等进程中执行,转移是由 WINCE 内核操作的,它会修改线程的上下文,记录线程 的当前进程、调用者进程、拥有者进程三个值。
3、如果在定制内核的时候选择了"Full Kernel Mode",那么在这个内核上运行的所有线程都处于内核模式,即使调用 SetKMode(FALSE)后 线程仍然具有内核模式的特点,能够访问任何有效的虚拟地址。假设现有一个 64MB RAM 的 WINCE 产品,RAM 映射从 0x80000000 到0x84000000,如果线程处于内核模式,它就直接可以访问这个范围的虚拟地址: 在 OnButton1()中编写
DWORD oldMode = SetKMode(FALSE);
volatile int *piTemp = (volatile int*)(0x20000000+0x84000000-0x00019000); ///或者(0x84000000-0x00019000)
*piTemp = 12345;
在 OnButton2()中编写
DWORD oldMode = SetKMode(FALSE);
volatile int *piTemp = (volatile int*)(0x20000000+0x84000000-0x00019000); ///或者(0x84000000-0x00019000)
int iTemp = *piTemp;
先只执行 OnButton1()然后关闭程序,再重启程序然后执行 OnButton2(),iTemp 仍然等于 12345。结果说明了两点:内核模式线程可以直接 访问 0x80000000 以上的有效虚拟地址;我们写到 RAM 中的数据没有丢失,说明虚拟地址有效。
如果在定制内核的时候没有选择"Full Kernel Mode",那么在这个内核上运行的所有线程都处于用户模式。可以调用 SetKMode(TRUE)使调 用线程暂时处于内核模式,还是原来的假设环境,我再举个例子:
在 OnButton1()中编写
DWORD oldMode = SetKMode(TRUE);
volatile int *piTemp = (volatile int*)(0x20000000+0x84000000-0x00019000); ///或者(0x84000000-0x00019000)
*piTemp = 12345;
在用 户模 式下 ,如 果不 调 用 SetKMode(TRUE) ,那 么执 行 *piTemp = 12345 一 定会 弹出 对话 框, 提示 地址 访问非法 ,如 果 调用 SetKMode(TRUE)就不会提示地址访问非法,而且在 OnButton2()中仍然能得到 12345 这个值。 通过这两个例子我相信读者能够完全了解两种模式的区别了。
4、WINCE 提供了两个函数 SetKMode 和 SetProcPermissions,其中 SetKMode 能够把调用线程切换到内核模式,还可以切换回用户模式。 SetProcPermissions + GetCurrentPermissions 添加当前进程访问权限给调用线程,SetProcPermissions (0xFFFFFFFF)能让调用线程访问所有进程 空间,但是调用线程仍然处于用户模式。SetKMode 和 SetProcPermissions 函数使得用户模式的特点不那么明晰。 如上所说一个应用程序的线程可能转移到其它两个进程地址空间中读写数据,而每一个线程在被创建的时候只有访问创建它的进程地址空间 的权限,所以驱动程序开发者必须在驱动程序读写数据前调用 SetKMode 或者 SetProcPermissions 增加调用此函数的线程访问其它进程空间 的权限。如果一个应用程序的线程只转移到一个进程地址空间,一般为设备管理器进程 device.exe,这种情况下不必增加线程访问其它进程 空间的权限,但如果驱动程序本身创建了一个线程,那还是要调用 SetKMode 或者 SetProcPermissions 增加新的线程访问其它进程的权限的, 因为驱动程序创建线程时,当前进程为设备管理器,所以新线程只具有访问设备管理器进程空间的权限,而不具备访问应用程序进程空间的 权限。
5 、可能一个编写过简单的流驱动的初学者会很疑惑,因为开发一个简单的流驱动程序根本不需要调用这些函数,也没 有调用过 MapPtrToProcess,那是因为如果标准流驱动接口函数的参数为指针(ReadFile、WriteFile、DeviceIoControl 参数都有指针),WINCE 内核会 自动映射指针包含的地址,但仅此而已,其余任何情况都要求开发者自行处理,比如流接口函数的参数是一个指向结构体的指针 PA,而结 构体中包括指针 PB,PB 指针就必须在流接口函数中映射,映射后才能访问,否则就会造成地址访问非法。所以结构体中每个指针都要映射。 为了让读者能了解其中的原因,我举个例子:
假设设备管理器被加载到 Slot4,应用程序 A 被加载到 Slot 8,A 只有一个主线程 T,T 开始执行,按照 WINCE 的规定,正获得 CPU 的进程必须映射到 Slot0,那么在执行代码的时候 A 的所有虚拟地址都被减去一个偏移值,也就是 8×0x02000000,A 调用 DeviceIoControl,传递 一个指向一个结构体的指针 B,而这个结构体中包含一个指针 C,指针 C 包含的地址假设为 0x00030000,当执行 DeviceIoControl 时 WINCE 把设备管理器的进程地址空间映射到 Slot0,因为放在注册表[HKLM\Drivers\BuiltIn]下的驱动程序是由设备管理器加载的,自然驱动程序的 代码段被加载到设备管理器进程空间,但是线程仍然是 T,此时 T 的当前所在进程为设备管理器(CurrentProcess),A 变成了 T 的调用者进 程(CallerProcess),T 自动具有了访问调用者进程空间的权限。这时访问 Slot0 中的虚拟地址其实质就是访问设备管理器的进程地址空间, 要把地址加上一个偏移值,也就是 4×0x02000000,所以 DeviceIoControl 访问指针 C 包含的地址时本应该加上 8×0x02000000,却加上 4×
0x02000000,结果地址并不是设备管理器的合法区域,系统就会提示地址访问非法。而如果做了一个映射,指针 C 包含的地址就会被加一 个正确的偏移值,使地址处于 A 的地址空间 Slot 8 中,T 此时具有访问 A 进程空间的权限,访问到正确的虚拟地址当然会得到正确的数据了。
z 为什么 WINCE 目录下的例子用 build+sysgen 能够编译成 EXE 文件,而我添加的例子就不能编译呢? 如果这个例子是一个应用程序,那么肯定包括代码文件(.h .c .cpp)和资源文件(.rc 和其它资源文件),build 工具根据 source 文件内容把代 码文件编译成 lib 文件,资源文件编译成.res 文件,sysgen 工具根据 makefile 文件内容将 source 文件中列出的需要链接的各个库文件合并成一个 EXE 文件。所以说关键在于 makefile 文件,WINCE 目录下凡是能够用 build+sysgen 编译的都在 makefile 中有如何链接的设置,而我们 添加的例子当然没有在 makefile 中找到如何链接的设置,nmake 工具就会提示不知道如何创建。
z pcienum.exe 干什么用的?
如果你要开发某一个 PCI 设备的驱动程序,首先要知道这个 PCI 设备的信息(如 VendorID、DeviceID、BaseClass、SubClass)和 PCI 总线的信息。运行这个 pcienum.exe 就能得到相关信息。pcienum.exe 提供了源码,位置\Public\Common\Oak\Drivers\Ceddk\Test\Pcienum。
z wince 下如何让操作系统进入待机模式?又如何把它激活?
通过注册表就可以设置,前提是你的驱动和硬件都支持。注册表项参见标题为"GWES Suspend Time-outs"的帮助文档。
[HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Power]
"BattPowerOff"=dword:300
"ExtPowerOff"=dword:0
"WakeupPowerOff"=dword:60
"ScreenPowerOff"=dword:0
z 现有一个 GPRS 模块,如何通过 GPRS 连接到 Internet ?
1、先在内核中加入 WAN 下面的几个组件,如 RAS/PPP、TAPI。WINCE 采用 unimodem 驱动,所以不必担心没有 Modem 驱动的支持。
2、WINCE 启动后新建一个拨号连接,比如名称叫"gprs1",输入用户名、密码、电话号码。电话号码不同,所采用的模式不一样,例如"*99#"是 GPRS 模式,"17201"是普通的数据模式,速度差很多,价钱也差很多。
3、开始连接,连接过程会在对话框中显示,直到显示"连接成功"。
4、打开浏览器或者自己开发的通讯软件测试网络连接情况。
5、关闭连接。
6、保存[HKEY_CURRENT_USER\Comm\RasBook\gprs1]下的所有数据,添加到 project.reg 中,重新编译后内核中就有了一个拨号连接"gprs1"。
7、调用 RAS 函数可以修改拨号连接"gprs1"的参数,如用户名、密码、电话号码,但是不能修改硬件设置,如波特率、串口、数据位、 停止位等。RAS 函数还能够拨号、挂断。为了修改波特率可以多保存几个拨号连接,也可以直接调用 TAPI 开发拨号软件,另外 WINCE 自 带的拨号连接是有源码的,位置在\PUBLIC\COMMON\OAK\DRIVERS\NETSAMP\CONNMC。
z 采用基于 HIVE 的注册表如何删除用户保存在注册表中的数据,恢复到出厂时的注册表?
用户修改的数据保存在 user.hv 文件中,直接删除一定失败,所以不能通过删除文件实现恢复出厂设置。微软考虑到了这个问题,在 WINCE启动过程中 filesys.exe 加载注册表时会调用 OEMIoControl 函数并传递一个 IOCTL,这个 IOCTL 在 pkfuncs.h 中定义如下:
#define IOCTL_HAL_GET_HIVE_CLEAN_FLAG CTL_CODE(FILE_DEVICE_HAL, 49, METHOD_BUFFERED, FILE_ANY_ACCESS) filesys.exe 会分别传递参数 HIVECLEANFLAG_SYSTEM 和 HIVECLEANFLAG_USERS,如果返回值为 TRUE 那么 filesys.exe 清除原来的注 册表文件,如果返回值为 FALSE 那么 filesys.exe 保留原来的注册表文件。默认 WINCE 并没有实现这个 IOCTL,所以 OEM 要删除注册表文 件就必须先编写这个 IOCTL 代码。代码的例子可参考标题为"IOCTL_HAL_GET_HIVE_CLEAN_FLAG"的帮助文档。另外必须在 ioctl.h和 ioctl.c 两个文件中编写该代码。在 ioctl.c 文件中找到 const OAL_IOCTL_HANDLER g_oalIoCtlTable[],添加 IOCTL 和对应的处理函数。
要进一步了解这个全局数组,参见标题为"IOCTL Library"的帮助文档。
z 如何在不删除必要组件的前提下减小内核文件长度?
要减小内核文件长度首先要在使用 PB 的定制内核向导中选择自定义,也就是说对于每个组件都由自己来选择,而不是选择 PB 的标准配置。
但减小内核文件长度最有效最直接的办法是缩小字体,尤其对于东亚字体,采用字体压缩技术并且选择合理的字库文件将明显缩小文件长度。
1、在定制内核时选择 AGFA AC3 Font Compression 组件。SYSGEN 变量为 SYSGEN_AGFA_FONT。
2、参考标题为"East Asian Font Versions"的帮助文档,从中选择你需要的字库文件加到内核中,从文档可以看出加 AC3 压缩比不加压缩在 文件长度方面差距很大。
z 如何得到 WAV 文件播放的总时间?
1、直接读取 wav 文件头信息,从文件起始地址偏移 28 个字节长度为 4 个字节保存的是每秒钟播放的字节数,从文件起始地址偏移 40 个字节长度为 4 个字节保存的是声音数据的总的字节数,相除就是播放时间。
2、调用 IGraphBuilder::RenderFile 打开一个 wav 文件,然后通过 IGraphBuilder 得到 IMediaSeeking 指针,再调用 IMediaSeeking::GetDuration得到总的时间(结果要除以 10000000),IMediaSeeking::GetCurrentPosition 得到当前播放时间。
z 如何在 Dialog-Based 程序中加入 menubar ?
先调用 CommandBar_Create 再调用 CommandBar_InsertMenubar。
z 请问 MultiByteToWideChar 与 _T 、 L 、 TEXT 的区别?
MultiByteToWideChar 函数转换的对象可以是常量也可以是变量。其它只能转换常量。_T 和 TEXT 会根据当前系统是否定义_UNICODE 宏来决定是否转换,而 L 就是转换成宽字符,当然也包括其他类型常量的转换。
z 在用 UBS 线缆通过 ActiveSync 同步有效的情况下,如何插上 USB 线缆后 WINCE 自动与 PC 同步?
1、新建一个拨号连接,假设名称为"usb1",选择连接类型为"直接连接",并在连接设备里选择通过 USB 线缆连接。
2、将注册表[HKEY_CURRENT_USER\Comm\RasBook\usb1]下的数据添加到 project.reg 或者 platform.reg 中。
3、在[HKEY_CURRENT_USER\ControlPanel\Comm]下添加如下:
"AutoCnct"=dword:1 ///直接连接
"Cnct"="usb1" ///连接名称
4、重新编译内核。为了节省编译时间也可以在内核工程下搜索*.reg 文件,将 2、3 步骤中的注册表数据添加其中,然后直接 make image。
z 如何通过进程句柄来获得该进程的主窗口句柄?
好像没有 API 能够通过进程句柄直接获得主窗口的句柄,因为并非每个应用程序都带 UI。但是可以反过来,先枚举当前系统所有主窗口,
然后根据每个窗口的句柄调用 GetWindowThreadProcessId 函数得到进程的 ID,再调用 OpenProcess 得到进程句柄,与现有的进程句柄比较。
z 我做的显示驱动 DLL 已经编译成功了,但是在加载显示驱动的过程中弹出话框,提示如下:
unhandled exception in gwes.exe (0xc0000005 access violation) 提示的错误--地址访问非法,表明你的驱动程序代码并没有在读写数据前添加 SetKMode(TRUE)或者 SetProcPermissions(0xFFFFFFFF)函 数让线程能够访问任何进程的地址空间。你可以调用 IsBadReadPtr 和 IsBadWritePtr 函数检测地址是否能够合法访问。编写和 gwes 有关的驱 动程序应该首先调用 SetKMode(TRUE)或者 SetProcPermissions(0xFFFFFFFF)函数,这是一个好习惯。
z 请问在嵌入式系统中如何设置 GPRS 拔号用的 APN ?
对一个拨号连接比如"我的连接"单击鼠标右键,在弹出的菜单中选择"属性",然后单击"配置"-"拨号选项",在"附加设置"中添加AT 命令如"+cgdcont=1,"ip","cmnet""。"cmnet"位置即为 APN。
z WINCE 的 IP Phone 功能如何?
WINCE 的 voip 需要 c-s-c 结构,既需要服务器的中转,而 skype 采用第三代 p2p 技术就不需要中转,但是在 gprs 下也做不到语音流畅。skype有 pocket pc 版本,但是无线方面需要 wlan 或者 cdma。
z 三星 ARM 平台如何定义自己的中断 ID ?
以 S3C2410 为例,在 oalintr.h 文件中定义中断 ID,也称 SYSINTR,例如 #define SYSINTR_MYINT (SYSINTR_FIRMWARE+20),最大值不能超过 SYSINTR_FIRMWARE+23。然后在 armint.c 文件中找到 OEMInterruptHandler 函数,用 if (IntPendVal == INTSRC_XXX) 判断当前 发生的中断源号,然后返回 SYSINTR_MYINT。内核分别调用 OEMInterruptDisable(禁止当前中断)、OEMInterruptDone(中断处理结束)、 OEMInterruptEnable(当前中断有效)三个函数,参数都为中断 ID,在这三个函数中用 case SYSINTR_MYINT 判断当前要处理的中断。
z 如何开发软件从 PC 端复制文件到基于 WINCE 的设备?
调用 RAPI(Remote Application Programming Interface)函数,此函数集由桌面计算机调用,由基于 WINCE 的设备执行。一旦连接上就可以在桌面计算机端调用 RAPI。通过注册表还可以限制 RAPI 能够访问目录的范围。具体参考 RAPI 和 RDP(远程桌面协议)。
z 请问如何对 NandFlash 分区、格式化?
你看看 WINCE420\PUBLIC\COMMON\OAK\DRIVERS\ETHDBG\BOOTPART\bootpart.cpp,在 Eboot 中先要调用 BP_LowLevelFormat(DWORD dwStartBlock, DWORD dwNumBlocks, DWORD dwFlags) 再 flash 的 一 个 区 域 建 立 空 的 MBR, 然 后 连 续 两 次 调 用 BP_OpenPartition(DWORD dwStartSector, DWORD dwNumSectors, DWORD dwPartType, BOOL fActive, DWORD dwCreationFlags)函数来建立 BINFS 和 FAT 分区。建好后,将 nk.bin 烧入 binfs 分区中。
z 要做个弹出对话框具有 always on top 属性,如何实现?
调用 SetWindowPos(.. , HWND_TOPMOST, ...., SWP_NOACTIVATE)。
z s3c2410 + WINCE 下网络 PING 一会就断,如何解决? 原因在于中断处理程序把已经产生的中断标志清除掉了,这样就丢失一次中断。因为原驱动里配置中断为上升沿触发,一次中断丢失就导致 不会再产生中断信号跳变,因为只有在中断服务中读取了 cs8900 的 Interrupt status queue 寄存器后,才会产生下一次中断!解决办法:
1、在 cfw.c 文件中全局定义 BOOL Inited = FALSE
2、修改 OEMInterruptEnable()中 case SYSINTR_ETHER: 下面的语句为:
if(Inited == FALSE)
{
s2410IOP->rEINTPEND = 0x200;
s2410INT->rSRCPND = BIT_EINT8_23;
if (s2410INT->rINTPND & BIT_EINT8_23)
s2410INT->rINTPND = BIT_EINT8_23; Inited = TRUE;
}
s2410IOP->rEINTMASK &= ~0x200;
s2410INT->rINTMSK &= ~BIT_EINT8_23;
break;
注:本解决办法转载于 http://stoned.blogchina.com/stoned/3083045.html ,非我本人研究成果。
z 已经搜索到文件,如何用 CListBox 以图标形式显示出来?
CListCtrl ListCtrl; CImageList ImageList;
ImageList.Create(IDB_BITMAP, 48, 2, RGB(0,0,0)); ListCtrl.SetImageList(&ImageList, LVSIL_NORMAL); ListCtrl.InsertItem(iListIndex, strItem, 1);
z 如何改变控制面板中电源属性对话框的尺寸 ?
1、需要修改对话框的尺寸是因为对话框是以资源方式加载的,不会根据当前系统显示分辨率而自我调节尺寸。
2、安装 WINCE 后有一些组件(feature)的资源文件*.res 就已经有了,如果你不改变,那么 build 内核的时候 PB 只是把这些.res 复制到工 程目录下,然后与*.obj 合并成 EXE、DLL、CPL。所以修改了.rc 文件里面的对话框尺寸后要重新编译.rc 文件为.res 文件,然后再覆盖原来WINCE 自带的.res 文件。
3、改变对话框尺寸有两种办法:一种方法是更改系统字体字号,系统字体的字号变化会影响对话框的尺寸,但是缺点是所有系统字体有关的 UI 都会改变。另一种是在.rc 文件中调整对话框尺寸,然后编译成.res 文件,再将.res 复制到对应的语言目录里,比如目录名为 0804(中 文),再执行 Rebuild 命令重新编译内核,或者执行sysgen+build。在研究中我发现.res 文件虽然能够直接用 EVC 打开、修改、保存,但是 和其它 Obj 链接成 EXE、DLL、CPL 后并不能运行,所以还是建议读者用 CE 自带的 rc 工具编译最好。读者可在 PB 的命令行中键入"rc /?"了解 rc.exe 工具的用途和参数。
z 使用 EVC build 之后连接模拟器的时候,提示 download file 等了一会又出现 download failed ?
一般这样的问题从下面几个步骤解决:
1、如果之前能启动模拟器而现在不能,那么先 clean 然后重启计算机再 build。
2、如果开发的主机为 WINXP+SP2,可能存在与 EVC 模拟器不兼容的情况,检查 C:\boot.ini,将/noexecute=optin 改为/execute=optin。
3、检查你的模拟器是否能运行,假设你正用的 SDK 名称为 MYSDK,单击菜单 tools-configure platform manager,选择 MYSDK-MYSDK emulator,再单击 properties-test,看看模拟器是否能够启动,如果能启动那问题就不大。
4、单击菜单 build-update remote output files,看看模拟器是否能够启动。
5、如果上述办法均不行,关闭 EVC 然后重新建立一个新的工程,编译,看看模拟器是否能够启动,如果能启动说明原来工程出了问题,最 好恢复原工程的备份。
z 如何设置能够自动拨号、禁止自动拨号? 在[HKEY_LOCAL_MACHINE\Comm\Autodial]下是自动拨号的注册表设置。 Enabled=DWORD:1 ///是否能够自动拨号
FailRetryWaitMS=DWORD ///如果失败再次拨号的等待时间 RasEntryName1= REG_SZ ///自动拨号采用的拨号连接名称 更多细节请参考标题为"Auto Dial Registry Settings"的帮助文档。