C++的进程间响应技术
吴小岭
在 Windows环境中,一个应用程序按指定方式响应另一个应用程序的解决方法一般是利用 DDE,这需要作出响应的一方提供DDE服务。但是,如果作出响应的一方没有这种服务,我们就没有别的办法了吗?当然有,那就是向应用程序发送消息,应用程序接收到标准的 Windows消息后,就会完成相应的操作。
要向应用程序发送消息,首先要得到它的窗口句柄,这可以通过两个 API函数 GetWindow和 GetWindowText来实现。首先用GetWindow函数遍历所有的 Windows窗口,再用 GetWindowText函数得到每个窗口的标题,如果得到的标题和我们想要查找的窗口的标题一样,就得到我们想要的窗口句柄了。应用程序的窗口是一个树状结构,每一个子窗口下还可能有子窗口,要得到所有子窗口的句柄,就要遍历整个树,为了实现这一点,我们在程序中用了一个简单的堆栈操作。有了具体的窗口句柄后 ,就可以通过发送消息实现我们想实现的操作。本文举了几个简单的例子,实现了一些比较常用的操作,如修改按钮或文本框中的文字,实现从一个程序控制另一个应用程序中的按钮单击等。
图 1是程序运行的主窗体画面:
下面是在 C++ Builder中实现的源程序部分代码:
单元文件 Unit1.cpp
# include <vcl.h>
# pragma hdrstop
# include“ Unit1.h”
# pragma package(smart_init)
# pragma resource“* .dfm”
TForm1* Form1;
__fastcall TForm1::TForm1(TComponent* Owner): TForm(Owner)
{
}
void __fastcall TForm1::btnSearchClick(TObject* Sender)
{
搜索标题和 txtTitle中的文本相符合的窗口,把搜索到的窗口句柄放到 h_Wnd中 ,并把它的所有子窗口的句柄放到数组hWndArray中
char Search[128];
char ClassName[128];
HWND h_Child;
int currPos;
int cmpLen;
currPos=0;
lstClassName- >Clear();
lstControlText- >Clear();
memoReadWrite- >Clear();
h_Wnd=GetWindow(Form1- >Handle,
GW_HWNDFIRST);
遍历所有窗口
while(h_Wnd){
GetWindowText(h_Wnd,Search,128);
cmpLen=txtTitle- >Text.Length();
Search[cmpLen]=0;
if(AnsiString(Search)== txtTitle- >Text){
寻找窗口标题和我们输入到 txtTitle中的内容相符合的窗口
lstClassName- >Items- >Add(AnsiString(Search));
lstControlText- >Items- >Add(AnsiString(Search));
sPush(h_Wnd);
h_Child=GetWindow(h_Wnd,GW_CHILD);
找出所有子窗口 ,把句柄放入数组 hWndArray中
while(1){
hWndArray[currPos]=h_Child;
GetClassName(h_Child,ClassName,128);
SendMessage(h_Child,WM_GETTEXT,128,(LPARAM)Search);
把找到的子窗口的类名和子窗口的序号放到列表框 lstClassName中 ,通过这些数据 ,我们可以确定对这些子窗口可以发送什么消息
lstClassName- >Items- >Add(AnsiString(currPos)+“-- "+ AnsiString(ClassName));
把找到的子窗口的文字信息放到列表框 lstControlText中,通过这些信息 ,我们能较准确地定位子窗口在主窗口的位置
lstControlText- >Items- >Add(AnsiString(currPos)+“-- "+ AnsiString(Search));
currPos++ ;
sPush(h_Child);
h_Child=GetWindow(h_Child,GW_HWNDNEXT);
if(!h_Child){
while (!h_Child&& stack_Top>0){
h_Child=sPop();
h_Child=GetWindow(h_Child,GW_CHILD);
}
if(stack_Top== 0)break;
}
}
break;
}
else
h_Wnd=GetWindow(h_Wnd,GW_HWNDNEXT);
}
}
[/url]下面是几个简单的操作例子 ,向指定的子窗口发送消息 ,完成规定的操作
修改主窗口的标题
void __fastcall TForm1::btnMainClick(TObject* Sender
{
SetWindowText(h_Wnd,txtToValue- >Text.c_str()); }
修改指定子窗口的文本 ,文本框 txtIndex的内容指定子窗体的索引号 ,用文本框 txtToValue中的内容替换子窗口的文本
void __fastcall TForm1::btnChildClick(TObject* Sender)
{
int ctlIndex;
ctlIndex=txtIndex- >Text.ToInt();
SetWindowText(hWndArray[ctlIndex],txtToValue- >Text.c_str());
}
获得类型为文本框的子窗口中的文本 ,将获得的文本放到 memoReadWrite中
void __fastcall TForm1::btnGetClick(TObject* Sender)
{
int ctlIndex;
char theCopy[256];
memoReadWrite- >Clear();
ctlIndex=txtIndex- >Text.ToInt();
SendMessage(hWndArray[ctlIndex],WM_GETTEXT,256,(LPARAM)theCopy);
memoReadWrite- >Lines- >Add(AnsiString(theCopy));
}
void __fastcall TForm1::btnExitClick(TObject* Sender){
Application- >Terminate();
}
[url=file://用]用指定的文本替换类型为文本框的子窗口中的文本
void __fastcall TForm1::btnChangeClick(TObject* Sender)
{
int ctlIndex;
ctlIndex=txtIndex- >Text.ToInt();
SendMessage(hWndArray[ctlIndex],EM_SETSEL,0,1000);
SendMessage(hWndArray[ctlIndex],EM_REPLACESEL,
(WPARAM)true,(LPARAM)(txtToValue- >Text.c_str()));
}
向被操纵的应用程序中类型为按钮的子窗口发送 BM_CLICK,以实现按钮单击动作
void __fastcall TForm1::btnCtlBTNClick(TObject* Sender)
{
int ctlIndex;
ctlIndex=txtIndex- >Text.ToInt();
SendMessage(hWndArray[ctlIndex],BM_CLICK,0,0);
/*其实 ,如果不熟悉 Windows的消息结构 ,简单的
鼠标和键盘消息同样可以实现大部分操作。例如,发送一个鼠标按下消息 ,再发送一个鼠标弹起消息 ,就可以模拟单击消息。
* /
SendMessage(hWndArray[ctlIndex],WM_LBUTTONDOWN,0,0);
SendMessage(hWndArray[ctlIndex],WM_LBUTTONUP,0,0);
}
头文件 Unit1.h
堆栈
HWND AStack[100];
堆栈指针
int stack_Top;
子窗口句柄数组和主窗口句柄
HWND hWndArray[100],h_Wnd;
入栈
void sPush(HWND hWndValue)
{
if(stack_Top<100)
{
AStack[stack_Top]=hWndValue;
stack_Top++ ;
}
else
{
MessageBox(NULL,“ Stack Overflow” ,“ Stack Error” ,0);
}
}
弹出
HWND sPop()
{
if(stack_Top >0)
{
stack_Top-- ;
return AStack[stack_Top];
}
else
{
MessageBox(NULL,“ Stack Empty” ,“ Stack Error” ,0);
}
}
# ifndef Unit1H
# define Unit1H
# include <Classes.hpp>
# include <Controls.hpp>
# include <StdCtrls.hpp>
# include <Forms.hpp>
# include <ExtCtrls.hpp>
# include <Graphics.hpp>
class TForm1 : public TForm
{
__published: // IDE- managed Components
TListBox* lstClassName;
TListBox* lstControlText;
TMemo* memoReadWrite;
TEdit* txtTitle;
TEdit* txtIndex;
TEdit* txtToValue;
TButton* btnSearch;
TButton* btnMain;
TButton* btnChild;
TButton* btnGet;
TButton* btnChange;
TButton* btnExit;
TLabel* Label1;
TLabel* Label2;
TLabel* Label3;
TButton* btnCtlBTN;
void__fastcall btnSearchClick(TObject* Sender);
void__fastcall btnMainClick(TObject* Sender);
void__fastcall btnChildClick(TObject* Sender);
void__fastcall btnGetClick(TObject* Sender);
void__fastcall btnExitClick(TObject* Sender);
void__fastcall btnChangeClick(TObject* Sender);
void__fastcall btnCtlBTNClick(TObject* Sender);
private:
public:
__fastcall TForm1(TComponent* Owner);
};
extern PACKAGE TForm1* Form1;
# endif
通过向作出响应的一方窗口发送 Windows消息,我们可以完成各种复杂的操作。在上面的例子中,“单击按钮”只是让响应窗体完成了按钮单击的动作,如果我们想一想办法,也许可以把按钮对应的窗口函数替换掉,有兴趣的读者可以自己研究一下。
另外,我们可以用此技术做一个针对某一应用程序的 ActiveX控件,在控制程序中加入这个控件,再加入微软的脚本控件 (MSSCRIPT.OCX),通过在外部编写脚本,就可以实现控制的自动化和程序化。