程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> 後台調用外部程序的完美實現(Delphi)

後台調用外部程序的完美實現(Delphi)

編輯: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中可以建立多個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;
  

  好了,這樣就幾乎完美的實現了一個後台調用程序的功能,它對最終客戶來說將是完全透明的,客戶根本感覺不到後台還有另一個程序在工作。是不是很爽啊,這樣別人的很多程序我們都可以直接拿來用了(當然了,得在遵守版權的基礎上才行拉)。
  

  有任何改進意見,或交流,可以Mail至:tonyki[at]citiz.net
  

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved