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

2016-02-19 19:56 79 1 收藏

今天图老师小编给大家展示的是后台调用外部程序的完美实现(Delphi),精心挑选的内容希望大家多多支持、多多分享,喜欢就赶紧get哦!

【 tulaoshi.com - 编程语言 】

最近在做的一个软件,其中有一部分功能需要调用其它的软件来完成,而那个软件只有可执行文件,根本没有源代码,幸好,我要做的事不难,只需要在我的程序启动后,将那个软件打开,在需要的时候,对其中的一个文本矿设置一些文字,再点击一个按钮就可以了。
  
  说到这里,相信你也有了对该功能的一些初步设想了,没错,其基本思路就是:
  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 WindowHandle0 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 FWindowHandle0 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.hProcess0 then begin
      TerminateProcess(FProceInfo.hProcess,0);
    end;
    if FDesktop0 then begin
      CloseDesktop(FDesktop);
    end;
  

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

  好了,这样就几乎完美的实现了一个后台调用程序的功能,它对最终客户来说将是完全透明的,客户根本感觉不到后台还有另一个程序在工作。是不是很爽啊,这样别人的很多程序我们都可以直接拿来用了(当然了,得在遵守版权的基础上才行拉)。
  

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

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

来源:https://www.tulaoshi.com/n/20160219/1622283.html

延伸阅读
  显式例子:  ?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> unit Main;   interface   uses   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,   Dialogs, StdCtrls, ExtCtrls, Grids, DBGrids, DB, DBTables, D...
for c++builder,用于vc应该把消息框改一下就可以了 void contour(void){ char ln[100];   int make;   char str[100]="";   char *tip="不能执行:"; sprintf(ln,"%s\%s",getcurrentdir(),"watson.exe"); char path[100]; sprintf(path,"%s",getcurrentdir()); c...
标签: Java JAVA基础
其程序如下: class RunThread{ Process t public void run(){ try{ t = java.lang.Runtime.getRuntime().exec("c:\\masm.exe"); }catch(java.io.IOException e ) { System.out.println(e); } } } /***********************************************...
标签: Delphi
  在用Delphi 3.0开发软件时,出现了硬件驱动程序(DLL)中的函数和过程不能正常调用的问题,该硬件由英国Schlumberger公司生产,驱动程序用汇编语言编写的。其《编程指南》给出的Microsoft C的示范程序均能正常运行。但运行此软件时现出的错误提示为: Access violation at address ×××××××× in module…… 经仔细分析...
标签: Delphi
  一、引言 Client/Server是一种分布式的计算模式,与传统的基于主机的结构相比,具有较好的可伸缩性和较优的性价比。过去,Client/Server结构一般分为两层:客户端和服务器端,所有客户端各自实现自己的用户界面和应用逻辑。随着系统的不断扩展,这种两层的Client/Server模式逐渐暴露出它的缺陷,由于最终客户需求的千...

经验教程

92

收藏

81
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部