OLE自动化技术作为Windows应用程序之间互相操作的一种技巧,被操纵的一端称为自动化对象或自动化服务器(Automation Server),而操纵自动化对象的一端称为自动化客户或自动化控制器(Automation Controller)。
Word 97是一个自动化服务器程序,它是依靠微软的COM组件模型来实现的。COM组件模型是一种基于客户/服务器结构的技术规范,使软件部件与应用程序可以进行交互,COM的实现具有与编程语言无关性。大家可以在许多编程开发工具中,如VB、Delphi、VC或BCB等,编写自动化客户程序来控制自动化服务器Word 97。
运行Word 97需占用较大的系统资源,况且其允许多次被加载到内存中运行。为了优化系统运行,应保证在实现的功能时Word 97不会多次加载。
在Windows环境下,每个进程都有一个ID标示号,它包含了不同的使用及工作对象;而这些对象通常有自己的窗体标题,但是他们必须存在一个称为窗体类名的属性。所以可以通过引用Windows API函数,如FindWindow或GetClassName等从系统中寻找是否存在对应于OLE自动化服务器的类名或者窗体标题,来禁止OLE自动化服务器的多次加载到内存。当然首先需要清楚OLE自动化服务器的类名,Excel 97对应的窗体类名为“XLMAIN”,而Word 97对应的窗体类名是“OpusApp”。
然而一般的OLE自动化服务器程序为MDI(Multi Document Interface)结构,当其中的一个子文件被最大化显示时,于是OLE自动化服务器程序的窗体名字将因当前活动的最大化的子文件名而不同,可能会无法确定其唯一性。如中新建的文件文档1被最大化时,Word 97的窗体名是“Microsoft Word - 文档1”;已存盘的文件Mydoc在Word 97打开并最大化显示时Word 97窗体名字变成“Microsoft Word - Mydoc”。
FindWindow函数检索类名和窗口名与指定字符串匹配的顶级窗口的句柄,不搜索子窗口。函数的语法是:HWND FindWindow(
LPCTSTR lpClassName, // 类名指针
LPCTSTR lpWindowName // 窗体标题指针
);
其中参数lpClassName是指向一以NULL结尾的表明类名的字符串指针,lpWindowName是指向窗体名字的指针,如果其是NULL则任何窗体名都将匹配。该函数若成功执行,将返回指定窗体类名或名字的窗体的句柄,否则其返回值为NULL。
使用HWND hPrevApp = ::FindWindow(NULL,”Microsoft Word”)来检索Word是已加载,将会出现窗体名为“Microsoft Word - Mydoc”而检测不到的情况(Word已经启动,打开的文件Mydoc.doc被最大化)。应该使用HWND hPrevApp = :::FindWindow(”OpusApp”, NULL)来保证能正确检测到Word 是否已加载运行。
同样可以调用一系列API函数,如GetWindowThreadProcessId、GetClassName和EnumWindows等将系统所有运行进程的全部类名枚举出来,写入数组中。在根据数组中是否发现所要求OLE自动化服务器的窗体类名决定是否启动Word应用程序。
其中GetClassName函数检索指定窗体的类名,语法是:
int GetClassName( HWND hWnd, // 窗体句柄
LPTSTR lpClassName, // 存放类名的缓冲器地址
int nMaxCount // 以字符计缓冲器的大小
);
函数执行成功,其返回值为复制到指定缓冲器的字符个数,否则值为0。
EnumWindows函数通过将每个窗体的句柄依次传递给应用程序定义的回叫函数来枚举屏幕上所有顶级窗口。只有最后的顶级窗口被枚举出来或者回叫函数返回0时,函数才执行结束。
BOOL EnumWindows(
WNDENUMPROC lpEnumFunc, // 回叫函数指针
LPARAM lParam // 应用定义值,32位传给回叫函数
);
EnumWindows执行成功,返回值不为0,否则为0。并且它对子窗口不加枚举。
在此采用C++ Builders实现Word 97自动化,将以上两种方法作到一个应用里,以检验它们之间的区别和保证Word 97加载运行的效果。EnumWindow方法通过将系统顶级窗口的句柄传递给一回叫函数枚举出它们的相应类名,存为一String数组ID[400],这里假设系统的进程数小于400。从中寻找对应Word 97的类名“OpusApp”,若找到,实现V=GetActiveOleObject(“Word.Application”);否则就启动Word 97。
设在BCB中建立工程Project1.bpr,包含工程Project1.cpp文件和Word.cpp文件。在对应Unit1的Form1上设置三个按钮,Name分别是ClassName、WindowName和EnumWindow。在Word的头文件(.h)的Form1的classdi定义的public 部分定义Variant型变量和针对EnumWindows 方法的函数,写入:“Variant V;Variant __fastcall GetOleObject();”。在Word.cpp文件中实现GetOleObject(),并于三个按钮的Click事件中写入代码。Unit1.cpp文件如下:
#include <vcl.h>
#pragma hdrstop
#include "word.h"
//---------------------------------------------------------------------#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
AnsiString AppName="Word.Application";
String ID[400]; //存放各窗口类名的数组
int Num; //系统进程数
//---------------------------------------------------------------------bool __stdcall EnumProc(/*HWND*/void * hWnd,/*LPARAM*/long/*lp*/)
//定义回叫函数
{
unsigned long* pPid; //LPDWORD
unsigned long result; //DWORD
void *hg; //HGLOBAL
Num=Num+1;
if(hWnd==NULL) return false;
hg = GlobalAlloc(GMEM_SHARE,sizeof(unsigned long));
pPid = (unsigned long *)GlobalLock(hg);
result = GetWindowThreadProcessId(hWnd,pPid); //获得系统进程
if(result){
char className[95];
GetClassName(hWnd,className,95); //获得窗口的类名
ID[Num]=className; //将类名赋给数组
}
else{
GlobalUnlock(hg);
GlobalFree(hg);
return false;
}
GlobalUnlock(hg);
GlobalFree(hg);
return true;
}
//---------------------------------------------------------------
Variant __fastcall TForm1::GetOleObject()
{
Variant Obj;
Num=0;
long lp=0;
EnumWindows((WNDENUMPROC)EnumProc,lp); //枚举系统的顶级窗口的类名
for (int j=0;j<Num+1;j++)
{
if (j<Num)
{
if (ID[j]=="OpusApp") //OpusApp是Word 97对应的类名
{
Obj=GetActiveOleObject("Word.Application");
break;
}
}
else
{
Obj=CreateOleObject("Word.Application"); //启动Word 97
Obj.OlePropertySet("Visible",true);//使Word 97可见
}
}
return Obj;
}
//-------------------------------------------------------------
void __fastcall TForm1::ClassNameClick(TObject *Sender)
{
HWND hPrevApp = ::FindWindow("OpusApp", NULL);
if(!hPrevApp)
V=CreateOleObject(AppName) ;
//Word 没 启 动 就 启 动 它 返 回 一 自 动 化 对 象
else
V=GetActiveOleObject(AppName);
// 否 则 返 回 正 在 运 行 的 实 例 自 动 化 对 象
V.OlePropertySet("Visible",true);
Vdocuments=V.OleFunction("Documents");
Vdocument2=Vdocuments.OleFunction("Add");
//使用Documents 对象的Add方法新建文档
}
//---------------------------------------------------------------------void __fastcall TForm1::WindowNameClick(TObject *Sender)
{
HWND hPrevApp = ::FindWindow(NULL,"Microsoft Word" );
if(!hPrevApp)
V=CreateOleObject(AppName) ;
else
V=GetActiveOleObject(AppName);
V.OlePropertySet("Visible",true);
}
//---------------------------------------------------------------------void __fastcall TForm1::EnumWindowClick(TObject *Sender)
{
V=GetOleObject();
}
以上就C++ Builder中实现OLE 自动化时引用API禁止OLE 自动化服务器多次加载提出了两种方法,并给出相应的例子(CPP文件)及解释。当然可以通过使用这些函数来关闭正在运行的应用程序。
|