虚拟桌面

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

最近在做的一个软件,其中有一部分功能需要调用其它的软件来完成,而那个软件只有可执行文件,根本没有源代码,幸好,我要做的事不难,只需要在我的程序启动后,将那个软件打开,在需要的时候,对其中的一个文本矿设置一些文字,再点击一个按钮就可以了。

说到这里,相信你也有了对该功能的一些初步设想了,没错,其基本思路就是:
1)调用CreateProcess()打开目标程序。
函数CreateProcess用来创建一个新的进程和它的主线程,这个新进程运行指定的可执行文件。CreateProcess函数用来运行一个新程序。WinExec和LoadModule函数依旧可用,但是它们同样通过调用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方面的东西,而我是在程序的主线程中调用该方法的,当然会失败拉,知道了这点就好办了,我只需要用一个“干净”的线程,让它绑定到新的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;
好了,这样就几乎完美的实现了一个后台调用程序的功能,它对最终客户来说将是完全透明的,客户根本感觉不到后台还有另一个程序在工作。是不是很爽啊,这样别人的很多程序我们都可以直接拿来用了(当然了,得在遵守版权的基础上才行拉)。

代码演示:

 

const
  WM_SECCES = WM_USER + 1;
  WM_ACTION = WM_USER + 2;

type
  TFindWindowThread = class(TThread)
  private
    FDesktop: THandle;
    FDeskdef: THandle;
    FWindowHandle: THandle;
  protected
    procedure Execute(); override;
  public
    constructor Create(ACreateSuspended: Boolean; const ADesktop: THandle;
      Predesk: THandle); reintroduce;
    property WindowHandle: THandle read FWindowHandle;
  end;

type
  TFormMain = class(TForm)
  private
    FDesktop: HDESK;
    FDeskdef: HDESK;
    FProceInfo: TProcessInformation;
    FindWindowThread: TFindWindowThread;
    FMainWindowHandle: THandle;
  public
    procedure MyDesk(var Msg: TMessage); Message WM_SECCES;
    procedure MyACTION(var Msg: TMessage); Message WM_ACTION;
  end;

var
  FormMain: TFormMain;


procedure TFormMain.FormCreate(Sender: TObject);
var
  StartInfo: TStartupInfo;
begin
  FDeskdef := GetThreadDesktop(GetCurrentThreadID);

  // 创建虚拟桌面
  FDesktop := CreateDesktop('MyDesk', nil, nil, 0, GENERIC_ALL, nil);

  // 为运行程序指定打开的桌面
  FillChar(StartInfo, sizeof(StartInfo), 0);
  StartInfo.cb := sizeof(StartInfo);
  StartInfo.lpDesktop := PChar('MyDesk'); // 指定Desktop
  StartInfo.wShowWindow := SW_NORMAL; // SW_HIDE;
  StartInfo.dwFlags := STARTF_USESHOWWINDOW;
  StartInfo.hStdError := 0;
  StartInfo.hStdInput := 0;
  StartInfo.hStdOutput := 0;
  if not CreateProcess(PChar('c:\\windows\\system32\\calc.exe'), nil, nil, nil, true, CREATE_NEW_CONSOLE + HIGH_PRIORITY_CLASS, nil, PChar(ExtractFilePath('c:\\windows\\system32\\calc.exe')), StartInfo, FProceInfo) then
  begin
    exit;
  end
  else
  begin
     CloseHandle(FProceInfo.hThread);
     CloseHandle(FProceInfo.hProcess);
     ShowMessage('新进程的ID号 :' + inttostr(FProceInfo.dwProcessId) + ' 新进程的主线程ID号:' + inttostr(FProceInfo.dwThreadId));
  end;
  PostMessage(Handle, WM_ACTION, 0, 0);
end;

constructor TFindWindowThread.Create(ACreateSuspended: Boolean;
  const ADesktop: THandle; Predesk: THandle);
begin
  inherited Create(ACreateSuspended);
  FDesktop := ADesktop;
  FDeskdef := Predesk;
end;

procedure TFindWindowThread.Execute;
var
  I: Integer;
  destHandle: THandle;
begin
  inherited;
  // 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 (60*500ms)seconds for open the main window
    FWindowHandle := FindWindow(nil, PChar('计算器'));
    if FWindowHandle <> 0 then
    begin
      if not SetThreadDesktop(FDeskdef) then
      begin
        exit;
      end;
      destHandle := FindWindow(nil, PChar('主窗体'));
      if destHandle = 0 then
      begin
        exit;
      end;
      PostMessage(destHandle, WM_SECCES, 0, 0);
      break;
    end;
    Sleep(500);
  end;
end;

procedure TFormMain.FormDestroy(Sender: TObject);
begin
  if FProceInfo.hProcess <> 0 then
  begin
    TerminateProcess(FProceInfo.hProcess, 0);
  end;
  if FDesktop <> 0 then
  begin
    CloseDesktop(FDesktop);
  end;
end;

procedure TFormMain.MyACTION(var Msg: TMessage);
begin
  Sleep(3000);
  FindWindowThread := TFindWindowThread.Create(false, FDesktop, FDeskdef);
  try
    FindWindowThread.WaitFor;
    FMainWindowHandle := FindWindowThread.WindowHandle;
  finally
    FindWindowThread.Free;
  end;
  if FMainWindowHandle = 0 then
  begin
    exit;
  end;
end;

procedure TFormMain.MyDesk(var Msg: TMessage);
begin
  ShowMessage('得到');
end;


 

你可能感兴趣的:(虚拟桌面)