COM自动化,第一部分

在这部分和以后的内容中,我们将深入COM 自动化世界。希望用一个简要的专栏来研究这个题目。我们将谈论怎样进行自动化(IDispatch)调用和处理自动化对象需要做什么。然后,我们将讨论用于自动化的特殊COM数据类型和研究双重接口。

自动化(从前叫做OLE自动化)是一个和迄今为止我们曾认为标准COM vtable接口完全不同的客户调用服务器方法。

自动化是使用标准COM接口IDispatch来存取对象的自动化接口。因此,我们说任何实现IDispatch的对象实现了自动化。

为什么要自动化?

最初开发自动化是作为一种应用程序(例如Word和Excel)用以把其功能显露给其他应用,包括脚本语言的方式。目的是提供一种简单方式来访问属性和调用方法,这种方式尽可能少的占用自动化客户的资源,并且不需要被访问对象的类型信息就可以进行调用的方法。

在C++头文件中描绘接口的类型信息决不是浪费时间,描绘方法的vtable偏移量也很重要,最困难的是,设置正确的C++堆栈框架以便正确的执行方法调用。对一个基于文本的解释语言所有这些尤其需要技巧。

如果每个脚本语言都不得不做这个机灵的程序,那么很少有能存取COM对象的了。使用自动化,对象就可以提供一个简单的自动化接口,这样脚本语言作者只需掌握IDispatch和几个COM应用程序接口就可以了。

Visual Basic的第一个32位版使用自动化存取OLE控件(现在叫ActiveX控件),他代替了16位的Visual Basic的VBX控件。Visual Basic仍然可以使用自动化存取一个控件的属性和方法,但是更近的版本也支持使用标准COM vtable接口。这次我们创建的例子将使用自动化接口。

脚本语言,例如Visual Basic for Applications、VBScript和J/Script,以独占模式使用自动化。所以如果你想要你的对象可以被脚本语言使用,你必须实现一个自动化接口。

对象和属性和方法,噢,我的上帝!
世界上关于自动化有三个主要概念。对象是最重要的概念。对象显露属性和方法。
图1. 自动化对象的属性和方法
把这个与更复杂的世界的COM观点对比,在这种观点中,是接口,而不是对象,是第一位的,而属性是不存在的,并且每个对象能有多个包含多个方法的接口。
图2. COM对象,接口,方法(包括没有标签的IUnknown)

方法与C++成员函数相似,而自动化的属性则与C++数据成员和实例数据(也叫属性)相似。注意接口没有独立的概念,每个对象有一个自动化接口。进一步注意到COM接口没有属性的概念,它们只有方法。(但是我们可以使用get/set方法对模拟属性。)

自动化对象怎样被创建?

创建一个自动化对象是一个简单的操作。这儿我将使用Visual Basic作为例子,但是在任何兼容自动化的语言中,方法基本上一样。

在Visual Basic,你应先创建一个对象变量:
Dim Beeper as Object
……接着设置它指向一个特殊的对象:
Set Beeper = CreateObject("BeepCntMod.BeepCnt")

在这个例子中我们创建了一个BeepCnt对象

我们可以接着调用对象上的方法控制它的属性,就像我们不久将看到的。

但是首先,让我们讨论Visual Basic(或者任何自动化客户程序)在幕后真正做什么。

我们早已知道我们将通过IDispatch COM接口访问自动化对象。所以DIM语句只显示集合至少需要的内存,因此Visual Basic能为我们即将创建的对象访问IDispatch指针。

CreateObject调用需要有一点技巧。首先,GUID在哪里?对象的CLSID没有GUID我们怎样创建它?

你可以重新调用,这样我们可以通过对象的ProgID引用对象类型。你也可以重新调用我们在注册表用ProgID作为键名注册的一个键。该键用一个CLSID作为子键。

COM提供一个叫CLSIDFromProgID的函数,它根据给出的ProgID查找CLSID。Visual Basic用我们传送到CreateObject的字符串调用这个函数。在这个例子中,Visual Basic将传送"BeepCntMod.BeepCnt"。CLSIDFromProgID查阅那个键和返回与它相关的CLSID。(顺便说一句,ProgID的第一部分是模块或应用程序名,第二部分是模块或应用程序中的对象名。)

在这一点上Visual Basic调用我们的老朋友CoCreateInstanceEx,传送CLSID和请求IDispatch接口。如果CoCreateInstanceEx成功,VB创建一个包含由CoCreateInstanceEx收到的IDispatch指针的对象变量,并且把它分配给我们的对象变量。

如果因任何原因创建失败:对象不存在,或者它没实现IDispatch,则对CreateObject的调用失败。

就像你所看到的,Visual Basic(或任何自动化客户)的开销是最小的,所有必须知道的是用两个简单的COM函数创建对象。

那么你怎样访问自动化属性和方法?

访问我们的对象的Visual Basic源代码可能像下面这样:
BC = Beeper.Count
Beeper.Count = 5
Beeper.Beep

这三个语句分别访问一个属性、设置一个属性和调用一个方法,都只使用了两种IDispatch方法:GetIDsOfNames和Invoke。IDispatch::GetIDsOfNames获得与方法或属性的文本名有关的整型ID。Visual Basic调用它发现"Beep(嘟嘟响)"对应ID 1和"Count(计数)"对应ID 2。当我们调用IDispatch::Invoke时,我们需要这些叫做dispids的ID。

所有现行的自动化属性和方法访问都是通过调用IDispatch::Invoke实现。换句话说,你的自动化客户要访问自动化对象所必须知道是几个简单的COM调用。如果你的执行语言不是C或C++,你可以为你的运行时间编写帮助者来做那些调用,所以从任何程序使用自动化是简单的。

也许很简单,但并不是不重要:IDispatch::Invoke接收一批参数,所有的参数必须被正确设置。最重要的是:

  • 一个叫dispid的整型ID,它指定要被访问的属性和方法(我们通过调用包含属性或方法名的字符串的GetIDsOfNames获得它)。
  • 一个包含一列参数指针的结构。(每个参数被存储到包含一个典型标记和一个叫variant的共用体的结构中。)
  • 一个包含指向属性(设置它、获得它、用一个引用设置它)或者方法(调用它)数列的指针的结构。
  • 一个作为属性获取的或者是方法调用返回的返回值参数,也是一个变量。

噢,万一你想本地化方法、属性、名字化参数名或参数值,Invoke和GetIDsOfNames都接受一个本地ID。

Invoke也有几个其他参数可以把错误信息传递给自动化客户。在这里我们将假定我们处在一个完美的世界,暂时不考虑它们。

