选拔Delphi运维和破产外界应用程序,使用CreateD

2019-11-11 19:29 来源:未知

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

 

说到这里,相信你也有了对该功能的一些初步设想了,没错,其基本思路就是:
1)调用CreateProcess()打开目标程序。
2)用FindWindow()找到目标程序的窗口Handle。
3)找到文本框的Handle,以及按钮的MessageID,用SendMessage()方法设置文字,并触发事件。

 

好了,这样确实很简单吧,但是当我实现它后,却发现这样做的结果则是:当我的程序启动并打开目标程序时,它的Splash窗口,以及主窗口都将显示出来,即使当我用FindWindow()找到主窗口Handle后,调用SendMessage(WindowHandle, SW_HIDE)来隐藏该窗口,还是会有一瞬主窗口被显示出来的,这样的效果实在是最求完美的我不忍心看到的。

Delphi与Windows平台紧密结合,编译代码快速高效。作为一种可视化的面向对象开发工具,Delphi可以帮助程序员更轻松、更快速地编写各种Windows应用程序。而且通过编程可以方便地调用其它语言编写的动态库或应用程序,并在不需要时关闭这些外部程序。这一点对许多编程人员非常有用,例如,在采用Delphi进行软件开发的同时,可能需要调用以前采用其它工具开发的应用程序,以免再次重新编写代码,或者需要调用Windows中的记事本和计算器等工具,以便在程序运行过程中记录信息或进行计算等。

那么怎么解决这个问题呢,首先我当然在CreateProcess()上面寻找方法,可惜,它只有一个参数可以设置窗口的默认显示方式,但是一旦这个窗口自己重设了显示方式,它就没有任何作用了。。。。继续查找文档,这时我看到CreateProcess()的一个参数TStartupInfo中有 lpDesktop这么一个属性,按照MSDN的说法,如果该指针为NULL,那么新建的Process将在当前Desktop上启动,而如果对其赋了一个Desktop的名称后,Process将在指定的Desktop上启动,恩,看来不错,就从它入手了:

  在Delphi中,通过调用一组API函数是可以比较轻松地完成上述要求的。下面本文将以一个实际的例子详细地介绍一下实现外部应用程序启动和关闭的具体方法和步骤:

1)首先,建立一个虚拟的Desktop,

  1. 创建一个应用程序,在应用程序的窗体上添加两个Tbutton组件和一个TopenDialog组件。设置其中一个Tbutton组件的Caption属性为“启动外部应用程序”、另一个Tbutton组件的Caption属性为“关闭已开启的外部应用程序”。设置TopenDialog组件的Filter属性为“可执行文件(*.exe)|*.exe”。

Delphi代码  图片 1

  2. 实现外部应用程序的启动功能

  1. const  
  2.   DesktopName = 'MYDESK';  
  3.   
  4. FDesktop:=CreateDesktop(DesktopName,nil,nil,0,GENERIC_ALL,nil);  

  ⑴ 要启动外部应用程序,可以通过调用API函数WinExec来实现。该函数用于运行指定的应用程序。下面介绍一下该函数所需的参数和返回值:

 
Windows中可以建立多个Desktop,可以使用SwitchDesktop()来切换哪个Desktop被显示出来,以前有过将Windows模拟成Linux的形式,可以在多个虚拟Desktop中切换的程序,其实那种程序也是用的Windows本身的虚拟Desktop功能来实现的,另外 Windows的启动画面,以及屏保画面也都是用虚拟Desktop实现的,好了,关于这方面不多介绍了,感兴趣的话,可以到MSDN中查看更详细资料:

UINT WinExec(

2)在CreateProcess的时候,指定程序在我新生成的Desktop上运行:

 LPCSTR lpCmdLine, file://命令行指针

