PDA

查看完整版本 : 基于VB的COM编程入门教程(三)


华亮
2004-11-17, 11:50 PM
扩展COM
  我们将讨论如何解决前面遇到的兼容性问题,然后再熟悉一下其他的ActiveX组件,并解决与实例相关的一些小问题,最终将使大家的COM水平达到一个新的台阶。

 第一节 兼容性
在本教程第二部分的最后,我们遇到一个小问题,但确切地说,那实际上是一个大问题。如果有时间的话,这个问题应该值得我们花大精力去研究。

  还记得我们是怎样遇到那个问题吗?当时,我们先编译ActiveX DLL,然后编译使用该DLL的测试程序。接着,我们重新编译DLL,那是因为假设DLL中的内容需要修改。然而,再运行测试程序时,却出现错误!

  虽然,我们可以重新编译测试程序,以便该程序能正确运行。但是,如果这里不是VB程序,而是Excel数据表或是C++统计程序在使用该DLL,那么是不是每次对ActiveX DLL进行小小的修改后都要重新编译这些程序呢?

  是的,肯定不能这样。

  因为经验告诉我们,这是一个兼容性问题。所以,可以这样处理:

  启动Visual Basic,打开Northwind工程;

  选择"Project"->"Northwind Properties"菜单;

  单击"Component"标签;

  浏览一下"Version Compatibility"的页面内容,可以发现有三个选项。现解释一下:

  No Compatibility —— 每次编译时,用户COM组件都被标有一个新的标记,这就意味着程序只能使用旧标记(以前版本)的DLL。

  Project Compatibility —— 每次编译时,用户COM组件不是总会被标有一个新的标记。如果是的话,任何当前使用的应用程序都会失败。事实上,只有当当前工程和已经编译过的DLL工程有较大不同时才会这样。

  Binary Compatibility —— 每次编译时,应用程序总试图保存前一个编译过的DLL标记,这样就确保了使用的应用程序不会出现蓝屏的死机现象。但是,若当前将要编译的DLL和以前编译过的DLL区别太大,则新的标记就会被标上。

  让我们测试一下上述论点:

  打开本教程上一部分的测试程序;

  重新编译一下;

  试运行一下,应该能正常工作;

  打开ActiveX DLL工程;

  将其属性设置为Binary Compatibility;

  重新编译一下该DLL;

  试运行一下测试程序,应该能正常工作。

  好了,看起来似乎解决了问题。但当重新编译DLL后,大多数开发人员将会陷入另一种不兼容的境地。

  难道就没有更好的解决办法吗?我们暂时将这个问题放到一边!

第二节 ActiveX

但这里不想再作更详细的讨论,因为ActiveX EXE和ActiveX DLL除了在运行时有一些微小区别外,其他都相同。

  它们的区别首先表现在它们的"进程空间"的不同。所谓"进程空间"是用于运行、处理和存取的一块计算机内存。任何Windows程序,如Microsoft Word等,都有自己的"进程空间",它很像程序的桌面那样。

  当使用ActiveX DLLs工程运行时,DLL是在使用它的程序的进程空间中运行的,而ActiveX EXE是在进程空间外面工作的。但是,ActiveX EXE还有自己的"桌面"。这究竟如何理解呢?

  假如,ActiveX DLL变得不稳定或意外受损时,使用它的应用程序常常出现蓝屏的死机现象,而在EXEs中却不会发生,因为它有自己的"进程空间",即使被破坏,也仅仅是桌面受损,当然用户程序应该很好地去修复它。

  其次,它们的区别还表现在装载的速度上。由于DLL是直接装载到已存在的进程空间,所以它的速度非常快。而EXEs由于还要分配自己的进程空间,所以速度上相对慢一点。

  上述两点区别可以说是它们真正的区别。

  总之,如果使用不同的Windows工具来实现相应的ActiveX组件,那么相应的工程类型就应该有所不同。例如,若使用MTS,则应创建DLL工程,若使用DCOM,则应创建EXE工程。当然,即使现在不理解这此缩写字母的含义,我们也不必担心。因为它们是针对高级用户的,并用于COM远程的工具组件。以后有机会再来给出相应的教程。

  这里再来分析第二点的区别。

  如果现在需要创建这样的一个程序,它不断地检测一个数据库是否有什么改变。那么我们想到的是在程序中使用一些"timer"(计时器),每隔10分钟激发一次并检测该数据库。但问题来了,在该进程空间的其他所有代码都要被停止运行直至数据库检测完毕。

  而ActiveX EXEs伟大之处,就在于它有自己的进程空间。所以在其中添加的计时器也只会工作在自己的进程空间中而不会影响其他使用它的程序。也就是说,对于前面的工程来说,若使用ActiveX EXE来检测数据库,则不会停止其他使用它的程序的运行;即使需要从其他程序中返回一个消息,也可以通过其他事件而获得。

  需要说明的是,运行代码远离正规程序而通过事件与使用的应用程序会话的方法称为"异步处理"。通常当需要对e-mail或数据库作定期检查时,或当运行一个长的报表以及计算大的统计数据时,我们就需使用这种异步处理方式。

  不怕你惊讶的话,我们可以将前面论述的内容总结成这样的一句话:

  "ActiveX DLLs是在进程内运行,而ActiveX EXEs是在进程外运行"。

  好了,下一节将创建并测试一个自己的ActiveX EXE工程,并使用大家还不太熟悉的"异步处理"技巧。然后,提出一个称为"实例"的有意义的概念,最后指明怎样获得更多的COM知识使自己达到一个新的水平。


