后台调用外部程序的完美实现(Delphi)

0 views
Skip to first unread message

yisu.tech

unread,
Dec 6, 2007, 9:40:03 PM12/6/07
to DelphiTopics

后台调用外部程序的完美实现(Delphi)

  其基本思路就是:
  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 <http://www.jpdown.com>中可以建立多个Desktop,可以使用
SwitchDesktop()来切换哪个Desktop被显示出来,以前有过将Windows
<http://www.jpdown.com>模拟成Linux的形式,可以在多个虚拟Desktop中切换的
程序,其实那种程序也是用的Windows <http://www.jpdown.com>本身的虚拟
Desktop功能来实现的,另外 Windows <http://www.jpdown.com>的启动画面,以
及屏保画面也都是用虚拟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 <http://www.jpdown.com> or hooks on its current desktop
(unless the hDesktop parameter is a handle to the current desktop).
  
  原来需要切换Desktop的线程中不能有任何UI方面的东西,而我是在程序的主
线程中调用该方法的,当然会失败拉,知道了这点就好办了,我只需要用一个“干
净”的线程,让它绑定到新的Desktop上,再让它用FindWindow()方法找到我要找
的WindowHandle,不就可以了吗,于是,这一步就需要借助一个线程了,线程的代
码如下:
  
  TFindWindowThread = class(TThread)
  private
  FDesktop:THandle;
  FWindowHandle:THandle;
  protected
  procedure Execute();override;
  public
  constructor Create(ACreateSuspended:Boolean;const
ADesktop:THandle);reintroduce;
  property WindowHandle:THandle read FWindowHandle;
  end;
  
  { TFindWindowThread }
  
  procedure TFindWindowThread.Execute();
  var
  I:Integer;
  begin
  //make the current thread find window on the new desktop!
  if not SetThreadDesktop(FDesktop) then begin
  exit;
  end;
  for I:=0 to 60 do begin //wait 30 seconds for open the main window
  FWindowHandle:=FindWindow(nil,PChar('WindowCaption'));
  if FWindowHandle<>0 then begin
  break;
  end;
  Sleep(500);
  end;
  end;
  
  constructor TFindWindowThread.Create(ACreateSuspended:Boolean;const
ADesktop:THandle);
  begin
  inherited Create(ACreateSuspended);
  FDesktop:=ADesktop;
  end;
  
  而主程序中的代码变成这样:
  FindWindowThread:=TFindWindowThread.Create(false,FDesktop);
  try
  FindWindowThread.WaitFor;
  FMainWindowHandle:=FindWindowThread.WindowHandle;
  finally
  FindWindowThread.Free;
  end;
  if FMainWindowHandle=0 then begin
  MessageBox(Application.Handle,'Error when init voice
(6).',PChar(Application.Title),MB_ICONWARNING);
  exit;
  end;
  
  呵呵,成功,这样果然可以顺利的找到窗口Handle了。
  
  4)最后,再用这个主窗口Handle,找出里面的EditBox的Handle,如这样:
  FEditWindow:=FindWindowEx(FMainWindowHandle,0,PChar('Edit'),nil);
  我在这里指定了这个文本框的ClassName,这个名称可以用Spy++得到。
  
  
  初始化的工作就到此结束了,如果顺利,程序就真正在后台被运行了起来。那
么功能调用呢,还是和一般的做法一样:
  
  if (FMainWindowHandle=0) or (FEditWindow=0) then begin
  exit;
  end;
  SendMessage(FEditWindow,WM_SETTEXT,0,LongInt(@AText[1]));
  SendMessage(FMainWindowHandle,WM_COMMAND,$8012,$0);
  其中$8012这个数字,也是用Spy++来得到的资源ID。
  
  最后,别忘了关闭程序,以及释放虚拟Desktop:
  if FProceInfo.hProcess<>0 then begin
  TerminateProcess(FProceInfo.hProcess,0);
  end;
  if FDesktop<>0 then begin
  CloseDesktop(FDesktop);
  end;


Reply all
Reply to author
Forward
0 new messages