Delphi代码  图片 2

 UINT uCmdShow file://应用程序的窗口风格

  1. var  
  2.   StartInfo:TStartupInfo;  
  3.   
  4.   FillChar(StartInfo, sizeof(StartInfo), 0);  
  5.   StartInfo.cb:=sizeof(StartInfo);  
  6.   StartInfo.lpDesktop:=PChar(DesktopName);      //指定Desktop的名称即可  
  7.   StartInfo.wShowWindow:=SW_HIDE;  
  8.   StartInfo.dwFlags:=STARTF_USESHOWWINDOW;  
  9.   StartInfo.hStdError:=0;  
  10.   StartInfo.hStdInput:=0;  
  11.   StartInfo.hStdOutput:=0;  
  12.   if not CreateProcess(PChar(FileName),nil,nil,nil,true,CREATE_NEW_CONSOLE+HIGH_PRIORITY_CLASS,nil,PChar(ExtractFilePath(FilePath)),StartInfo,FProceInfo) then begin  
  13.     MessageBox(Application.Handle,'Error when init voice (5).',PChar(Application.Title),MB_ICONWARNING);  
  14.     exit;  
  15.   end;  

);

 

  如果成功,返回值大于31。否则可能返回下列结果:

3)用FindWindow去找程序的主窗口
开始我直接写下了这样的代码:

  0 系统内存或资源不足

Delphi代码  图片 3

  ERROR_BAD_FORMAT 该*.EXE文件无效

  1. for I:=0 to 60 do begin //wait 30 seconds for open the main window  
  2.   WindowHandle:=FindWindow(nil,'WindowCaption');  
  3.   if WindowHandle<>0 then begin  
  4.     break;  
  5.   end;  
  6.   Sleep(500);  
  7. end;  

  ERROR_FILE_NOT_FOUND 没找到指定的文件

 
但是,实践证明,这样是找不到不在当前Desktop中的Window的,那怎么办呢:
答案是,可以用SetThreadDesktop()函数,这个函数可以设置当前Thread工作所在的Desktop,于是我在以上代码前又加了一句:

  ERROR_PATH_NOT_FOUND 没找到指定路径

Delphi代码  图片 4

  ⑵ 通过编写标题为“启动外部应用程序”组件的OnClick事件,来实现外部应用程序的启动,代码如下:

  1. if not SetThreadDesktop(FDesktop) then begin  
  2.   exit;  
  3. end;  

procedure TForm1.Button1Click(Sender: TObject);

 
但是,程序运行后,该函数却返回了false,说明方法调用失败了,再仔细看MSDN,发现有这么一句话:

var

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).

str: string; file://存储指定的应用程序文件名

哦,原来需要切换Desktop的线程中不能有任何UI方面的东西,而我是在程序的主线程中调用该方法的,当然会失败拉,知道了这点就好办了,我只需要用一个“干净”的线程,让它绑定到新的Desktop上,再让它用FindWindow()方法找到我要找的WindowHandle,不就可以了吗,于是,这一步就需要借助一个线程了,线程的代码如下:

begin

Delphi代码  图片 5

if opendialog1.Execute then file://选择要调用的外部可执行程序

  1.   TFindWindowThread = class(TThread)  
  2.   private  
  3.     FDesktop:THandle;  
  4.     FWindowHandle:THandle;  
  5.   protected  
  6.     procedure Execute();override;  
  7.   public  
  8.     constructor Create(ACreateSuspended:Boolean;const ADesktop:THandle);reintroduce;  
  9.     property WindowHandle:THandle read FWindowHandle;  
  10.   end;  
  11.   
  12.   
  13. { TFindWindowThread }  
  14.   
  15. procedure TFindWindowThread.Execute();  
  16. var  
  17.   I:Integer;  
  18. begin  
  19.   //make the current thread find window on the new desktop!  
  20.   if not SetThreadDesktop(FDesktop) then begin  
  21.     exit;  
  22.   end;  
  23.   for I:=0 to 60 do begin //wait 30 seconds for open the main window  
  24.     FWindowHandle:=FindWindow(nil,PChar('WindowCaption'));  
  25.     if FWindowHandle<>0 then begin  
  26.       break;  
  27.     end;  
  28.     Sleep(500);  
  29.   end;  
  30. end;  
  31.   
  32. constructor TFindWindowThread.Create(ACreateSuspended:Boolean;const ADesktop:THandle);  
  33. begin  
  34.   inherited Create(ACreateSuspended);  
  35.   FDesktop:=ADesktop;  
  36. end;  

