65.9K
CodeProject 正在变化。 阅读更多。
Home

自动启动您的 Windows Mobile 应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (25投票s)

2008年7月18日

CPOL

10分钟阅读

viewsIcon

229900

downloadIcon

2397

本文讨论了在 Windows Mobile 上自动启动应用程序的各种方法,包括按计划启动或响应系统事件启动。

引言

如何让应用程序自动启动,对我来说曾是一个长期的谜团。信息分散,需要进行一些研究才能收集齐全。在研究过程中,我将所有笔记整理在不同的文档中,并决定将它们整合到一个文档中与大家分享。本文提供的信息已在 Windows Mobile 5 和 Windows Mobile 6 设备上进行过测试,但也可应用于 Windows CE 的多个版本(Windows Mobile 即源自于此操作系统)。

可以通过编写自己的可执行程序来监视 SystemState 类中的系统变化并做出相应响应,从而实现类似的功能,但这会增加程序的占用空间。

关于代码

自动启动程序的第一个方法是围绕我称之为配置任务的几个方面:创建快捷方式、注册表项或将文件放置在特定位置。对于这些方法,我没有提供代码示例。本文后面,我将讨论通过代码设置自动启动应用程序的方法。对于这些方法,我已在文章中包含示例代码,并在文章中附带了完整的源代码。

代码示例依赖于通过 P/Invoke 调用的一些 Win32 函数。两个代码示例都引用了一个名为 Win32 的项目,该项目包含了这些函数的引用。此外,Win32 还包含一个结构和一个枚举,用于正确地将信息传递给 WinAPI 函数。要运行代码示例,您需要一台 Windows Mobile 设备(或模拟器)和 Visual Studio 2008。

请记住,在 Windows Mobile 设备上,您应该防止您的程序在内存中存在多个实例(如果启动了第二个实例,它应该通知第一个实例然后立即终止)。

“自动启动”是什么意思?

当我使用“自动启动”这个词时,我指的是基于除用户单击程序图标之外的任何事件来启动任何程序。程序可以通过以下四种方式之一自动启动:

  • 插入内存卡后,内存卡上的程序立即运行
  • 程序按预定时间安排启动
  • 程序响应系统更改而启动
  • 程序在设备开机时启动

内存卡自动运行

用户插入内存卡可以通过两种方式之一触发应用程序启动。内存卡上可能有一个程序会在插入时启动,或者 Windows Mobile 设备可能会响应内存卡插入而在其主存储中启动一个已存在的程序。后一种方式将通过注册应用程序在系统更改时启动来实现(稍后讨论)。

当内存卡插入 Windows Mobile / Windows CE 设备时,操作系统会在一个特定文件夹中自动查找名为 Autorun.exe 的程序。如果找到该程序,它将立即运行。操作系统查找的文件夹将取决于设备处理器的类型。对于绝大多数 Windows Mobile 设备,该文件夹将是“/2577”。以下是其他 Windows CE 设备可能的文件夹名称的表格:

Processor 文件夹名称
ARM 720 1824
Arm 820 2080
ARM 920 2336
ARM 7TDMI 70001
Hitachi SH3 10003
Hitachi SH3E 10004
Hitachi SH4 10005
Motorola 821 821
SH3 103
SH4 104
Strongarm 2577

如果您已经在内存卡上有一个想要自动运行的应用程序,但不想重命名可执行文件或将其放在该文件夹中,那么您总是可以创建一个第二个可执行文件,其目的是启动您的第一个可执行文件。

启动快捷方式

可以创建一个快捷方式指向您希望自动启动的应用程序,并将其放置在 \Windows\StartUp 文件夹中。如果您有一个需要启动的单一可执行文件,并且它没有对其他可执行文件的依赖,则可以使用此方法。Windows Mobile 设备上快捷方式的格式很简单。它始终为 00#"<\program Files\path>" 的形式,其中 00 被替换为 '#' 符号后的字符数;'#' 是分隔符,然后是可执行文件的完整路径。以下是 Windows Media Player 快捷方式的示例:

23#“\windows\wmplayer.exe”

不过,您不必手动创建快捷方式。有一个原生 API 调用 SHCreateShortcut 可以为您创建快捷方式。第一个参数是要创建的快捷方式的完整路径,第二个参数是快捷方式指向的文件的完整路径。

启动应用程序

