嵌入式平台下应用于民用领域的软件有它的特点:占用资源低、操作界面简单、反应迅速。而且传统观念上嵌入式平台同一时刻只运行一两个程序。这不同于PC下软件,除了实现基本功能外,还要求华丽的界面,甚至换肤,启动N个进程也无所谓。所以在Windows CE下构思一个应用于民用领域的软件时,必须考虑软件的实用性、易操作性等。太复杂的软件就不要想了,也不要妄想把PC下所有软件都移植到Windows CE下。
不过随着嵌入式CPU的快速发展和RAM的降价,在Windows CE下实现多个程序同时运行也是可行的。而且运行速度也可以接受。我们公司的产品就能够一边上网,一边听mp3。使用起来真的很爽。
假如我们要做一个产品,需要同时运行多个程序,这就涉及到窗口切换的问题。Windows CE 自带了资源管理器explorer.exe作为外壳程序。它有窗口切换的功能。但是绝大多数基于Windows CE的产品都不可能用这个程序做主界面。原因之一,它的界面和PC下资源管理器一样(也就是桌面),不符合我们这个产品的整体界面风格。可能我的所有应用程序的界面风格都很好,很漂亮,但主界面,既资源管理器的界面和其它所有程序界面风格不相符,那样太糟糕了。而且总用同一个界面,用户迟晚会烦的。原因之二,它将所有资源管理功能都暴露给用户,那样太危险。用户肆意更改资源设置很容易造成设备损坏或无法启动,一旦无法启动,用户一定会来找你维修,但是你很冤。所以我们必须自己做一个主操作界面(也就是一个应用程序,或者称为外壳程序)。这样既可以设计实现自己理想的界面风格,又可以限制留给用户操作的功能。这个想法虽然好,但是如果允许同时运行多个程序,就必须先解决窗口切换问题。好在Windows CE 的API和PC下的差不多,实现也不算难。其实难的是如何把自定义的程序作为外壳程序,替换原来标准的程序。
注:Windows CE允许同时打开最多32个进程,这包括Windows CE启动时系统程序。
注:以后称主操作界面程序为自定义外壳程序。
一、 添加自定义外壳
原来打算不讲解PB方面的知识,但是在Windows CE下开发,PB和EVC关系紧密,必须两个都懂,才能做出理想的产品。所以在这里我简单说说PB和CE方面的知识。
1、PB是用于定制操作系统内核的工具,可以把它看成是一个IDE,集成了编辑器(可以编辑源码、注册表等)、编译器(编译操作系统内核、编译某一个模块)、各种向导(生成新内核向导、生成BSP向导、SDK导出向导、组件特征导出向导)、内核调试工具集合、x86模拟器、支持添加或删除组件等。通常情况下,通过PB来添加、删除组件,每个组件对应一个特定的功能。假如你希望你的产品具有上网功能,那你就添加某一种网络连接组件和浏览器组件。假如希望你的产品具有永久存储功能,那你就添加支持永久存储设备的组件,再添加支持某一种文件系统的组件。设置完毕后,编译整个内核。最后PB将编译后的所有功能模块组合在一起,形成一个.bin文件,默认为nk.bin。
2、nk.bin包含了你定义的Windows CE内核的全部。在设备启动时需要解压nk.bin并且将一些系统模块(DLL、EXE)加载到内存中。用于做解压和加载工作的程序,称为引导程序,可以放在ROM中,也可以放在永久存储设备中。当设备启动时先检测硬件,再加载引导程序,引导程序解压nk.bin,再把系统模块加载到特定内存区域中,然后将CPU交给CE内核。
3、当你安装完Windows CE.net(Windows CE.net 安装分为三部分。分别安装PB、Platform manager、SDK)后,大部分功能模块的源码被复制到安装目录下。你可以修改这些源码,然后重新编译。这些源码为OEM提供了更广阔的开发空间。也是Windows CE比其它Windows操作系统更适合研究的原因。
4、修改源码不是简单的事。使用PB开发一个内核很少需要修改源码,更多的工作是添加组件、添加文件、修改注册表数据、添加环境变量等。要添加自定义外壳,涉及到修改注册表和添加文件两个工作。
下面讲具体操作方法:
以下假设你已经利用生成内核向导建立了一个内核工程。
1、修改注册表:
Windows CE启动时分别执行[HKEY_LOCAL_MACHINE\init]键下所有子键列出的程序。例如:
[HKEY_LOCAL_MACHINE\init]
"Launch20"="device.exe"
"Launch30"="gwes.exe"
"Depend30"=hex:14,00
内核启动时执行device.exe和gwes.exe 。“LaunchXX”中的XX为序列数,内核依据这个序列数按由小到大的顺序来分别执行所有子键列出的应用程序。“DependXX”为“LaunchXX”的附属键,此键键值表示“LaunchXX”指定的程序需要依靠哪个程序才能启动。例如“Depend30”=hex:14,00 。十六进制数14等于十进制数20,既gwes.exe需要device.exe先启动后才能启动。因为自定义外壳程序是最后启动的,所以你可以在你的平台选择一个最大的序列数,比如80,然后在common.reg中“[HKEY_LOCAL_MACHINE\init]”键下添加如下格式:
"Launch80"="CustomShell.exe" ///自定义的外壳程序,名为CustomShell.exe
"Depend80"=hex:14,00,1E,00 ///依靠gwes.exe 和 device.exe
添加后千万别忘了把原来默认的标准外壳组件从你的内核中删除,找到“Shell and User Interface-Shell-Standard Shell”,单击鼠标右键,再单机“delete”。当然,你也可以把其它外壳程序都删除。
修改注册表工作到此结束。
2、添加自定义外壳程序:
只要允许多个程序同时运行,必然涉及到窗口切换问题,但不同的产品所采取的窗口切换解决方案也不一样。假如你的产品携带标准键盘,那你可以注册一个或几个热键,当热键按下时,启动窗口切换程序,然后选择你要操作的窗口,然后窗口在最上层显示。这比较像Windows下任务管理器。如果你的产品没有携带标准键盘,而是几个按钮,那可以通过串口通讯方式或其它通讯方式解决,当按下按钮时,通过串口发送消息给系统,系统启动窗口切换程序。如果你的产品在外观上没有键盘,也没有任何按钮,那就必须做一个窗口,像任务栏一样,让它时刻保持在所有窗口最上层,以便响应用户操作。具体方法你自己选。我假设现在你用EVC已经把自定义外壳程序做完了。接着要做的是如何将自定义外壳程序放入内核中,也就是nk.bin里。从上述“修改注册表”中你会发现,要启动的程序都只有文件名,而没有路径名,这是因为CE只允许把启动程序放到nk.bin中,nk.bin中的所有文件在CE系统启动后被放在目录\Windows 中。这一步要做的就是把已经做好的自定义外壳程序放入nk.bin中。
先把编译完的程序CustomShell.exe复制到内核工程目录下。假设你的内核名为ABC,内核工程所在路径为C:\ABC,采用CPU为National Geode。那么就复制到“C:\ABC\RelDir\NATIONAL_GEODE_X86Release”目录中。接着在project.bib中添加下列格式:
CustomShell.exe $(_FLATRELEASEDIR)\CustomShell.exe NK
这表示在PB编译内核时将CustomShell.exe加入到nk.bin中,并且在CE内核加载时将CustomShell.exe放入内存的系统区域中,内核启动后在\windows目录下可以看到这个文件。
添加自定义外壳程序工作到此结束。
3、编译内核
现在编译前期工作已经完成,单击“Build Platform”或“Rebuild Platform”开始编译。编译前相关设置在此不再多说。启动CE内核后就可以看到自己设计的外壳程序了。
二、实现窗口切换
图1 假想的任务管理器界面
一个简单的窗口切换实例。打开此程序时,显示所有窗口(不包含隐藏窗口),单击某一个窗口名称,再单击“切换”按钮,相应的窗口出现在Z轴最上层。在这里我只给出了部分重要的代码。此处假设工程名为WndList。
///全局函数和全局变量
HWND g_hWndArray[50]; //存所有窗口句柄
CString g_strArray[50]; //存所有窗口标题
int g_iWndCount = 0; //计数器
BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lparam)
{
CWnd* pWnd = CWnd::FromHandle(hWnd);
if(hWnd != NULL && pWnd->IsWindowVisible())
{
pWnd->GetWindowText(g_strArray[g_iWndCount]);
if((!g_strArray[g_iWndCount].IsEmpty())
&& (g_strArray[g_iWndCount].CompareNoCase(L"Desktop") != 0))
{
g_hWndArray[g_iWndCount] = hWnd; //保存句柄
g_iWndCount++;
}
}
return TRUE;
}
BOOL CWndList::OnInitDialog()
{
::EnumWindows((WNDENUMPROC)EnumWindowsProc, 0); /////查找所有窗口
////创建大字体并加入到列表对象中
////将所有窗口句柄加入到列表中
}
///相应“切换”按钮
void CWndList::OnOK()
{
::SetForegroundWindow(g_hWndArray[m_List.GetCurSel()]); //m_List为列表对象
}
写作时间:2004-5-8
未经本文作者同意,不准擅自转载本篇文章。