变量(Variant)以16个字节存储。前两个字节是一个标记,包含一个代表变量类型的数,其次的六个字节填满,最后的八个字节是变量的值。值的格式取决于标记的值。在C/C++中,我们使用一个共用体表示变量的值。变量可以拥有大多数C++数据类型,包括指针、数组、串、日期和当前对象。下一次我们将对包括变量COM数据类型做完整的处理。

那么为什么它比常规接口容易?

注意不一定是必须通过IDispatch::Invoke进行一次调用:

  • 没有偏移量--我们使用一个dispid,我们通过请求对象本身获得它。
  • 没有C/C++参数列表和调用约定--我们使用变量的数组。
  • 不用C/C++头文件告诉你上面的东西--但是典型库是可选择的。

你不能完全使用C/C++来进行分发。很显然,这四个调用需要使用C/C++调用约定完成。但这是你作为一个自动化客户唯一需要担心的地方。

那么,所有需要为调用做的是一个对象的IDispatch指针,你想访问的属性、方法名和参数的列表。

出于同样的原因,如果你想用脚本语言中编写COM对象,只有用IDispatch(噢,一种创建对象的方式)代替试图处理常规接口的无数变量的无数细节,这会使你的语言运行时间实现更方便。

COM接口和自动化间的不同

从前面的描述,你可以直接的看到自动化与COM接口间不同的几个方式:

  • 自动化接口不必是永恒的,虽然在运行的时候你不必改变它们,因为客户能缓存dispids。但是自动化接口的改变是平常的,尤其是从对象的一个版本到另一个版本时添加方法。(如果你删除方法或改变参数,你可能破坏现有的客户代码。)
  • 自动化方法(和属性)可以获取包括不同类型的可变长度参数列表。在运行时解析参数和执行任何必需的类型转变是IDispatch::Invoke的事。(如果不能实现参数的转换,对象的IDispatch::Invoke实现将返回一个错误,HRESULT。)
  • 自动化方法和属性的访问是最后限制的;换句话说,确定要被访问的方法/属性被推迟到调用时才进行。
  • 因为所有这个后期连接,自动化方法和属性是多形态的,在某种意义上更像Smalltalk而非C++;你可以存取任何对象上任何方法或属性,拒绝有害的调用是对象的责任。例如,你可以在一个任意对象上调用一个Print方法。任何对象调用Print方法大概都会打印它的值;没有任何对象会发生调用失败。在C++中,你只能在那些定义了Print方法的类的对象上调用Print:在编译时检查名字和参数,而不是运行时。


那么看一些例子怎么样?

作为一个快速例子,让我们着眼于刚展示的三个调用怎样使用。

调用一个方法

因为没有参数传递到Beep方法,也没有从Beep方法或返回值,让我们先调用它。记住调用被写作:
Beeper.Beep

