作者在 2007-07-06 06:37:00 发布以下内容
最近在做的一个软件,其中有一部分功能需要调用其它的软件来完成,而那个软件只有可执行文件,根本没有源代码,幸好,我要做的事不难,只需要在我的程序启动后,将那个软件打开,在需要的时候,对其中的一个文本矿设置一些文字,再点击一个按钮就可以了。
说到这里,相信你也有了对该功能的一些初步设想了,没错,其基本思路就是:
1)调用CreateProcess()打开目标程序。
2)用FindWindow()找到目标程序的窗口Handle。
3)找到文本框的Handle,以及按钮的MessageID,用SendMessage()方法设置文字,并触发事件。
好了,这样确实很简单吧,但是当我实现它后,却发现这样做的结果则是:当我的程序启动并打开目标程序时,它的Splash窗口,以及主窗口都将显示出来,即使当我用FindWindow()找到主窗口Handle后,调用SendMessage(WindowHandle, SW_HIDE)来隐藏该窗口,还是会有一瞬主窗口被显示出来的,这样的效果实在是最求完美的我不忍心看到的。
那么怎么解决这个问题呢,首先我当然在CreateProcess()上面寻找方法,可惜,它只有一个参数可以设置窗口的默认显示方式,但是一旦这个窗口自己重设了显示方式,它就没有任何作用了。。。。继续查找文档,这时我看到CreateProcess()的一个参数TStartupInfo中有 lpDesktop这么一个属性,按照MSDN的说法,如果该指针为NULL,那么新建的Process将在当前Desktop上启动,而如果对其赋了一个Desktop的名称后,Process将在指定的Desktop上启动,恩,看来不错,就从它入手了:
1)首先,建立一个虚拟的Desktop,
const
DesktopName = 'MYDESK';
FDesktop:=CreateDesktop(DesktopName,nil,nil,0,GENERIC_ALL,nil);
Windows中可以建立多个Desktop,可以使用SwitchDesktop()来切换哪个Desktop被显示出来,以前有过将Windows模拟成Linux的形式,可以在多个虚拟Desktop中切换的程序,其实那种程序也是用的Windows本身的虚拟Desktop功能来实现的,另外 Windows的启动画面,以及屏保画面也都是用虚拟Desktop实现的,好了,关于这方面不多介绍了,感兴趣的话,可以到MSDN中查看更详细资料:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/enumdesktops.asp
2)在CreateProcess的时候,指定程序在我新生成的Desktop上运行:
var
StartInfo:TStartupInfo;
FillChar(StartInfo, sizeof(StartInfo), 0);
StartInfo.cb:=sizeof(StartInfo);
StartInfo.lpDesktop:=PChar(DesktopName); //指定Desktop的名称即可
StartInfo.wShowWindow:=SW_HIDE;
StartInfo.dwFlags:=STARTF_USESHOWWINDOW;
StartInfo.hStdError:=0;
StartInfo.hStdInput:=0;
StartInfo.hStdOutput:=0;
if not CreateProcess(PChar(FileName),nil,nil,nil,true,CREATE_NEW_CONSOLE+HIGH_PRIORITY_CLASS,nil,PChar(ExtractFilePath(FilePath)),StartInfo,FProceInfo) then begin
MessageBox(Application.Handle,'Error when init voice (5).',PChar(Application.Title),MB_ICONWARNING);
exit;
end;
3)用FindWindow去找程序的主窗口
开始我直接写下了这样的代码:
for I:=0 to 60 do begin //wait 30 seconds for open the main window
WindowHandle:=FindWindow(nil,'WindowCaption');
if WindowHandle<>0 then begin
break;
end;
Sleep(500);
end;
但是,实践证明,这样是找不到不在当前Desktop中的Window的,那怎么办呢:
答案是,可以用SetThreadDesktop()函数,这个函数可以设置当前Thread工作所在的Desktop,于是我在以上代码前又加了一句:
if not SetThreadDesktop(FDesktop) then begin
exit;
end;
但是,程序运行后,该函数却返回了false,说明方法调用失败了,再仔细看MSDN,发现有这么一句话:
说到这里,相信你也有了对该功能的一些初步设想了,没错,其基本思路就是:
1)调用CreateProcess()打开目标程序。
2)用FindWindow()找到目标程序的窗口Handle。
3)找到文本框的Handle,以及按钮的MessageID,用SendMessage()方法设置文字,并触发事件。
好了,这样确实很简单吧,但是当我实现它后,却发现这样做的结果则是:当我的程序启动并打开目标程序时,它的Splash窗口,以及主窗口都将显示出来,即使当我用FindWindow()找到主窗口Handle后,调用SendMessage(WindowHandle, SW_HIDE)来隐藏该窗口,还是会有一瞬主窗口被显示出来的,这样的效果实在是最求完美的我不忍心看到的。
那么怎么解决这个问题呢,首先我当然在CreateProcess()上面寻找方法,可惜,它只有一个参数可以设置窗口的默认显示方式,但是一旦这个窗口自己重设了显示方式,它就没有任何作用了。。。。继续查找文档,这时我看到CreateProcess()的一个参数TStartupInfo中有 lpDesktop这么一个属性,按照MSDN的说法,如果该指针为NULL,那么新建的Process将在当前Desktop上启动,而如果对其赋了一个Desktop的名称后,Process将在指定的Desktop上启动,恩,看来不错,就从它入手了:
1)首先,建立一个虚拟的Desktop,
const
DesktopName = 'MYDESK';
FDesktop:=CreateDesktop(DesktopName,nil,nil,0,GENERIC_ALL,nil);
Windows中可以建立多个Desktop,可以使用SwitchDesktop()来切换哪个Desktop被显示出来,以前有过将Windows模拟成Linux的形式,可以在多个虚拟Desktop中切换的程序,其实那种程序也是用的Windows本身的虚拟Desktop功能来实现的,另外 Windows的启动画面,以及屏保画面也都是用虚拟Desktop实现的,好了,关于这方面不多介绍了,感兴趣的话,可以到MSDN中查看更详细资料:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/enumdesktops.asp
2)在CreateProcess的时候,指定程序在我新生成的Desktop上运行:
var
StartInfo:TStartupInfo;
FillChar(StartInfo, sizeof(StartInfo), 0);
StartInfo.cb:=sizeof(StartInfo);
StartInfo.lpDesktop:=PChar(DesktopName); //指定Desktop的名称即可
StartInfo.wShowWindow:=SW_HIDE;
StartInfo.dwFlags:=STARTF_USESHOWWINDOW;
StartInfo.hStdError:=0;
StartInfo.hStdInput:=0;
StartInfo.hStdOutput:=0;
if not CreateProcess(PChar(FileName),nil,nil,nil,true,CREATE_NEW_CONSOLE+HIGH_PRIORITY_CLASS,nil,PChar(ExtractFilePath(FilePath)),StartInfo,FProceInfo) then begin
MessageBox(Application.Handle,'Error when init voice (5).',PChar(Application.Title),MB_ICONWARNING);
exit;
end;
3)用FindWindow去找程序的主窗口
开始我直接写下了这样的代码:
for I:=0 to 60 do begin //wait 30 seconds for open the main window
WindowHandle:=FindWindow(nil,'WindowCaption');
if WindowHandle<>0 then begin
break;
end;
Sleep(500);
end;
但是,实践证明,这样是找不到不在当前Desktop中的Window的,那怎么办呢:
答案是,可以用SetThreadDesktop()函数,这个函数可以设置当前Thread工作所在的Desktop,于是我在以上代码前又加了一句:
if not SetThreadDesktop(FDesktop) then begin
exit;
end;
但是,程序运行后,该函数却返回了false,说明方法调用失败了,再仔细看MSDN,发现有这么一句话:
The SetThreadDesktop function will fail if the calling thread has any windows or hooks on its current desktop (unless the hDesktop parameter is a handle to the current desktop).
哦,原来需要切换Desktop的线程中不能有任何UI方面的东西,而我是在程序的主线程中调用该方法的,当然会失败拉,知道了这点就好办了,我只需要用一个“干净”的线