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

美甲师木宇夜

美甲师木宇夜

2016-02-19 19:56

今天图老师小编给大家展示的是后台调用外部程序的完美实现(Delphi),精心挑选的内容希望大家多多支持、多多分享,喜欢就赶紧get哦!
最近在做的一个软件,其中有一部分功能需要调用其它的软件来完成,而那个软件只有可执行文件,根本没有源代码,幸好,我要做的事不难,只需要在我的程序启动后,将那个软件打开,在需要的时候,对其中的一个文本矿设置一些文字,再点击一个按钮就可以了。
  
  说到这里,相信你也有了对该功能的一些初步设想了,没错,其基本思路就是:
  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;
  

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

  { 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;
  

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

  而主程序中的代码变成这样:
    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;
  

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

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

展开更多 50%)
分享

猜你喜欢

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

编程语言 网络编程
后台调用外部程序的完美实现(Delphi)

再谈后台调用外部程序的完美实现

编程语言 网络编程
再谈后台调用外部程序的完美实现

s8lol主宰符文怎么配

英雄联盟 网络游戏
s8lol主宰符文怎么配

用java实现外部调用exe文件

编程语言 网络编程
用java实现外部调用exe文件

Delphi多层应用程序的实现

Delphi
Delphi多层应用程序的实现

lol偷钱流符文搭配推荐

英雄联盟 网络游戏
lol偷钱流符文搭配推荐

delphi的调用约定

编程语言 网络编程
delphi的调用约定

VC实现系统热键激活后台服务程序

编程语言 网络编程
VC实现系统热键激活后台服务程序

lolAD刺客新符文搭配推荐

英雄联盟
lolAD刺客新符文搭配推荐

在Delphi中通过函数获取GUID

在Delphi中通过函数获取GUID

把Access数据库移植到SQL SERVER7.0

把Access数据库移植到SQL SERVER7.0
下拉加载更多内容 ↓