其他语言

位置:IT落伍者 >> 其他语言 >> 浏览文章

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


发布日期:2022年05月10日
 
后台调用外部程序的完美实现(Delphi)
其基本思路就是

)调用CreateProcess()打开目标程序

)用FindWindow()找到目标程序的窗口Handle

)找到文本框的Handle以及按钮的MessageID用SendMessage()方法设置文字并触发事件

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

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

)首先建立一个虚拟的Desktop

const

DesktopName = MYDESK;

FDesktop:=CreateDesktop(DesktopNamenilnilGENERIC_ALLnil);

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

sp

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

var

StartInfo:TStartupInfo;

FillChar(StartInfo sizeof(StartInfo) );

StartInfocb:=sizeof(StartInfo);

StartInfolpDesktop:=PChar(DesktopName);//指定Desktop的名称即可

StartInfowShowWindow:=SW_HIDE;

StartInfodwFlags:=STARTF_USESHOWWINDOW;

StartInfohStdError:=;

StartInfohStdInput:=;

StartInfohStdOutput:=;

if not CreateProcess(PChar(FileName)nilnilniltrueCREATE_NEW_CONSOLE+HIGH_PRIORITY_CLASSnilPChar(ExtractFilePath(FilePath))StartInfoFProceInfo) then begin

MessageBox(ApplicationHandleError when init voice ()PChar(ApplicationTitle)MB_ICONWARNING);

exit;

end;

)用FindWindow去找程序的主窗口

开始我直接写下了这样的代码

for I:= to do begin //wait seconds for open the main window

WindowHandle:=FindWindow(nilWindowCaption);

if WindowHandle<> then begin

break;

end;

Sleep();

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 TFindWindowThreadExecute();

var

I:Integer;

begin

//make the current thread find window on the new desktop!

if not SetThreadDesktop(FDesktop) then begin

exit;

end;

for I:= to do begin //wait seconds for open the main window

FWindowHandle:=FindWindow(nilPChar(WindowCaption));

if FWindowHandle<> then begin

break;

end;

Sleep();

end;

end;

constructor TFindWindowThreadCreate(ACreateSuspended:Boolean;const ADesktop:THandle);

begin

inherited Create(ACreateSuspended);

FDesktop:=ADesktop;

end;

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

FindWindowThread:=TFindWindowThreadCreate(falseFDesktop);

try

FindWindowThreadWaitFor;

FMainWindowHandle:=FindWindowThreadWindowHandle;

finally

FindWindowThreadFree;

end;

if FMainWindowHandle= then begin

MessageBox(ApplicationHandleError when init voice ()PChar(ApplicationTitle)MB_ICONWARNING);

exit;

end;

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

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

FEditWindow:=FindWindowEx(FMainWindowHandlePChar(Edit)nil);

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

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

if (FMainWindowHandle=) or (FEditWindow=) then begin

exit;

end;

SendMessage(FEditWindowWM_SETTEXTLongInt(@AText[]));

SendMessage(FMainWindowHandleWM_COMMAND$$);

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

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

if FProceInfohProcess<> then begin

TerminateProcess(FProceInfohProcess);

end;

if FDesktop<> then begin

CloseDesktop(FDesktop);

end;               

上一篇:新手上路:Delphi接口笔记

下一篇:基于Delphi的条码打印系统设计与实现