第三节 测试ActiveX EXE


本节将创建并测试自己的ActiveX EXE程序。

  示例中将使用这样一个组件,它是一个有效的文件探测器。大约每隔60秒检测指定文件的存在性。如何该文件存在,该组件激发一个事件来调用应用程序,如果不存在,则另作处理。

  当然,如果将所有代码写到ActiveX DLL工程,则运行时程序代码将被挂起直到文件检测代码运行完毕为止。由于ActiveX EXE工程拥有自己的进程空间,代码运行时会自我协调、异步处理,从而不会使其他程序代码停顿。

  下面就来创建:

  新建一个"ActiveX EXE"工程;

  工程名设为"File";

  添加的类名为"FileCheck";

  下一步,我们需要构造一些用于每隔1分钟左右检测文件的代码。这里将在ActiveX EXE工程插入一个带有计时器的表单。但该表单不会被显示,因为我们只是使用上面的计时器控件每隔1分钟左右来检测文件,如果相应的文件被检测到,则激发一个事件。

  选择"Project"->"Add Form";

  在表单Form1中添加一个计时器;

  在表单代码中添加下列变量的声明:
Public Filename As String
  
  该变量用于保存被监视的文件名。

  在表单代码中添加下列事件的声明:
Public Event FileFound()
  
  该事件只有当前面的文件发现后才被激发。

  在Timer1代码中添加下列语句:
  Private Sub Timer1_Timer()

   If Dir(Filename) <> "" Then

    RaiseEvent FileFound

    Timer1.Interval = 0

   End If

  End Sub

  代码中,首先简单地检测文件,若存在则激发FileFound事件,然后将Timer1的时间间隔设为0,停止以后的检测。

  打开FileCheck类;

  在通用声明处添加下列对象的声明:
Dim WithEvents objFileCheck As Form1
  
  这就是Form1的代码,它通知Visual Basic上述定义的对象是用来保存表单的。关键词WithEvents表示该类可以接收传送来的事件,如前面的FileFound等。

  本节将创建并测试自己的ActiveX EXE程序。

  示例中将使用这样一个组件,它是一个有效的文件探测器。大约每隔60秒检测指定文件的存在性。如何该文件存在,该组件激发一个事件来调用应用程序,如果不存在,则另作处理。

  当然,如果将所有代码写到ActiveX DLL工程,则运行时程序代码将被挂起直到文件检测代码运行完毕为止。由于ActiveX EXE工程拥有自己的进程空间,代码运行时会自我协调、异步处理,从而不会使其他程序代码停顿。

  下面就来创建:

  新建一个"ActiveX EXE"工程;

  工程名设为"File";

  添加的类名为"FileCheck";

  下一步,我们需要构造一些用于每隔1分钟左右检测文件的代码。这里将在ActiveX EXE工程插入一个带有计时器的表单。但该表单不会被显示,因为我们只是使用上面的计时器控件每隔1分钟左右来检测文件,如果相应的文件被检测到,则激发一个事件。

  选择"Project"->"Add Form";

  在表单Form1中添加一个计时器;

  在表单代码中添加下列变量的声明:
