Visual Basic 部件关闭规则

这个主题与“ActiveX 部件关闭”密切相关,后者描述部件的正确关闭行为。

对于进程内部件和进程外部件来说,Visual Basic 用于决定何时关闭部件的规则是不同的。

规则 进程内部件 进程外部件
引用 无论内部还是外部,均无对部件的 Public 对象的引用。 没有任何进程外客户正在引用部件的 Public 对象
窗体 部件没有可见窗体。 部件没有加载窗体
代码 部件模块中的代码没有正在运行,或在调用列表中等待执行。 同进程内部件
启动 部件不在启动的进程中,也不正在响应客户对对象的请求。 同进程内部件

规则 1:引用

当决定是否关闭一个部件时,Visual Basic 只考虑对公共对象的引用。而不考虑对 Instancing 属性值为私有的类所创建的对象的引用。

重点 VB 类型库中的类也是私有的,对这些类的对象的引用并不能保持部件的加载。VBA 类型库中的类是公共的。(要查看这些库中的类,请在“对象浏览器”的“工程/库”框中选择感兴趣的库。)

进程内部件

对于进程内部件来说,引用规则在两个方面有其它影响:私有对象及对公共对象的内部引用。

注意 用 Visual Basic 写的客户应用程序可能不会在最后一个引用被释放后立刻试图下载进程内部件。这要看是否常有空闲的时间。这个时间的经验值为两分钟。

对私有对象的无效引用对进程内客户可能是致命的

假如进程内部件将对 Private 对象的引用传递给客户,且该客户接着释放对公共对象的最后一个引用。该部件可在此后的任何时候下载— 因为私有对象引用不阻止关闭— 从而给客户留下对该私有对象的无效引用。

如果此客户试图使用此无效引用,则会发生程序失败,且客户应用程序会突然终止。如果此客户极少运行到试图使用无效引用的代码段,这个程序失败则会随机出现,且很难调试。

重点 私有对象──即从 VB 库中的类生成的对象,或从 Instancing 属性值为 Private 的类生成的对象──通常因为某个原因才为私有的。如果需要将其中一个私有类的一个实例传递给客户,则必须使其对象安全地供公共使用,然后将该类的 Instancing 属性值更改为 PublicNotCreatable。

对公共对象的内部引用阻止了 DLL 下载

对于进程内部件来说,Visual Basic 无法区分对公共对象的外部引用(即客户拥有的引用)和内部引用(即进程内部件内部的对象变量的引用)。

因此,如果进程内部件保持对自己的一个公共对象的引用,则 Visual Basic 不会下载它。该部件引用着的任何对象将继续占用内存和资源。

这种内部引用最常见的原因是全局对象变量— 例如全局集合— 及循环引用。当两个对象互相引用对方时出现循环引用。请参阅“部件设计的一般准则”中的“循环引用的处理”一节。“部件设计的一般准则”中的应用程序探讨了关于全局集合和循环引用的各种问题。

多个进程内部件可互相保持对方加载

如果使用了几个进程内部件,且它们都引用对方的公共对象,这些对象引用会阻止 Visual Basic 关闭部件。

进程外部件

对于进程外部件来说,Visual Basic 对公共对象的外部和内部引用分别保持计数。只有外部引用— 即客户拥有的引用— 才能保持部件运行。这与进程内部件的计数规则相反。

注意 与进程内部件相反,Visual Basic 的调度规则禁止进程外部件将私有对象引用传递给客户。

避免保持内部引用

对公共对象— 全局对象变量、全局集合及循环引用— 的内部引用仍应避免。尽管内部引用不会保持进程外部件运行,它们却可能阻止破坏孤立对象。

例如,假如一个进程外部件正被两个客户所使用,且客户 A 比客户 B 先释放其所有的对象引用。如果部件正保持对客户 A 正在使用的对象的内部引用,这些 A 已不使用的对象将继续占用内存和资源,直到该部件关闭— 而部件关闭只有在客户 B 释放其最后一个对象后才可能发生。

如果此时有第三个客户开始使用部件提供的对象,则客户 A 和客户 B 的孤立对象都将继续占用内存和资源。

进程内部件保持的引用不在计数之列

进程外部件使用的对象可能是进程内部件提供的,而在进程外部件的进程空间中运行。进程外部件可以将对它的公共对象的引用传递给进程内部件,或者进程内部件可能独立地从进程外部件请求一个对象。

不管它们是如何获得的,当进程外客户释放了对该部件的所有对象引用时,进程内部件保持的引用不会阻止进程外部件的下载。

规则 2:窗体