对于 Windows Mobile 设备,自动启动条目的位置是 HKEY_LOCAL_MACHINE\Init。与桌面计算机上的启动条目不同(后者只需要可执行文件的路径即可启动),Windows Mobile 设备的条目结构稍微复杂一些。一个需要自动启动的应用程序有两个相关的键:一个 LaunchXX 键和一个可选的 DependXX 键。XX 将被替换为一个数字。这个数字也称为序列号。LaunchXX 的值是一个字符串值(REG_SZ),包含要启动的可执行文件的路径。DependXX 键用于指定当前应用程序依赖于哪些应用程序(因此应用程序必须以何种顺序启动)。DependXX 键包含一个单词(2 字节)值列表,其中包含必需应用程序的序列号值。

下面的屏幕截图显示了我的 Windows Mobile 5 手机上的注册表。Launch21 指的是一个名为“coldinit”的应用程序。如果我们查看 Depend21,会发现“coldinit.exe”依赖于一个由 0x14(十进制 20)标识的应用程序。因此,“coldinit.exe”必须在 Launch20 中标识的应用程序“Device.exe”之后启动。

Screen shot of Windows Mobile Registry

使用此方法启动的应用程序必须通过调用 SignalStarted(DWORD) 函数来通知应用程序成功启动。这是一个原生调用。对于 C 程序,此函数的头文件定义在 Winbase.h 中,库文件为 Coredll.lib。使用托管代码的开发人员需要通过 P/Invoke 调用此方法。该函数唯一的参数是可执行文件的序列号。序列号作为唯一的命令行参数传递给应用程序。请注意,序列号是应用程序可以通过命令行参数接收的唯一参数。必须传递给应用程序的任何其他信息都应通过配置文件或注册表项传递。

在指定时间启动程序

Windows CE / Windows Mobile 操作系统包含在指定时间自动启动程序的内置功能。该功能可通过调用 CoreDLL 库中的 CeRunApAtTime 来实现。正如 Jim Wilson 在 MSDN 的许多“我该如何”视频帖子中所提到的,此函数期望启动时间以 WinAPI SystemTime 结构而不是 DateTime 结构指定(CeRunAppAtTime 是一个使用平台调用功能调用的非托管函数)。将 DateTime 转换为 SystemTime 并不困难;有 WinAPI 函数可以为您完成此操作。为了更方便地调用此函数,我将以下代码放在了我的 Win32Helper 类中。我还提供了一个重载函数,允许将时间作为当前时间的偏移量,并使用 TimeSpan 对象传递。

public static void RunAppAtTime(string applicationEvent, DateTime startTime)
{
    long fileTimeUTC = startTime.ToFileTime();
    long fileTimeLocal = 0 ;
    SystemTime systemStartTime = new SystemTime();
    CoreDLL.FileTimeToLocalFileTime(ref fileTimeUTC, ref fileTimeLocal);
    CoreDLL.FileTimeToSystemTime(ref fileTimeLocal, systemStartTime);
    CoreDLL.CeRunAppAtTime(applicationEvent, systemStartTime);
}
public static void RunAppAtTime(
     string applicationEvent, 
     TimeSpan timeDisplacement
)
{
    DateTime targetTime = DateTime.Now + timeDisplacement;
    RunAppAtTime(applicationEvent, targetTime);
}

applicationEvent 是要启动的应用程序的完整路径,startTime 是应用程序应执行的时间。还有一个方法的重载版本,它接受一个 TimeSpan 对象而不是 DateTime 对象,如果您想指定相对于当前时间的启动时间。

如果一个应用程序尝试将自己安排在稍后时间重新启动,它将需要能够传递其完整路径。我使用 Reflection 来查找该路径。

Module[] m = this.GetType().Assembly.GetModules();
target = m[0].FullyQualifiedName;

Screenshot of timed start program

因系统更改而运行程序

有许多系统更改可用于触发程序的执行。WinAPI 函数 CeRunAppAtEvent 用于将程序与事件关联。一旦关联,每当该事件发生时,程序都会被启动!因此,您还必须记住在不再希望程序自动启动时解除该程序与事件的关联。

我在 Win32 类中创建了一个名为 WhichEvent 的枚举,其中包含可用于触发程序执行的事件的 ID 号。

当程序因系统状态更改而启动时,会向程序传递一个单个参数,指示触发程序执行的状态更改。(我在此处不讨论如何执行此操作的细节)。有关可能的参数的完整列表,请参阅 AutoStartArgumentString.cs 中的示例代码。

枚举元素 描述

NOTIFICATION_EVENT_NONE

用于清除与程序关联的所有事件

NOTIFICATION_EVENT_TIME_CHANGE

NOTIFICATION_EVENT_SYNC_END

ActiveSync 同步在设备上完成

NOTIFICATION_EVENT_ON_AC_POWER

设备充电器已连接