Public Filename As String
 
  该变量用于保存被监视的文件名。

  在表单代码中添加下列事件的声明:

  Public Event FileFound()

  该事件只有当前面的文件发现后才被激发。

  在Timer1代码中添加下列语句:
  Private Sub Timer1_Timer()

   If Dir(Filename) <> "" Then

    RaiseEvent FileFound

    Timer1.Interval = 0

   End If

  End Sub

  代码中,首先简单地检测文件,若存在则激发FileFound事件,然后将Timer1的时间间隔设为0,停止以后的检测。

  打开FileCheck类;

  在通用声明处添加下列对象的声明:
Dim WithEvents objFileCheck As Form1

  这就是Form1的代码,它通知Visual Basic上述定义的对象是用来保存表单的。关键词WithEvents表示该类可以接收传送来的事件,如前面的FileFound等。
从"Object"下拉列表框中选择"Class";

  再从"Procedure"下拉列表框中选择"Initialize";

  在Class_Initialize事件中添加下列代码:
  Private Sub Class_Initialize()

   Set objFileCheck = New Form1

  End Sub

  该代码简单地使objFileCheck等于Form1的新的一个实例。之后,我们将使用在Form1中添加的功能。接下来,我们编写一个子过程用来监视一个文件。

  在FileCheck中添加下列代码:
  Public Sub MonitorFile(Filename As String)

   objFileCheck.Filename = Filename

   objFileCheck.Timer1.Interval = 60000

  End Sub

  当我们调用此过程时,需要给出文件名参数。这时,表单的Filename变量保存该文件名,然后将计时器的时间间隔属性设置为60,000毫秒并激活该计时器。

  至此,我们构造了用于监视文件的所有代码。但是当文件检测到时,我们需要通过激发FileFound事件通知程序正在使用ActiveX EXE。

  在通用声明部分添加下列事件声明:
Public Event FileFound(Filename As String)

  该代码只是简单地定义一个FileFound事件,下一步该事件的相应代码。

  从"Object"下拉列表框中选择"objFileCheck";

  再从"Procedure"下拉列表框中选择"FileFound";
  Private Sub objFileCheck_FileFound ( )

   RaiseEvent FileFound ( objFileCheck.Filename)

  End Sub

  显然,当文件检测到时,这里的FileFound事件就被激发。但我们还需要在使用EXE程序中添加这个事件的添加代码。

  在objFileCheck_FileFound事件中添加下列代码:

  RaiseEvent FileFound(objFileCheck.Filename)

  这就是我们的全部代码。

  当程序员使用该类时,都可以文件名为参数调用MonitorFile方法,然后表单中的计时器被引发,每隔60秒钟检测一下文件,若该文件被查找到,则激发FileCheck类中的事件,该事件又激发相关的应用程序中的另一个事件,用来通知程序员,该文件已被找到。


第四节 实例

在测试文件组件之前,我们必须先编译它。但在进行这项工作之前,让我们先来浏览一下Visual Basic所给出来的一些额外选项。

  在打开FileCheck代码窗口的同时,我们来看看它的属性窗口。

  属性有Name、DataBindingBehaviour(用于将类和数据源相"绑定")、可持久性(用于控件中,允许保存某个类的属性)以及实例。

  当类名属性修改后,我们或许不必担心前三项的属性。那么什么是实例呢?

  实例(Instancing)属性决定自己的类对于使用ActiveX组件的应用程序来说是否可见。若可见,则在任何时候可运行不止一个的实例。

  实例属性有很多选项,我们来看一下:

  MultiUse —— 这可能是最常用的选项。它只提供给其他应用程序一个组件的实例,这个实例可以提供多个对象。这样就节约了内存空间并允许用户共享全局变量。

  Private —— 除该组件内的对象,类对于其他对象是不可见的。它通常用于类单独被类中的其他对象所访问。

  GlobalMultiUse —— 这个类的各种属性和方法可以象简单的全局函数那样被调用。另外,在VB中该类的实例不需要显式创建,因为它会自动创建。各种属性和方法都可从单独的一个组件实例中调用。

  PublicNotCreatable —— 它表示只有在创建实例的前提下,该类才是可见的。换句话说,用户不能用New关键词创建一个类对象。用户的类对象必须选被创建,然后才可使用。这有点像DAO的记录集,用户不能创建一个新的记录集,而只能用OpenRecordset方法简单地打开它。

  SingleUse —— 它表示每次在代码中开始的一个组件的新的实例,只能运行另一个ActiveX组件的实例。换句话说,每个实例都获得自己的"进程空间"。虽然还有一些限制,没有什么奇怪的,它和MultiUse是相对的。

  GlobalSingleUse —— 类似于GlobalMultiUse,除了代码中创建的对象运行一个组件的新实例。