首先你需要知道一件事:参数指针传递到Invoke实际上是一个指向一个DISPPARAMS结构的指针,就像下面定义的:
typedef struct FARSTRUCT tagDISPPARAMS{ // Pointer to array of arguments, named and unnamed VARIANTARG FAR* rgvarg; // Array of Dispatch IDs of named arguments DISPID FAR* rgdispidNamedArgs; // Total number of arguments, named and unnamed unsigned int cArgs; // Number of named arguments. unsigned int cNamedArgs; } DISPPARAMS; 我们将不会使用名字化的参数,所以rgdispidNamedArgs将为空,而cNamedArgs将为零。 进行这个简单调用的代码为: DISPID dispid; OLECHAR * szMember = "Beep"; // No parameters, so no array DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0}; // pdisp is an IDispatch pointer // to the Beeper object hresult = pdisp->GetIDsOfNames(IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid); hresult = pdisp->Invoke( dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispparamsNoArgs, NULL, NULL, NULL); 首先,我们通过调用GetIDsOfNames获得dispid。在两次调用中,IID_NULL值是保留参数的值。我们传递一个指向字符指针的数组的指针(在这个例子中,只有一个字符指针)和数组中指针的数目、地区(万一我们想要本地化名字)和dispid数组的一个指针(同样,这儿只有一个指针)。当调用返回时,dispid将包含与"Beep"对应的dispid。 如果我们提供一种方式记住"Beep"的dispid,我们不用多次调用GetIDsOfNames。 一旦我们有dispid,我们可以调用Invoke。 你将注意即使参数列表是空的,我们仍不得不提供一个最小的DISPPARAMS结构。也会注意我们不得不说明我们想通过DISPATCH_METHOD执行方法调用。 你也将注意到这比只调用Beep( )更加复杂的多。自动化对非C/C++客户来说容易使用,但是他总比直接调用来得慢。然而,如果服务器在另一个进程中或另一个机器上,设置(和执行)自动化调用的额外时间变的可以忽略。 获取一个属性的值 获取一个属性的值和调用一个方法很相似,唯一的不同是当调用返回时我们将注意返回值。 Visual Basic中获得一个属性的值的代码写为: BC = Beeper.Count 调用Invoke的C++代码是: VARIANT varResult; // No parameters, so no array DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0}; // dispid set by call to GetIDsOfNames // (omitted for brevity) hresult = pdisp->Invoke( dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &dispparamsNoArgs, &varResult, NULL, NULL); // Property's value stored in varResult 通过传递参数获取参数化属性(或者"属性数组")很容易,就象当获取(和设置)属性时用到的一个索引或查找关键字。 注意Visual Basic语法不能区分获取属性和调用没有参数但有返回值的方法之间的区别。换句话说,不可能从语法上区别BC = Beeper.Count是存储一个叫Count的属性或是调用一个叫Count的方法。 作为一个结果,一些自动化客户将为一个属性访问传送两个标记,在一个属性获取的情况下,可能传送DISPATCH_PROPERTYGET | DISPATCH_METHOD因为它不能区别是否它在做一个属性存取或一个方法调用。自动化对象不得不通过根据dispid是指向一个属性或是一个方法来正确运行。 设置一个属性的值 设置一个属性与获取一个属性有三种区别:

  • 属性的新值有一个参数。
  • 使用dispid DISPID_PROPERTYPUT为参数命名。
  • 返回的值参可以忽略。

设置一个属性与调用一个方法的不同之处在于属性设置的参数使用特殊的dispid命名。

为什么我们不得不进行这么详细的讨论?记住属性可以被参数化的。属性的值有一个特殊的名字使自动化对象容易认出哪个参数是属性的值。(好大夫真正想避免使用命名的参数,但是这是我们绝对需要的例子)

回想在Beeper对象上设置Count属性的VB代码是:
Beeper.Count = 5

调用的C++代码是:

// parameter structure DISPPARAMS dispparams; // one-element array of parameter names DISPID mydispid[1] = { DISP_PROPERTYPUT }; // one-element array of parameters VARIANTARG vararg[1]; dispparams.rgvarg = vararg; // 1-element array VariantInit(&rgvarg[0]); dispparams.rgvarg[0].vt = VT_I4; // 32-bit integer dispparams.rgvarg[0].iVal = 5; // here's our 5! dispparams.rgdispidNamedArgs = mydispid; // name array dispparams.cArgs = 1; // total args dispparams.cNamedArgs = 1; // named args // dispid set by call to GetIDsOfNames // (omitted for brevity) hresult = pdisp->Invoke( dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, &dispparams, NULL, NULL, NULL);



所有这些代码是一个小调用!

对于实现自动化调用需要多少代码和为什么自动化可能很缓慢,你可能开始有一个概念了。如果有更多的参数,这三行设置每个变量:
VariantInit(&rgvarg[0]);
dispparams.rgvarg[0].vt = VT_I4; // 32-bit integer
dispparams.rgvarg[0].iVal = 5; // here's our 5!

……每个参数会被重复--一个语句初始化变量、一个语句设置它的类型、一个语句设定它的值。如果参数需被命名,在rgdispidNamedArgs中有一个附加的语句设置每个参数名。并且参数的大小应被正确设置。

所以自动化是用代码的尺寸作为代价实现了灵活调用。并且它还放弃一件事情:你唯一能传递的参数是那些能在变量(variant)中被描述的参数。而最大的损失是你不能使用变量描述结构。

我们不久将讨论变量。如果你想知道更多关于自动化参数传递的信息,查找你最喜欢的COM参考书。

准备写你自己的IDispatch::Invoke调用?阅读本部分……

除非你正在编写你自己的脚本语言或直接调用IDispatch::Invoke,否则你不会遇到我们将要讨论的问题,对于一个C++客户来讲是一个纯自动化对象。

在阅读关于可选择的自变量时,会发现一个令人迷惑的情形:现在对IDispatch::Invoke的COM文档中,你不得不为每个遗漏的没命名的变量传送带有VT_ERROR标记的变量。如果你跳过一个没命名的自变量,就像在object.method(a,,c)中,是明显正确的。但是如果可选择的自变量是在最后呢?脚本语言能否根本不使用类型库就知道要传递多少哑元自变量?答案:不能。

如果你最后想省略没命名的可选择的自变量,你可以省略它们。使用实现了IDispatch的COM对象将会通过向在IDL中指定的双重接口函数提供自变量来正确处理这种情况。但是有一个gotcha:如果你在调用的对象本身实现了IDispatch::Invoke本身,而不依赖COM,它可以被写入,所以它假设所有可选择的和缺省值的自变量为哑元自变量,--所以如果你能获取类型库信息,你应该可以确定参数的数目,,因此你能提供哑元自变量。

自动化:服务器端

关于怎样调用方法、设置和获取属性、存取返回值和传送参数,我们现在看到的比我们曾经想知道的还要多。自动化对象怎样处理所有这些问题?

简而言之,对象通过实现IDispatch::Invoke(当然还有IDispatch其他部分,包括GetIDsOfNames)处理所有这些问题。但是让我们假定你正在实现这个对象。你怎样实现这些方法?

实现IDispatch困难的方式

如果你的方法没有任何参数(或者可能只有一个参数)和没有参数化的属性,你自己实现IDispatch::Invoke就非常简单。全部需要做的是一个开关语句,它为每个dispid和类型的组合调用正确的函数。(你也可以使用一个调用表。)你将不得不把参数转换为你需要的类型,但是调用VariantChangeType可以很容易的从任何类型转换变量到你需要的任何类型。(如果转换失败,就返回一个错误给调用者。)

但是可以想象如果你有多个参数的混乱状况。首先,未命名的参数在数组中是颠倒次序的。如果他们被命名,顺序由数组的dispids决定,意味着你将不得不把它挑选出。但是等一下--那不是全部--你可能需要支持可选择的参数和带有缺省值可选择的参数。给出所有的这些的描绘是一巨大的混乱。它不仅仅困难--你很可能在做它时产生错误。如果你去做,在你能处理的调用上你将多半以对一个你本来能处理的调用给出一个错误而结束---这不是在你的用户当中鼓舞信心的一种伟大的方式。

有比较容易的方式吗?你可以打赌一定有。

不要用困难方式做它。让COM帮你做它!


如果你的服务器遇到两个相对简单的要求,你能利用COM的内建的IDispatch的实现。要求是:
· 你的自动控制接口必须是一个双重接口(作为ATL产生的),不是一个纯粹的dispinterface。 
· 你必须产生和利用一个类型库告诉COM是哪些方法和属性。

ATL甚至不支持引入的纯disp接口,所以你的选择是双重的(包括自动控制)和自定义接口。一个双重接口和等价的常规接口之间主要的差别是双重接口派生自IDispatch而不是IUnknown。这意味着除了QueryInterface、Release和AddRef外,双重接口也必须实现所有的IDispatch方法(包括GetIDsOfNames和Invoke)。正如ATL为你提供了IUnknown方法的实现,它也提供了IDispatch方法的实现。

双重接口的IDL

双重接口的IDL看起来非常像常规接口的IDL。对BeepCnt对象,IbeepCnt接口的IDL是:

[ object, uuid(4F74530F-3943-11D2-A2B5-00C04F8EE2AF), dual, helpstring("IBeepCount Interface"), pointer_default(unique) ] interface IBeepCount : IDispatch { [id(1), helpstring("method Beep")] HRESULT Beep(); [propget, id(2), helpstring("property Count")] HRESULT Count([out, retval] long *pVal); [propput, id(2), helpstring("property Count")] HRESULT Count([in] long newVal); };



注意双重接口的IID和接口是一个双重接口的事实在接口属性中被指定。同时也应该注意到接口派生自IDispatch,而非IUnknown。

方法属性中的IDs是自动化接口的dispids。注意实现属性的方法有一个特别的属性--在这个情况中,Count属性有两种方法,一个是获取值和另一个是设置它。当MIDL产生C++头文件时,这两种方法将被命名为get_Count和put_Count。 

顺便说一句,这个接口的vtable将有10个入口:三个为IUnknown,四个为IDispatch,和三个IBeepCount方法。 
 
类型库

类型库由IDL文件的库部分产生:

[ uuid(4F745303-3943-11D2-A2B5-00C04F8EE2AF), version(1.0), helpstring("BeepCnt 1.0 Type Library") ] library BEEPCNTLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(4F745310-3943-11D2-A2B5-00C04F8EE2AF), helpstring("BeepCount Class") ] coclass BeepCount { [default] interface IBeepCount; }; };



库属性指定LIBID、版本和帮助字符串。该库引入标准类型库,接着为这个对象指定了coclass。

注意对象的CLSID被GUID在coclass属性表中指定。因为这是一个简单的对象,剩下的只是指定对象实现接口。(IUnknown和IDispatch被继承性所覆盖。)

