对于Pocket PC上的软件输入面板(SIP)开发者,或者那些为自定义输入法开发卸载程序的人来说,如何卸载DLL以更新或从设备中移除它们是一个常见的问题。然而,似乎很难找到不重启系统就实现这一目标的方法。本文旨在解决这一问题。
根据官方文档,SIP应该在调用IInputMethod->Deselect()
方法后释放输入法COM对象,即在另一个输入法被选中后。可以确认SIP确实释放了它。因此,最简单的方法似乎是简单地选择另一个输入法。然而,这并不奏效。
众所周知,COM子系统不会立即卸载未使用的DLL。相反,它等待用户代码调用CoFreeUnusedLibraries()
或CoFreeUnusedLibrariesEx()
。SIP确实调用了第一个函数,但不幸的是,这并不足以立即卸载所有未使用的输入法。在这个函数内部,COM子系统为所有似乎不再使用的DLL调用DllCanUnloadNow()
例程,并将所有返回S_OK
(DLL可以卸载)的DLL放入候选卸载列表。当这个列表中的DLL再次需要COM时(例如,应用程序请求创建位于此DLL中的新COM对象),该DLL将从候选列表中移除。如果某个DLL在调用CoFreeUnusedLibraries()
或CoFreeUnusedLibrariesEx()
时仍然位于候选列表中,并且自将DLL放入列表以来已经过去了足够的时间,那么这个DLL最终会被卸载。
当使用CoFreeUnusedLibrariesEx()
时,可以指定第一个参数为时间间隔,但对于SIP使用的CoFreeUnusedLibraries()
,这个超时是系统默认值,即10分钟!对于用户来说,等待时间太长了...
解决这个问题的一种可能方法是在SIP所在的进程中调用CoFreeUnusedLibrariesEx()
,并使用零或接近零的超时参数。幸运的是,这很容易实现,因为SIP是作为设备驱动程序实现的,并且位于所有设备驱动程序所在的device.exe
进程中。因此,可以创建一个简单的驱动程序,调用所需的函数,然后加载它,享受移除DLL的可能性。当然,在这样做之前,应该先取消选择输入法。还有一个技巧。
如果能在Init()
入口点调用CoFreeUnusedLibrariesEx()
,那么驱动程序会更简单。但这并不奏效,因为COM子系统需要所有者进程ID等于当前进程ID(device.exe
),这样才能卸载DLL。然而,在Init()
调用中的所有者进程ID等于加载驱动程序的进程ID(即示例程序),因此COM尝试卸载这个示例应用程序的COM DLL。
为了克服这个问题,只需要创建另一个线程,并从这个线程的上下文中进行调用。这个线程的所有者进程ID等于device.exe
的ID,SIP和输入法就生活在那里。
示例可以用eVC++ 4.0编译。归档中有两个项目:一个简单的设备驱动程序,加载后会按照上述描述调用CoFreeUnusedLibrariesEx()
;以及一个简单的可执行文件,用于加载这个驱动程序。
驱动程序公开了所需的最小入口点集(Init/Deinit
,Open/Close
,IOControl
),因为只需要它被加载到device.exe
中,不需要更多。