NOTIFICATION_EVENT_OFF_AC_POWER

设备充电器已断开连接

NOTIFICATION_EVENT_NET_CONNECT

设备已连接到网络

NOTIFICATION_EVENT_NET_DISCONNECT

设备已断开与网络的连接

NOTIFICATION_EVENT_DEVICE_CHANGE

已插入或移除内存卡或其他设备

NOTIFICATION_EVENT_IR_DISCOVERED

设备已检测到其他红外设备

NOTIFICATION_EVENT_RS232_DETECTED

设备已连接到 RS232 设备

NOTIFICATION_EVENT_RESTORE_END

设备已完成完整还原

NOTIFICATION_EVENT_WAKEUP

设备已从挂起状态唤醒

NOTIFICATION_EVENT_TZ_CHANGE

设备的时区已更改

NOTIFICATION_EVENT_MACHINE_NAME_CHANGE

设备名称已更改

我创建了一个名为 Core 的类,用于声明 CoreDLL.dll 库中的平台调用方法,并在其中声明了 CeRunApAtEvent 函数。以下代码安排 Windows 计算器在设备从挂起状态唤醒时启动:

CoreDLL.CeRunAppAtEvent(@"\Windows\Calc.exe", 
       (int)WhichEvent.NOTIFICATION_EVENT_DEVICE_CHANGE);

在此调用之后,Windows 计算器将每次设备唤醒时启动。要阻止计算器启动,需要进行第二次调用:

CoreDLL.CeRunAppAtEvent(@"\Windows\Calc.exe", 
       (int)WhichEvent.NOTIFICATION_EVENT_NONE);

本文包含一个示例应用程序,可用于触发应用程序在各种事件中启动。最初,此程序仅注册一个程序以在唤醒时启动。但我已扩展该程序,使其还能响应其他事件来启动程序。请注意,通过更改 CERunAppAtEvent 调用中的值,您可以使用该程序来触发一个可执行文件在其他事件(例如插入内存卡或 ActiveSync 完成同步)时启动。

WiMoAutostart/WakeupStart.png

当程序自动启动时,会通过命令行将一个字符串传递给它,指示启动它的事件。我已将一个名为 ShowCommandLine 的程序包含在源代码中,它只会显示它收到的命令行参数。从下面的屏幕截图中,您可以看到程序在连接到网络连接时启动时收到的命令行参数。

WiMoAutostart/ShowCommandLine.png

防止多个实例

通常,.NET Framework 会负责确保您的程序不会运行多个实例。对于因系统事件而启动的程序,这种情况并不总是有效。多个系统事件可能会快速连续触发,或者同一个事件可能会(由于某种奇怪的原因)触发两次(唤醒事件通常会触发两次)。我第一次尝试将 ShowCommandLine 程序安排在唤醒或其他事件时启动时,结果运行了多个实例。

WiMoAutostart/MultiInstance.png

为了解决这个问题,我在加载窗体之前使用 P/Invoke 创建了一个事件对象。如果多个程序实例快速连续启动,则事件创建将失败,我们可以利用此失败来知道这不是程序的第一个实例,并立即卸载它。

static void Main()
{
    IntPtr eventHandle = IntPtr.Zero;
    const string ApplicationEventName = "ShowCommandLineEvent";
    //We will use this as our handle to ensure only one instance of the
    //program is started            

    try
    {
        //Try to create the event. If the creation fails then it is 
        //because another instance of this application is already 
        //running. If another instance exists then this instance
        //should immediatly terminate.
        eventHandle = CoreDLL.CreateEvent(IntPtr.Zero, true, 
                                          false, ApplicationEventName);
        int lastError = Marshal.GetLastWin32Error();
        //MessageBox.Show(String.Format("event handle {0}",eventHandle));
        
        if (0 == lastError)
        {
            Application.Run(new Form1());
        }
    }
    finally
    {
        //When the application is no longer 
        //running it should release the event
        if (eventHandle != IntPtr.Zero)
            CoreDLL.CloseHandle(eventHandle);
    }
}

当以这种方式创建程序的多重实例时,您可能希望向第一个实例发送通知,以便它能够响应事件。

接下来呢?

本文很大程度上是我为计划设计的软件解决方案进行准备的研究成果。在我接下来的研究中,我将开发一种解决方案,用于让手机响应 CeRunAppAtEvent 函数未直接公开的其他事件。

历史

  • 2008 年 7 月 19 日 - 初次发布。
  • 2008 年 7 月 31 日 - 添加了对 SHCreateShortcut 的引用。(感谢 Zoomesh!)
© . All rights reserved.