begin

 

str := opendialog1.FileName; file://获取可执行文件名

而主程序中的代码变成这样:

winexec(PChar(str), SW_SHOWNORMAL); file://启动指定的可执行程序

Delphi代码  图片 6

end;

  1. FindWindowThread:=TFindWindowThread.Create(false,FDesktop);  
  2. try  
  3.   FindWindowThread.WaitFor;  
  4.   FMainWindowHandle:=FindWindowThread.WindowHandle;  
  5. finally  
  6.   FindWindowThread.Free;  
  7. end;  
  8. if FMainWindowHandle=0 then begin  
  9.   MessageBox(Application.Handle,'Error when init voice (6).',PChar(Application.Title),MB_ICONWARNING);  
  10.   exit;  
  11. end;  

end;

 

  3. 关闭已开启的外部应用程序

呵呵,成功,这样果然可以顺利的找到窗口Handle了。

  ⑴ 通过调用两个API函数,可以实现该功能。这两个函数分别为:

4)最后,再用这个主窗口Handle,找出里面的EditBox的Handle,如这样:  

  ① FindWindow函数 该函数用于查找与指定的类名和窗口名相匹配的高层窗口,如果查找成功,返回非0值,否则返回0。

Delphi代码  图片 7

  ② SendMessage函数 此函数向一个或多个窗口发送指定的消息。在此通过发送WM_CLOSE消息来关闭指定的外部应用程序。

  1. FEditWindow:=FindWindowEx(FMainWindowHandle,0,PChar('Edit'),nil);  

  ⑵ 通过编写标题为“关闭已开启的外部应用程序”组件的OnClick事件,来关闭已开启的外部应用程序。代码如下:

 我在这里指定了这个文本框的ClassName,这个名称可以用Spy++得到。

procedure TForm1.Button2Click(Sender: TObject);

初始化的工作就到此结束了,如果顺利,程序就真正在后台被运行了起来。那么功能调用呢,还是和一般的做法一样:

var

Delphi代码  图片 8

hWndClose: HWnd; file://存储指定的外部应用程序窗口句柄

  1. if (FMainWindowHandle=0) or (FEditWindow=0) then begin  
  2.   exit;  
  3. end;  
  4. SendMessage(FEditWindow,WM_SETTEXT,0,LongInt(@AText[1]));  
  5. SendMessage(FMainWindowHandle,WM_COMMAND,$8012,$0);  

str: String; file://存储指定的外部应用程序的窗口名

 
其中$8012这个数字,也是用Spy++来得到的资源ID。

begin

最后,别忘了关闭程序,以及释放虚拟Desktop:

str := InputBox(提示,请输入应用程序名:,); file://获取要关闭的应用程序窗口名

Delphi代码  图片 9

if str <> then begin

  1. if FProceInfo.hProcess<>0 then begin  
  2.   TerminateProcess(FProceInfo.hProcess,0);  
  3. end;  
  4. if FDesktop<>0 then begin  
  5.   CloseDesktop(FDesktop);  
  6. end;  

file://根据窗口名查找要关闭的窗口句柄

 

hWndClose := FindWindow(nil, PChar(str));

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

if hWndClose <> 0 then file://如果查找成功,则发送消息,关闭指定的窗口

SendMessage(hWndClose,WM_CLOSE,0,0);

else file://否则,给出提示信息

ShowMessage(没找到指定的应用程序,所以无法关闭!);

end;

end;

 

TAG标签:
版权声明:本文由990888藏宝阁发布于计算机网络,转载请注明出处:选拔Delphi运维和破产外界应用程序,使用CreateD