上述选项,我们很少全部都使用过。毫无疑问,MultiUse是最常见的,其次是Private和GlobalMultiUse,其他的一般很少使用。当然,我们不禁要问,在创建ActiveX DLLs时,我们能得到多少这样的选项呢?答案是明确,因为它们的工作方式是相同的。

  好了,非常抱歉在前面过程中耽搁太多的时间,不过若不论及这些不常使用的实例选项,那么又有人会抱怨了。

  行啦,让我们编译并测试我们的ActiveX EXE组件吧!

  选择"File"->"Make File.exe";

  选择一个文件名,然后按[OK]。

  下一节,我们将创建一个测试应用程序。

第五节 创建测试程序

这里我们直接创建一个应用程序来测试一下前面生成的ActiveX EXE文件监视组件:

  新建一个"Standard EXE"工程;

  下面需要添加一个引用到我们的新的文件组件中去,然后添加少量的代码作尝试:

  选择"Project"->"References"菜单;

  选中"File"组件选项,然后单击[OK]按钮;

  在表单的通用声明部分添加下列代码:

  Dim WithEvents MyFileObject As FileCheck

  从"Object"下拉列表中选择"MyFileObject";

  确保插入符在FileFound事件程序中;

  键入下列代码:

  MsgBox "Found: " & Filename

  在表单Form1中添加一个命令按钮;

  在该按钮中添加下列代码:

  Set MyFileObject = New FileCheck

  MyFileObject.MonitorFile ("c:\test.txt")

  这里,我们是将MyFileObject定义成FileCheck的一个新实例,然后用c:\test.txt参数运行MonitorFile方法。这时,程序在后台中启用计时器,且每隔60秒都来检测一次这个文件。

  由于,我们的计算机中还没有这个文件,所以什么也没有发生。现在,我们用Notepad(记事本)在C盘中创建一个名为test.txt的文件。

  则在60秒内,弹出一个消息对话框用来表示我们的文件被找到。这就是我们刚刚创建的ActiveX EXE!

  对于上述这样的组件,使用异步处理是没有太大的必要的。正如我们以前讨论的那样,ActiveX EXE是有自己的进程空间的。因此,当计时器启用并检测文件的存在性时,它不会使其它程序暂停。而如果使用的是DLL,那么就可以使用异步处理了。

  即使这样,你能在计时器中放入任何代码吗?是能放入创建大型报表的程序、复杂的计算代码,还是其他?

  至此,我们构造了一些实际的常规组件,但我们还没有来得及讨论它们的发布问题,这将在下一节中进行。

第六节 发布组件

既然我们构造了这世界上最有魅力的ActiveX组件,就应该把它们发布出去。那么我们该怎么做呢?

  幸运的是,发布COM组件是一件非常容易的事。 我们只要使用Package & Deployment(打包和展开)向导就可对计算机上的常规Visual Basic工程进行相应的操作。同样,该向导还适用于ActiveX工程。

  但是,发布时只有ActiveX组件本身自己吗?如果不是,那么为何又要运行向导,单独地为自己的ActiveX工程创建一个安装程序?或是连同使用它的应用程序一起发布?对于常规工程来说,简单地创建一个安装,其组件就会自动被打包。

  唔,可能就这么简单吧?