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

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

说到这里,相信你也有了对该功能的一些初步设想了,没错,其基本思路就是:
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;


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
EmpireCMS从2004年对外发布第一个版本以来,历经四载精耕细作,实现13个版本的飞跃,我们荣幸的宣布,帝国网站管理系统(EmpireCMS)“开源版”发布了! 作为Web内容管理解决方案的提供商,帝国始终致力于通过不断优化产品基础架构、增强系统扩展框架,为站长运营网站提供稳健灵活的内容管理解决方案,实现业务流程与IT技术的完美结合。EmpireCMS5.1开源版在开发上更加注重安全、稳定、灵活、开放这四个方面,以为站长传递、创造价值为己任,打造更加安全稳定的开源PHPWeb内容管理平台。 5.1版开源的同时,还新增了非常实用的功能,而且对系统进行全面的代码检查,保证系统更加安全稳定。主要升级功能如下: 新增 1、新增WAP手机浏览功能。支持多模板管理及每个栏目可以设置使用不同的模板。 2、模板调用新增“灵动标签”支持直接在模板中输写标签模板,且标签模板支持各种程序代码,更灵活。 3、新增循环栏目导航标签,做多栏目导航及网站地图更方便。 4、备份与恢复系统升级至帝国备份王2008版。备份数据更高效更方便。 5、安全性方面更强: (1)、管理员密码采用双重md5模式,防止密码泄漏被********; (2)、登录新增认证码功能,要同时满足密码跟认证码同时正确才能登录,此功能可防止非法用户知道密码也无法登录; (3)、登录次数限制采用COOKIE+数据库双重验证; (4)、登录错误日志记录,使用户第一时间检查非法登录IP,做出及时的非法IP封锁。 (5)、COOKIE认证码验证,为身份认证再加一把锁。 6、登录日志及管理操作日志支持按各种字段升序及降序排序,查询更方便。 7、新增在线支付功能: (1)、支持在线购买点数、存预付款。 (2)、后台可设置支付接口参数及支付点数兑换参数。 (3)、支付记录管理,方便与支付接口商记录对账。 8、模板新增自动识别每次显示条数设置。方便模板制作新手。 9、管理员登录次数及最后登陆时间记录。 10、优化批量生成栏目跟信息页。生成效率更高。 11、新增后台地图导航,可以更快捷的进入相应的管理操作。 12、商城系统模型操作界面改进,并内置在线支付功能,下订单后直接在线支付。 13、会员控制面板首页界面改进,并且必须登陆后才能进入会员中心。更人性化。 14、会员登陆默认转向会员控制面板。 15、注册增加来源验证。防止外部表单提交注册。 16、增加系统模型新增批量全选字段功能。选择模型字段更方便。 17、增加信息时可选择使用所有内容模板,不仅限于当前模型。多个相同字段模型只需做一个内容模板。 18、模板支持int型的时间格式调用。方便调用外部数据的时间格式。 19、验证码改进,增加图片增加干扰字符。防止机器识别图片字符。 修复: 20、修复批量发送邮件编码问题。 21、修复统计信息标签为只统计审核信息数,并改进算法。 22、修复内容模板不支持调用静态评分标签。 23、修复“循环子栏目数据标签”将外部链接也环循进去问题。 24、升级在线模板编辑器,支持xhtml格式页面。 25、UTF-8版截取字符改进,修复截取部分特殊字符会出现/a>问题。
### 回答1: 在Qt编程中,要实现点击按钮调用外部程序,可以使用Qt的QProcess类。 首先,我们需要在Qt工程中创建一个按钮,并在相应的槽函数中编写调用外部程序的代码。 在点击按钮的槽函数中,我们可以创建一个QProcess对象,并通过QProcess的start方法来启动外部程序。 下面是一个简单的示例代码: ```cpp void MainWindow::on_button_clicked() { // 创建QProcess对象 QProcess *process = new QProcess(this); // 设置要调用外部程序路径 QString program = "C:/path/to/external/program"; // 设置外部程序的命令行参数(如果有的话) //QStringList arguments; //arguments << "argument1" << "argument2"; // 启动外部程序 process->start(program); // 或者使用 process->startDetached(program) 来在后台运行程序 // 检查是否启动成功 if (!process->waitForStarted()) { // 启动失败,显示错误信息 QString error = process->errorString(); QMessageBox::critical(this, "Error", "Failed to start external program: " + error); } } ``` 在上述代码中,按钮点击事件的槽函数为`on_button_clicked()`,在这个函数中我们首先创建了一个QProcess对象process,然后设置要调用外部程序的路径和命令行参数(如果有的话),最后调用process的start方法来启动外部程序。启动之后,我们可以通过waitForStarted()函数来等待程序是否成功启动,如果启动失败,可以通过errorString()获取错误信息并进行处理。 这样,当我们点击按钮时,就会调用外部程序。 ### 回答2: 在Qt编程中,要实现点击按钮调用外部程序,可以使用Qt的信号与槽机制。 首先,需要在Qt的工程文件(*.pro文件)中添加一个外部程序的路径,例如: LIBS += -L/path/to/external/program -lexternalProgram 接下来,在代码中创建一个按钮,并连接按钮的"clicked"信号与一个槽函数。 然后,在槽函数中使用QProcess类来执行外部程序。QProcess提供了启动外部程序并与其进行交互的功能。 以下是一个示例代码: ```cpp // 头文件 #include <QPushButton> #include <QProcess> class MyWidget : public QWidget { Q_OBJECT public: MyWidget(QWidget *parent = nullptr) : QWidget(parent) { QPushButton *button = new QPushButton("调用外部程序", this); connect(button, &QPushButton::clicked, this, &MyWidget::startExternalProgram); } private slots: void startExternalProgram() { QProcess *process = new QProcess(this); process->start("/path/to/external/program"); // 启动外部程序,传入路径 process->waitForFinished(); // 等待外部程序执行完毕 } }; ``` 以上代码中,当按钮被点击时,会调用startExternalProgram槽函数。该函数创建一个QProcess对象,并调用其start函数来启动外部程序。waitForFinished函数会阻塞当前线程,直到外部程序执行完毕。 注意,路径"/path/to/external/program"应替换为真实的外部程序的路径。另外,在使用QProcess时,还可以通过信号与槽机制来处理外部程序的输出、错误信息等。 通过上述方法,就可以在Qt编程中实现点击按钮调用外部程序的功能。 ### 回答3: 在Qt编程中,我们可以使用QProcess类来实现点击按钮调用外部程序。QProcess是Qt提供的用于启动外部进程的类。 首先,在Qt Creator中创建一个新的项目,并在主窗口中设计一个按钮(如"调用外部程序"),并关联一个槽函数。 接下来,我们需要在相关的槽函数内部编写代码来实现调用外部程序的功能。具体步骤如下: 1. 在槽函数内部创建一个QProcess对象。 ```cpp QProcess process; ``` 2. 设置要调用外部程序路径。 ```cpp QString programPath = "外部程序路径"; process.setProgram(programPath); ``` 3. 可选:如果需要传递参数给外部程序,可以使用setArguments()函数。 ```cpp QStringList arguments; arguments << "参数1" << "参数2"; process.setArguments(arguments); ``` 4. 启动外部程序。 ```cpp process.start(); ``` 以上就是实现点击按钮调用外部程序的基本步骤。需要注意的是,外部程序的路径需要根据实际情况进行设置,同时还可以根据需要设置参数传递给外部程序。另外,点击按钮之前,需要确保外部程序已经正确安装并设置了正确的路径。 希望以上回答对您有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值