XP下切换输入法造成程序卡死的原因及解决方案
(by ysai)
现象:
在XP下,如果线程中创建了窗口而线程中没有消息循环,那么可能切换输入法时会造成程序卡死(某些XP下必现,跟安装盘有关)
原因:
线程创建一个窗口后,系统会自动创建一个Default IME窗口以便通知输入法消息(可能只有可以接收输入的窗口才会创建,未证实)
XP下切换输入法,会向所有DefaultIME窗口SendMessage告诉应用程序,当前输入法改变了
而线程窗口如果没有消息循环,则不会处理消息队列,然后卡死
演示代码:
TTestThread = class(TThread)
private
procedure ProcessMessages;
protected
procedure Execute; override;
end;
procedure TTestThread.Execute;
begin
TTimer.Create(nil); //TTimer会创建隐藏的窗口
while not Terminated do
begin
DoSomething;//线程干活
//ProcessMessages; //去掉这一句就可能卡死
Sleep(1);
end;
end;
///内建的一个简单消息循环
procedure TTestThread.ProcessMessages;
var
Msg: TMsg;
begin
while PeekMessage(Msg, 0, 0, 0,PM_REMOVE) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;
程序中使用上面的线程,则在XP下切换输入法可能卡死
即使线程中加入了消息循环,如果在线程工作时DoSomething占用过多时间,也会假死
终极解决方案:
如果不能避免在线程中创建窗口(比如调用了某个COM组件,但COM内部创建了窗口,如ADO会创建一个ADODB.AsyncEventMessenger窗口)
则在线程创建窗口后,执行以下代码
procedure FreeIMEWindow;
const
IME_WINDOW_CLASS ='IME';
IME_WINDOW_TEXT = 'Default IME';
var
h : HWND;
pid : DWORD;
dh : HWND;
begin
if GetCurrentThreadId= 主线程IDthen exit; //如果是主线程,那么它应该有消息循环,可以不处理
h := FindWindow(IME_WINDOW_CLASS, IME_WINDOW_TEXT);
while IsWindow(h) do
begin
ifGetWindowThreadProcessId(h, pid) = GetCurrentThreadId then
dh := h
else
dh := 0;
h:= FindWindowEx(0, h, IME_WINDOW_CLASS, IME_WINDOW_TEXT);
if dh<> 0 then
DestroyWindow(dh);
end;
end;
procedure TTestThread.Execute;
begin
TTimer.Create(nil); //TTimer会创建隐藏的窗口
FreeIMEWindow; //释放IME窗口
while not Terminated do
begin
DoSomething;//线程干活
Sleep(1);
end;
end;