Visual Basic 确定关闭部件是否安全时将窗体也计算进去了,这看起来有些奇怪。要理解这一点,考虑象 Microsoft Excel 这样的应用程序,它可被使用计算机的人启动,也可提供对象给客户应用程序。

如果不让使用对象的客户应用程序使用该部件是错误的,则仅仅因为客户应用程序释放了它们正在使用的对象,就破坏了用户的电子数据表将更加错误,因为它可能是数小时.艰辛劳作的成果。

换句话说,使用用户界面(如果部件有用户界面的话)的人也是客户。

Visual Basic 如何解释窗体规则

进程内部件与进程外部件依赖客户的方式不同,所以 Visual Basic 对这两种情况的窗体规则的解释也不同。

即使客户释放了所有的对象引用,可见窗体也会使进程内部件继续驻留在内存中;而不可见窗体则不会。

注意 在客户应用程序关闭之前,进程内部件可通过这种方式保持运行。在客户应用程序关闭时,所有窗体都被强制下载。此时窗体下载而不必收到 QueryUnload 事件,且 Unload 事件的 cancel 参数被忽略。

与之相反,即使到所有引用都被释放了,甚至在所有引用都终止了,任何加载的窗体将保持进程外部件的运行。

Visual Basic 做此区别是因为进程外部件可能是一个独立的桌面应用程序,而且其窗体不可见的状态可能是临时状态。

控制窗体寿命

总之,当创建窗体的对象终止时,窗体应被下载。例如,部件可能使用一个隐藏窗体来保持 Timer 控件。可以在创建和加载窗体的对象的 Terminate 事件过程中放置下载该窗体的代码。

提示 如果多个对象正在共享同一窗体,可为此窗体创建 UseCount 属性。在对象的 Initialize 和 Terminate 事件中增减 UseCount 属性值,当 UseCount 值为零时,下载窗体。

部件是桌面应用程序

如果部件还是一个独立的桌面应用程序,即使没有客户应用程序使用该部件的对象,其主窗体也将使部件继续运行。当用户从“文件”菜单中选择“退出”,或单击主窗体的“关闭”框时,此窗体应和所有已加载的隐藏窗体,例如常用的对话框,一道下载。

对于用户来说,应用程序已经关闭。然而,如果客户应用程序仍然要引用部件的对象,Visual Basic 则让该部件在后台继续运行。

客户应用程序何时可以操作用户界面

对于提供控制其用户界面的对象部件是最难以关闭的。例如,可能创建一个 Window 类,它控制应用程序主窗体中的 MDI 子窗体。如果用户关闭主窗口,是否应不管客户应用程序是否有可见的子窗口都隐藏主窗体呢?

这类问题使用户界面设计者夜不能寐。有很多方案可选择。例如,主窗体的 QueryUnload 事件中有一个 unloadmode 参数,可测试该参数来确定窗体是否因用户单击了“关闭”框而关闭,或是因程序代码正试图下载它而关闭。

可下载那些用户通过应用程序菜单及工具栏按钮打开的子窗体,隐藏主窗体,以及将 Application 对象的 Visible 属性置为 False。客户应用程序可在需要时将 Visible 属性值重新置为 True。

或者,可下载所有的用户窗体,而让主窗口中剩下的子窗体保持可见。或者想显示一条信息告诉用户正在进行什么操作,提供以何种方式隐藏主窗口的选项,因为计算机用户通常被认为是可视界面的最终所有者。

此问题没有合适的解决办法。最好的办法是做许多可用性测试。

规则 3:代码执行

如果部件代码正在执行,Visual Basic 不会下载它。这包括这样一些情况:部件调用了另一个部件的例程、DLL 中的函数、或一个 Windows API 函数。当外部例程执行时,调用它的过程则在调用列表中等待,而 Visual Basic 则不会关闭此部件。

在这种关闭情况下,没有什么需要采取或避免的措施。与外部对象引用一样,由 Visual Basic 为用户管理一切。

规则 4:启动

从客户应用程序请求某一对象而引起的部件启动,到该部件返回对象引用的那一刻之间,是特别麻烦的时间段,最后的一个特殊情况正好覆盖了它。在启动序列中,可能有时没有加载的窗体也没有要执行的代码,当然也没有任何外部对象引用。

在这样的代码执行情况下,用户不需采取任何操作,因为 Visual Basic 会为用户处理这种情况。

部件关闭指南总结

下列指南总结了对于部件关闭必须记住的要点。

制造一个好的部件的大部分工作都是由 Visual Basic 完成的。这样用户就可以免去许多繁重的劳动。