MIDL在一个.tlb文件里产生类型库。你可以独立地使用这个文档,但是你通常不必这样。这文件在资源部分里被包含在DLL内,所以它建立到DLL中。

类型库的原始使用也很重要的:它们是一些像Visual Basic、Visual J++和Visual C++的智能指针(#输入)的工具,常用于描绘对象有什么方法和属性。

ATL怎样使用COM实现IDispatch

在这一点上,ATL的IDispatch实现非常简单。首先,在DllMain函数(当DLL载入时运行)中,Module对象的Init方法在其他事务中载入类型库。COM允许我们通过标准COM接口ITypeLib和ITypeInfo访问类型库。Init为对象的类型库ITypeInfo接口存取一个指针。

我们的好朋友ITypeInfo

ITypeInfo接口有两个叫做GetIDsOfNames和Invoke的方法。ITypeInfo::GetIDsOfNames运用类型库信息获取你发送的名字的正确的调度IDs。这个方法的实现是COM一个内置部分,所以你不必写它--只需提供类型库。

ITypeInfo::Invoke更有趣:它解析dispid和参数(等等),并且实际上通过你的双重接口的vtable调用适当的C++方法。它使用类型库信息决定参数将被转换成什么类型;怎样处理缺省的、可选择的和指定的参数;vtable偏移量是多少。然后它为参数建立一个堆栈框架和执行调用。

因为COM为你做所有这些工作,在IDispatchImpl里的IDispatch方法的ATL实现很简单:当对象被初始化时,它只是通过存储的ITypeInfo接口指针把调用传送到类型库。例如,IDispatchImpl::Invoke的编码很简单:

STDMETHOD(Invoke)(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr) { return _tih.Invoke((IDispatch*)this, dispidMember, riid, lcid, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr); }


ITypeInfo指针将被贮存到tih去。我们要做的全部是把指针传送到传递给我们的对象和参数上,COM做其余的工作。不可能更简单了。 

好的,那么有什么收获呢?

但是COM实现被优化到最小的管理开销--因为在调用结束必要的全部工作(例如数组设置和调用堆栈设置,像刚刚叙述的),IDispatch调用在任何情况下都很慢。所以差别没有那么巨大--尤其考虑到如果你自己执行IDispatch::Invoke你不得不做相同数量的工作,除去挖掘类型库。

什么时候你的组件应该支持自动化?

那么什么时候你应该和不应该支持自动化?


明显地,如果你需要你的组件被脚本语言和其他只能自动化的客户应用,你必须支持自动化。那么如果你的组件将用于网页,被Windows脚本主机,或者Office应用程序和许多其他应用程序中Visual Basic for Applications使用,你必须支持自动化。
 
换句话说,如果你不因为你的组件而彻底的分割市场,你需要支持自动化。它和在ATL里单击正确按钮一样容易,所以没有太多的理由不去做它。

另一方面,有一些组件你将只能从vtable兼容的语言(Visual C++,Visual J++,Visual Basic,等等)中调用。这里有一个好例子,如果因为你的系统设计成为组件化(一个很好的东西),而你正在编写组件。像这样的组件经常不能用于它们被设计的系统外,因为它们是专用的--所以使用这些组件可能没有意义,比如说,在网页上。

支持自动化将耗费你什么?

一些与支持自动化相关的费用。

大小和速度


如果你提供一个双重接口,你的ATL组件的大小将有一个小的增长。通常这不重要,但是如果你在一个一个的计算字节,那就有必要看看两种方式产生组件的差别是否重要了。

IDispatch调用速度慢,但是如果你用一个双重接口,你可以让顾客选择是用快的(vtable)还是慢的(自动化)调用。所以性能不是一个真正的大问题。

但是如果涉及到编组(marshalling),就有一个微妙的性能问题会减慢你的调用,比如客户端和服务器在不同的进程(或者在不同的机器)中时。双重接口需要使用COM的通用编组器(marshaller),它是由类型库信息驱动。这个编组器(marshaller)比用MIDL更慢几分。然而,花费在编组器(marshaller)调用上的额外时间与花费在转换过程或者和网络上另一个机器通信上的时间相比是可以忽略的。

对于同一个进程中(DLL)服务器编组(marshalling)通常不是必需的,所以这个问题在大多数情况下不影响性能。然而,如果客户和对象在不同的"房间"里,编组(marshalling)也被用于进程内服务器,因为它们经常处在多线程的应用程序中。在进程内部跨房间的情况下,额外的编组(marshalling)时间是明显的。

没有C++灵活

较大的问题是处理自动化接口模型的和标准COM接口模型之间的差别。首先,对一个特别的对象通常只有唯一的自动化接口。在某些设计中可能存在问题。

更坏的是,你的参数和返回值受限于可以被放入变量(Variant)的那些类型。我们在下一专栏里将讨论变量,但是现在你应该知道变量支持大多数类型,比如字符串和数组。所以它包括了要用到的大部分类型。

但是自动化接口不支持结构。你可以不去考虑它,但它确实是一件痛苦的事。顺便说一句,那意味着连接数据结构也是困难的。

所以如果你需要传送高级C++数据结构,自动化不适合你。但是假如你无论如何都要支持自动化呢?

智能选择

如果你需要支持自动化,即使你有一些好理由不支持它,有一个办法:实现一个双重接口和一个等价的(在函数性方面)常规接口。

常规接口可以利用C++强大的数据构造,并且不需要通用的编组(marshaller)--事实上,你可以定制的编组器(marshaller)使性能达到最优。这将成为你的C和C++客户使用的接口。

每个人愿意使用双重接口。你可能不得不做一些充满想象力的工作来描绘出怎样把一个复杂的C++数据结构转换成一个可以放入变量(Variant)的类型,但是一旦你实现了它,你将拥有一个任何客户都能用的对象。

给一个机会!

如果你不知道是否你真的明白这一部分所谈论的内容,除非你试一下--所以给一个机会!

· 通过编写IDispatch::Invoke调用(和相关的代码),独立写一个应用程序调用一个简单的COM自动化组件。你不需要经常做,但是做一次它将会很好的帮助你--如果没有更好的理由帮你评价你的脚本语言为你做了什么。

· 独立写一个简单的自动化组件,执行IDispatch,包括可怕的Invoke方法。有两个简单的方法我们没有提及--检查文档找出有关的信息。如果你愿意,使用COM的标准化IDispatch实现改变你的解决方案。

这次,我们通过讨论自动化属性和方法如何被使用,创建了一个COM自动化的诀窍。在下一部分里,我们将谈论有关自动化数据类型和更深入的钻研双重接口。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、 系统功能描述 1.1 信息发布 新闻:有相关权限的用户可以发送各种业务、行政、人事等单位的相关新闻,被指定接收的用户可以随时查看,第一时间了解单位动态。支持新闻评论,可以直接在新闻及新闻评论中上传文件和图片,可以对重要的新闻进行置顶,有阅读者的回执以及查看每一个阅读者的阅读时间,在发布新闻之前,可以设定审批流程,可以设定必须通过审批才能发布相关新闻。 公告:有相关权限的用户可以选择对某个人、某些人、某个部门或全体人员发送各种通告、制度、会议等消息,并可以随时查看自己已发和已收的公告,可以在发送公告时设置自动回复以确认指定接收人员是否收到,即使一时没在网上也不会错过任何信息,及时沟通,协同工作。可以对重要的公告进行置顶。有阅读者的回执以及查看每一个阅读者的阅读时间。在发布公告之前,可以设定审批流程,可以设定必须通过审批才能发布相关新闻。 通知:有相关权限的用户可以对指定人员发送通知,并可以随时查阅自己已发和已收的通知,对需要留存的通知进行归档。可以在发送通知时设置该通知同时以短信形式发送给指定人员,有效结合当前各种通讯资源,使信息的传递更为及时。可以对重要的通知进行置顶。有阅读者的回执以及查看每一个阅读者的阅读时间。在发布通知之前,可以设定审批流程,可以设定必须通过审批才能发布相关通知。 大事记:可以记录单位发展历程中的重大事件,建立单位的历史档案。可以在发表大事记时直接记录事件内容,也可以上传相关文档。大事记在发表后所有人员都可以发表相关评论,有相关权限的用户可以对事件记录及评论进行维护。便于单位的宣传和新进员工的培训。大事记中可以插入图片及附件。 1.2 公共资源 知识管理:这里是单位各种知识和资源的共享中心,单位可以将各种资料分门别类的存放到这里,便于日后方便的查阅,可以使成功案例、先进经验、技术方法、各种表单、文件资料等资源在这里共享,便于大家共同学习、使用,快速提高。可设置权限以保护机密资料安全。 下载中心:单位可以将平时积累的文摘、收集的信息、经常需要使用到的工具、文件、客户资料等各种资源有序的存放在这里进行统一管理,便于需要的时候及时查阅下载,而不必再东寻西找,浪费时间。系统运行中需要下载的OA助手、控件等也在此下载。 规章制度:政策法规、人事制度、行政制度、财务制度、管理制度、销售制度等各种制度文件的在线存放、查阅,可以方便的进行管理。 知识管理、下载中心、规模制度等模块中,均可以对文档进行按权限的全文检索。包括Word/Excel/PPT/HTML/PDF/RTF等格式。 可以对目录夹设定候修改和访问的权限,只有有权限的员才可以修改目录夹及相关文件,在设定权限时,可以按人来设定,也可以按部门来设定。 可以通过设定,对每个人的上传附件的大小进行限制。 通过突破性的技术,我们可以上传任意大小的文件,突破的IE文件上传大小限制的瓶颈。 实用工具:火车时刻、飞机航班、邮编、电话区号及国际时间等,这些常用工具只需系统管理员设置相关链接后,所有在线用户均可随时查询。 1.3 审批流转 Ÿ 起草申请 Ÿ 我的申请查询 Ÿ 待我审批 Ÿ 经我审批 Ÿ 表单管理 Ÿ 审批管理 在网上起草申请,并发送给相关负责人进行审批,实现无纸化办公。审批人可以在网上直接对申请内容进行修改、审批,注明审批意见,并盖章或签名。申请内容在多个审批人之间按顺序自动流转,审批过程即可以在建立审批模板时设定,也可以由起草者设定。可以在审批管理中根据单位要求设置模板的统一格式,可以设置每个模板的使用者、管理者和修改者。具有使用权限的用户能够使用模板起草申请;具有管理权限的用户能够在表单管理中管理使用该模板起草的申请;具有修改权限的用户能够对模板进行维护。 审批的流程即可以采用固定流程,一个模板梆定一个流程的方式,也可以设定允许在流转过程中自定义流程或修改已设定的流程,流程支持直流、分流、并流、条件分支、流程嵌套以及各种协办、联办等复杂流程。 在审批中,可以按照角色进行审批的流转,可以通过相对路径找到相应的岗位,如审批者的上级领导,当前审批者的上级领导。 流程中可以支持退文的功能,可以退到以前的任何一级,也可以退回到发起人. 在审批过程中,支持痕迹保留,电子印章,手写签名、全文批注。 可以实现催办,督办,统计。 1.4 任务管理 任务管理的作用是实现团队高效率协同办公,团队成员通过任务组实现沟通项目信息、共享项目文档。项目管理者可以在线查看项目进度图示,进行任务绩效统计,对任务内容进行调整,并对团队成员进行督办,提升员工的执行力。 具体功能包括:任务桌面、批量任务、任务查询、我的任务、绩效统计、任务督办、任务管理设置。 1.5 人力资源 实现对单位全体人员从入职到离职的全方位管理,包括以下独立模块: s 人事管理; s 考勤管理; s 招聘管理; s 绩效管理; s 培训管理; s 在线考试; 1.6 收发文管理 A、发文管理是拟稿人根据需要选择行文类别(行文用笺,处理流程,正文模版)起草公文,根据行文类别的流程设定,送交相关人员进行审核,复核,会签、签发、校对等工作,然后由办公室进行发文登记、编号、套头,盖章后进行文件的发放(分发、下发、办结)等工作,最后由公文管理员对办结公文进行归档过程。提供督办、催办功能。 在发文的审批中,可以按照角色进行审批的流转,可以通过相对路径找到相应的岗位,如审批者的上级领导,当前审批者的上级领导。 发文的流程既可以采用固定流程,一个模板梆定一个流程的方式,也可以设定允许在流转过程中自定义流程或修改已设定的流程,流程支持直流、分流、并流、条件分支、流程嵌套以及各种协办、联办等复杂流程。 流程中可以支持退文的功能,可以退到以前的任何一级,也可以退回到发起人. 在审批过程中,支持痕迹保留,电子印章,手写签名、全文批注。 可以实现催办,督办,统计 B、收文管理是公文到达后,收文登记人选择公文类别,登记公文,送交相关人员进行拟办,根据公文类别的设定流程,送交相关人员进行批示、承办、传阅等工作,也可以自定义下一步相关处理人员。并且在流转过程中,只有待办人员才有权打开批办,其他人都不能打开。最后由公文管理员对公文进行归档。归档时,可采用自动归档和手工归档。 由于我们在收发文管理中采用了MS Office插件的技术,系统可以提供:扫描输入、原文引入、手写笔输入、手工录入、从电子邮件中直接转入多种录入方式。 1.7 综合行政 考勤管理: Ÿ 工作时间设定 Ÿ 补签登记 Ÿ 请假登记 Ÿ 外出登记 Ÿ 出差登记 Ÿ 考勤查看 Ÿ 请假查询 Ÿ 外出查询 Ÿ 出差查询 考勤签到根据需要可以设置为每日1次考勤记录或每日2次考勤记录。根据选定的考勤方式可以设置具体的工作时间。补签登记可以帮助忘记考勤登记的人员补签考勤。外出登记可记录外出及返回的时间,并记录外出原由。请假登记和出差登记可以将请假和出差的日期、内容登记备案。所有登记均可在对应查询中查到,在考勤查看中能够查到全部考勤记录及登记记录。 会议管理:包括会议申请、会议自动通知、会议室申请、会议查询、会议管理(会议审批、会议总结、会议纪要、纪要归档、会议维护、会议室设置)等。建立现代化的会议制度,确保会议的必要性和功效性。 车辆管理:包括车辆使用申请、车辆使用查询、车辆管理(使用审批、信息管理、使用维护、维护管理、类型设置)等,帮助单位合理的登记、使用、管理自己的车辆,杜绝公车私用现象的发生。 办公用品:对单位使用到的各种物资进行有效管理,包括购买登记、领用登记、在库查询、领用查询等各个环节的使用情况进行记录和管理。 图书管理:对单位内部书籍进行管理,包括新书登记、图书查询、借阅登记、返还登记等,在图书查询中能够及时查询到图书的具体情况,方便员工借阅。 固定资产:对单位的固定资产进行有效管理,包括资产登记、资产查询、资产折旧等,可以随时查询单位现有资产,按照设置进行资产折旧,查询现有资产的实际价值。 1.8 个人办公 短消息:用户可以向指定人员或部门发送消息,发送消息时可附带文件同时发送。 文件传送:与短消息类似,可以向指定人员或部门发送文件,做到文件的快速传递。 个人文件柜:作为网上的文件柜,用户可以将自己的文档上传到此处并分类保存。在阅读各类新闻、通知、公告、消息及文件时,也可以将附件保存直接保存到此处。 电子邮件:建立自己的邮件中心,方便的给同事或客户发送电子邮件,消除传统信函效率低,成本高的弊端。支持群发。 手机短信:可以方便的给同事、朋友、客户等发短信,即使员工不在单位也可以保持时时沟通,并建立更加紧密的客户关系。支持群发。 工作日程:可以按天、周、月计划自己的工作内容,清楚的查看未完成、要完成、待完成的各项任务的内容、状态和时间,便于用户统筹安排工作,调整进度,及时跟进,彻底执行,确保各项工作的顺利完成。 工作日记:可以每天在网上记录下当天工作的内容和心得,便于日后查阅和总结。可以设置不同的权限查看同事或者下属的日记,以在线监督指导。可以将自己的日记导出系统作为工作档案永久保存下来。 工作计划:做出本周、本月、本季度或本年等的工作计划,任务目标等,在目标的指导下开展工作。 个人考勤:包括上下班登记、外出登记、请假登记、出差登记、个人考勤查询等内容。上下班登记相当于网上签到,既方便又防止作假。在个人办公中的“今日外出人员”可以方便用户查询当日员工外出情况。 通讯录:包括单位通讯录、个人通讯录和公共通讯录。用户可以创建属于自己的名片信息,对同事、朋友、客户、关联单位、政府机关等处的联系人资料分类记录、查询、管理。也便于单位记录业务人员的客户资料,在其离职时避免客户流失。 1.9 报表中心 可以通过报表设计器定义报表模板来制作各类报表,报表模板定义时,如Excel一样,可以定义各种样式,通过自定义SQL或向导生成SQL,生成数据源模板。通过报表设计器,可以生成各类复杂的报表。 在报表设计器中设计完成后的报表模板,上传到OA系统中,访问者就可以查看相关的报表的内容,报表的内容会根据定义好的SQL条件进行实时获取数据。 可以根据要求,自动生成日报表,周报表,月报表,自动发送到相关人员的OA文档中,达到报表自动到桌面的理想的应用境界。 报表中心可以设定相应的权限,权限可以设定到部门,指定人员,人员组。 1.10 系统管理部分 组织机构:即单位的部门设置。部门采用树式结构,可以在任意部门下定义子部门,可以设置某个部门的具体用户,指定部门经理。 用户帐号:可以为新员工建立帐号;可以为遗失密码的用户初使化密码;可以修改用户所在的权限组及部门;可以设置员工的个人信息。 权限组:可以将每个用户设置到不同的权限组中以保证不同用户具有与其身份相对应的权限。可以自由设置每个权限组的权限,本系统的权限设置不仅能够设置用户的菜单项,包括每个画面每个功能都是可以设置的。不具备权限的功能用户是不能看到不能使用的。 天生创想在权限组方面的优势是:可以设定权限到每个菜单,界面上的每个按钮。 系统日志:可以查看系统使用情况。 可以查看系统中每个人,什么时候登陆,什么时候退出,每个登录都操作了哪个功能。并且对使用者的IP地址和地区都做了详细的记录。 管理员设置: Ÿ 单位名称设置 Ÿ 单位格言设置 Ÿ 通知审批设置 Ÿ 公告审批设置 Ÿ 新闻审批设置 Ÿ 内部IP地址段设定 Ÿ 上下班时间设置 Ÿ 实用工具网址链接 Ÿ 管理员对通知、新闻、公告、消息等的删除和强行编辑、置顶。 Ÿ 设定每个用户的服务器硬盘的使用容量以及每次上传文件最大文件的尺寸限制。 可以设置通知、公告、新闻审批人员,如果审批开关被打开,用户在相应功能中发送信息时,被发送的信息将先被发送到审批者处,只有审批者通过审批,信息才能够发布,达到管理的规范化,对于管理要求不是很严格的客户,则可以设定关闭审批功能(系统默认为关闭状态),以便于快速发布信息。 网址:http://www.515158.com/ 演示地址:http://oa.515158.com/ 联系QQ:526605913
楼主辛苦开发的源码,0分放送。亲们只要评价五分就是对我的努力付出的最大回报! 此版本比v1.7的多增加了几个函数和方法,同时也修正了一些bug。 vb封装的一个控制窗口操作的类,使用非常简单!时刻更新。 clsWindow是VB6环境下使用的一个操作外部程序窗口的类,比如得到窗口句柄,得到窗口里某个文本框的内容。非常方便,使用它可以让您脱身于一堆api函数,功能强大使用简单! 这个类楼主很早就开始封装了,原本打算做成类似DOM对象那样,通过一堆getElmentByXXX等方法实现对桌面程序下各个窗口以及里面各个控件对象的自由访问,但是具体要做的工作太多,目前只实现了一部分,期待大家一起加入更新维护。 目前该类封装了绝大部分对windows窗口的常用操作,例如:获取窗口句柄,设置窗口为活动窗口,设置窗口内文本框内容,点击窗口内的某些按钮等。 这个类现在还在一直不断地扩充,功能已经很强大很广泛,使用它可以轻而易举地设置窗口标题栏文字,移动窗体等等。以前要实现这些操作常常需要一大堆api函数,现在只需要一点点代码就可以了,完全让您脱身于api函数的海洋。当然需要您需要研究每个方法实现原理的话可以看一看源代码。 使用范例: 1)关闭腾讯新闻窗口“腾讯网迷你版”。 Dim window As New clsWindow If window.GetWindowHwndByTitle("腾讯网迷你版") > 0 Then window.CloseWindow '关闭窗口 End If 以上是不是很简洁呢? 2)获取某个打开的记事本里面的内容。假设记事本标题为“测试.txt - 记事本”,通过SPY等工具查看得知记事本的文本框类名为:Edit,那么我们编写程序如下: Dim window As New clsWindow If window.GetWindowHwndByTitle("测试.txt - 记事本") > 0 Then MsgBox window.GetElementTextByClassName("Edit") End If 这个看起来也很简单,方法自由还可以使用正则匹配,可以写成下面这样: Dim window As New clsWindow If window.GetWindowHwndByTitleRegExp("测试\.txt.*?") > 0 Then MsgBox window.GetElementTextByClassName("Edi", , True)'第三个参数表示是否使用正则,默认为false End If 更多演示案例: 演示程序一(关闭包含“扫雷、蜘蛛纸牌”等系统自带游戏的窗口): http://files.cnblogs.com/sysdzw/clsWindow1.7_demo1.rar 演示程序二(调用系统计算器,点击里面的按钮进行计算): http://files.cnblogs.com/sysdzw/clsWindow1.7_demo2.rar 演示程序三(激活移动外部程序窗口): http://files.cnblogs.com/sysdzw/clsWindow1.7_demo3.rar 演示程序四(调用系统计算器,花样移动出现,效果很好): http://files.cnblogs.com/sysdzw/clsWindow1.7_demo4.rar clsWindow类最新版下载请关注博客: http://blog.csdn.net/sysdzw/article/details/9083313 '=================================================================================== '描 述:一个操作windows窗口的类,可对窗口进行很多常用的操作(类名为clsWindow) '使用范例:Dim window As New clsWindow ' window.GetWindowHwndByTitle "计算器" ' window.closeWindow '编 程:sysdzw 原创开发,如果有需要对模块扩充或更新的话请邮箱发我一份,共同维护 '发布日期:2013/06/01 '博 客:http://hi.baidu.com/sysdzw ' http://blog.csdn.net/sysdzw 'Email :sysdzw@163.com 'QQ :171977759 '版 本:V1.0 初版 2012/12/03 ' V1.1 修正了几个正则相关的函数,调整了部分类结构 2013/05/28 ' V1.2 增加属性Caption,可以获取或设置当前标题栏 2013/05/29 ' V1.3 增加了方法Focus,可以激活当前窗口 2013/06/01 ' 增加了方法Left,Top,Width,Height,Move,处理窗口位置等 ' V1.4 增加了窗口位置调整的几个函数 2013/06/04 ' 增加了得到应用程序路径的函数AppName ' 增加了得到应用程序启动参数的函数AppCommandLine ' V1.5 增加了窗口最大最小化,隐藏显示正常的几个函数 2013/06/06 ' 增加了获取控件相关函数是否使用正则的参数UseRegExp默认F ' V1.6 将Left,Top函数改为属性,可获得可设置 2013/06/10 ' V1.7 增加了CloseApp函数,可以结束进程 2013/06/13 ' 修正了部分跟正则匹配相关的函数 ' 增加函数:GetElementTextByText ' 增加函数:GetElementHwndByText ' V1.8 增加GetWindowHwndByClassName函数 2013/06/26 ' 增加GetWindowHwndByClassNameEx函数 ' 增加GetWindowHwndByAppName函数 ' 增加私有变量hWnd_ ' 增加属性hWnd,可设置,单设置时候会检查,非法则设置为0 ' 更新GetWindowHwndByTitleEx函数,使之可以选择性支持正则 ' 删除GetWindowHwndByTitleRegExp函数,合并到上面函数 ' 增加SetFocus函数,调用Focus实现,为了是兼容VB习惯 ' 扩了ProcessID、AppPath、AppName、AppCommandLine三个函数,可带参数 ' 网友wwb(wwbing@gmail.com)提供了一些函数和方法属性: ' CheckWindow, Load, WindowState, Visible, hDC, ZOrder ' AlphaBlend, Enabled, Refresh, TransparentColor ' 部分采纳网友意见,将句柄变量改为hWnd_,但是hWnd作为公共属性 '===================================================================================
clsWindow类使用说明 clsWindow是VB6环境下使用的一个操作外部程序窗口的类,比如得到窗口句柄,得到窗口里某个文本框的内容。非常方便,使用它可以让您脱身于一堆api函数,功能强大使用简单! 这个类楼主很早就开始封装了,原本打算做成类似DOM对象那样,通过一堆getElmentByXXX等方法实现对桌面程序下各个窗口以及里面各个控件对象的自由访问,但是具体要做的工作太多,目前只实现了一部分,期待大家一起加入更新维护。 目前该类封装了绝大部分对windows窗口的常用操作,例如:获取窗口句柄,设置窗口为活动窗口,设置窗口内文本框内容,点击窗口内的某些按钮等。 这个类现在还在一直不断地扩充,功能已经很强大很广泛,使用它可以轻而易举地设置窗口标题栏文字,移动窗体等等。以前要实现这些操作常常需要一大堆api函数,现在只需要一点点代码就可以了,完全让您脱身于api函数的海洋。当然您如果想知道里面的方法实现原理的话可以看一看源代码。 使用范例: 1)关闭腾讯新闻窗口“腾讯网迷你版”。 Dim window As New clsWindow If window.GetWindowHwndByTitle("腾讯网迷你版") > 0 Then window.CloseWindow '关闭窗口 End If 以上是不是很简洁呢? 2)获取某个打开的记事本里面的内容。假设记事本标题为“测试.txt - 记事本”,通过SPY等工具查看得知记事本的文本框类名为:Edit,那么我们编写程序如下: Dim window As New clsWindow If window.GetWindowHwndByTitle("测试.txt - 记事本") > 0 Then MsgBox window.GetElementTextByClassName("Edit") End If 这个看起来也很简单,方法自由还可以使用正则匹配,可以写成下面这样: Dim window As New clsWindow If window.GetWindowHwndByTitleRegExp("测试\.txt.*?") > 0 Then MsgBox window.GetElementTextByClassName("Edi", , True)'第三个参数表示是否使用正则,默认为false End If clsWindow类最新版下载请关注博客: http://blog.csdn.net/sysdzw/article/details/9083313 '======================================================================= '描 述:一个操作windows窗口的类,可对窗口进行很多常用的操作(类名为clsWindow) '使用范例:Dim window As New clsWindow ' window.GetWindowByTitle "计算器" ' window.closeWindow '编 程:sysdzw 原创开发,如果有需要对模块扩充或更新的话请邮箱发我一份,共同维护 '发布日期:2013/06/01 '博 客:http://hi.baidu.com/sysdzw ' http://blog.csdn.net/sysdzw 'Email :sysdzw@163.com 'QQ :171977759 '版 本:V1.0 初版 2012/12/03 ' V1.1 修正了几个正则相关的函数,调整了部分类结构 2013/05/28 ' V1.2 增加属性Caption,可以获取或设置当前标题栏 2013/05/29 ' V1.3 增加了方法Focus,可以激活当前窗口 2013/06/01 ' 增加了方法Left,Top,Width,Height,Move,处理窗口位置等 ' V1.4 增加了窗口位置调整的几个函数 2013/06/04 ' 增加了得到应用程序路径的函数AppName ' 增加了得到应用程序启动参数的函数AppCommandLine ' V1.5 增加了窗口最大最小化,隐藏显示正常的几个函数 2013/06/06 ' 增加了获取控件相关函数是否使用正则的参数UseRegExp默认F ' V1.6 将Left,Top函数改为属性,可获得可设置 2013/06/10 ' V1.7 增加函数:CloseApp 结束进程 2013/06/13 ' 修正了部分跟正则匹配相关的函数 ' 增加函数:GetElementTextByText ' 增加函数:GetElementHwndByText ' V1.8 增加函数:GetWindowByClassName 2013/06/26 ' 增加函数:GetWindowByClassNameEx ' 增加函数:GetWindowByAppName ' 增加私有变量hWnd_ ' 增加属性hWnd,可设置,单设置时候会检查,非法则设置为0 ' 更新GetWindowByTitleEx函数,使之可以选择性支持正则 ' 删除GetWindowByTitleRegExp函数,合并到上面函数 ' 增加SetFocus函数,调用Focus实现,为了是兼容VB习惯 ' 扩了ProcessID、AppPath、AppName、AppCommandLine三个函数,可带参数 ' 网友wwb(wwbing@gmail.com)提供了一些函数和方法属性: ' CheckWindow, Load, WindowState, Visible, hDC, ZOrder ' AlphaBlend, Enabled, Refresh, TransparentColor ' 采纳wwb网友的部分意见,将句柄变量改为hWnd_,但是hWnd作为公共属性 ' V1.9 修正函数:GetMatchHwndFromWindow 正则表达式的错误 2013/08/07 ' 修正函数:GetMatchHwndFromWindow 函数中的一些错误 2014/09/23 ' 增加函数:GetWindowByClassNameEx ' 增加函数:GetWindowByPID 根据PID取窗口句柄 ' 增加函数:GetCaptionByHwnd 根据句柄取得标题 ' 增加函数:SetTop设置窗体置顶,传入参数false则取消 2014/09/24 ' 增加函数:Shake、FadeIn、FadeOut 抖动、淡入、淡出特效 '=======================================================================
功能描述 clsWindow是VB6环境下使用的一个操作外部程序窗口的类,比如得到窗口句柄,得到窗口里某个文本框的内容。非常方便,使用它可以让您脱身于一堆api函数,功能强大使用简单! 这个类楼主很早就开始封装了,原本打算做成类似DOM对象那样,通过一堆getElmentByXXX等方法实现对桌面程序下各个窗口以及里面各个控件对象的自由访问,但是具体要做的工作太多,目前只实现了一部分,期待大家一起加入更新维护。 目前该类封装了绝大部分对windows窗口的常用操作,例如:获取窗口句柄,设置窗口为活动窗口,设置窗口内文本框内容,点击窗口内的某些按钮等。 这个类现在还在一直不断地扩充,功能已经很强大很广泛,使用它可以轻而易举地设置窗口标题栏文字,移动窗体等等。以前要实现这些操作常常需要一大堆api函数,现在只需要一点点代码就可以了,完全让您脱身于api函数的海洋。当然您需要研究每个方法实现原理的话可以看一看源代码。 使用范例(请在v1.9以上测试): 1)关闭腾讯新闻窗口“腾讯网迷你版”。 Dim window As New clsWindow If window.GetWindowByTitle("腾讯网迷你版").hWnd > 0 Then window.CloseWindow '关闭窗口 End If 以上是不是很简洁呢? 20150715更新追加: 最新1.9版本更简洁,一句话解决: w.GetWindowByTitle("腾讯网迷你版").CloseWindow 小伙伴,是不是简洁爆了呢?:) 为了防止程序找不到窗口而一直等待可以改成: w.GetWindowByTitle("腾讯网迷你版",1).CloseWindow (意思为超时等待1秒。默认会耐心等60秒,除非你确定窗口一定有,然后就用上面的。) 2)获取某个打开的记事本里面的内容。假设记事本标题为“测试要求.txt - 记事本”,通过SPY等工具查看得知记事本的文本框类名为:Edit,那么我们编写程序如下: Dim window As New clsWindow If window.GetWindowByTitle("测试要求.txt - 记事本").hWnd > 0 Then MsgBox window.GetElementTextByClassName("Edit") End If 这个看起来也很简单,方法自由还可以使用正则匹配,可以写成下面这样: Dim window As New clsWindow If window.GetWindowByTitleEx("工作任务\.txt.*?", , , True).hWnd > 0 Then MsgBox window.GetElementTextByClassName("Edi", , True) '第三个参数表示是否使用正则,默认为false End If 获取标题那边如果觉得要把标题写完整太麻烦,可以将GetWindowByTitle该车GetWindowByTitleEx然后后面只要写关键字就行啦。看招: Dim window As New clsWindow If window.GetWindowByTitleEx("工作任务").hWnd > 0 Then MsgBox window.GetElementTextByClassName("Edit") End If clsWindow类最新版下载请关注博客: http://blog.csdn.net/sysdzw/article/details/9083313 '============================================================================================== '名 称:windows窗体控制类v2.0 '描 述:一个操作windows窗口的类,可对窗口进行很多常用的操作(类名为clsWindow) '使用范例:Dim window As New clsWindow ' window.GetWindowByTitle "计算器" ' window.closeWindow '编 程:sysdzw 原创开发,如果有需要对模块扩充或更新的话请邮箱发我一份,共同维护 '发布日期:2013/06/01 '博 客:http://blog.163.com/sysdzw ' http://blog.csdn.net/sysdzw 'Email :sysdzw@163.com 'QQ :171977759 '版

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值