delphi(f3)
时间:2006-06-10 来源:许我一个信仰
附录C 将程序转变为自动化服务器
自动化是COM的一个方面,您可以利用它从程序中获得额外的收益。本附录示范了一些基本的步骤来选取一个程序(这里是第19章的SQLBuilder),然后向工程添加自动化对象,将程序转换为自动化服务器。之所以选择SQLBuilder,是因为自动化客户可利用SQLBuilder服务器来针对任何数据集生成SQL。
请记住,在运行自动化服务器之前,还需要有自动化客户,即任何请求创建COM对象实例的程序。可创建COM对象实例的工具非常多,包括报表工具、VBA(大部分在Microsoft Office中),以及大部分的Wintel编程语言。
C.1 向工程添加自动化对象
这里不再涉及第19章的SQLBuilder程序的细节,该工程的源代码和编译后的版本都在本书的光盘上。该程序使用了TSession类型的全局变量来从ODBC和BDE注册文件获取数据库名字的列表。当用户选定某个数据库名时,将以同样的方式获取该数据库中可用的表名。用户选取一个表时,将从TDataSet.Fields集合读入字段定义并显示在TValueListEditor对象中,该对象是网格形状的、基于TStrings的控件。在字段值已知的情况下,可利用Format函数对定义了参数的SQL语句进行简单的字符串替换。就是这样。
如果您已经读过本书前面的部分,那么您已经掌握了这些技巧,因此我们不再涉及SQLBuilder工程的更多细节。从这里开始,我们将进行一些必要的步骤,向该工程添加自动化对象。
C.1.1 使用自动化对象向导
现在打开原来的SQLBuilder工程,开始向它添加自动化对象。打开工程后,在Delphi中单击File | New | Other菜单项。然后在New Items对话框中选择ActiveX属性页,并选择其中的Automation Object图标(如图C.1所示)。这里使用的CoClass名为Builder,并采用了缺省的实例化和线程模型(关于实例化和线程模型的特定细节,请参考第15章)。单击OK生成类型库并触发类型库编辑器。
我们将使用类型库编辑器来定义自动化服务器的接口,并更新类型库和包含TBuilder类的Pascal源文件,该类是服务器的包裹类。
C.1.2 在类型库编辑器中定义接口
在向导中单击OK后,将定义一个包裹类,该类继承了TAutoObject类,并实现了IBuilder接口。在光盘上,相应的文件名是UBuilder.pas。当结束自动化接口定义后,类型库编辑器(见图C.2)中的Refresh按钮将把声明和定义添加到Pascal包裹类的单元。
图C.1 自动化对象向导小应用程序
图C.2 类型库编辑器以及TBuilder类的定义
对于该自动化对象,我们将定义一些方法和特性,以便设置数据库名和表名,获取数据库名和表名的列表,以及字段和SQL文本的管理等。按下列步骤,可以在类型库编辑器中定义接口。
1. 在类型库编辑器中,单击New Property工具栏按钮(或右键单击IBuilder接口,从接口的上下文菜单中选择该菜单项,参考图C.2)添加DatabaseName特性。
2. 在Attributes属性页上(在图C.2中也可以看到),将Name修改为DatabaseName,将类型修改为BSTR,对读写定义二者都进行同样的修改。
3. 对TableName特性重复步骤2。这两个特性将用于修改选定的数据库名和表名。
4. 添加两个方法(单击Methods工具栏按钮),将其命名为GetTableNames和GetDatabaseNames。TStrings对象可返回分隔好的文本,因此我们可以利用TStrings的Text特性将所有的列表项作为一个字符串传递。
5. 在Parameters属性页上将Type设置为BSTR*,这是指向BSTR,即宽字符串的指针,并将Modifier修改为[out,retval]。BSTR*指针用于返回类型。现在我们只用一个方法调用就可以得到所有的数据库名和方法名。
6. 重复步骤5,定义GetFieldsList和GetSQLText方法。它们在自动化服务器中都存储为TStrings,因此声明步骤与5相同(看一下SQLBuilder工程,可以发现字段在TValueListEditor中,而SQL文本在TMemo对象中。两个控件都有TStrings特性)。
7. 将SetFieldsList在Parameters属性页上定义为[in] BSTR。该方法可用于更新对字段和值的列表的修改。
8. 添加一个可用于读写的QueryType特性,它使用整数来修改查询类型,在服务器中用TRadioGroup来表示。
9. 最后,定义方法TestQuery,它没有参数。我们将使用TestQuery对服务器进行查询。
10. 单击Refresh Implementation工具栏按钮。最后的结果是在包含TBuilder类的源代码单元中生成的声明和定义。
如果一切正常,类声明如下:
TBuilder = class(TAutoObject, IBuilder)
protected
function Get_DatabaseName: WideString; safecall;
function Get_TableName: WideString; safecall;
function GetDatabaseNames: WideString; safecall;
function GetFieldsList: WideString; safecall;
function GetSQLText: WideString; safecall;
function GetTableNames: WideString; safecall;
procedure Set_DatabaseName(const Value: WideString);
safecall;
procedure Set_TableName(const Value: WideString); safecall;
procedure SetFieldsList(const FieldsList: WideString);
safecall;
procedure TestQuery; safecall;
function Get_QueryType: SYSINT; safecall;
procedure Set_QueryType(Value: SYSINT); safecall;
{ Protected declarations }
end;
这里没有什么真正值得惊奇的东西。看一下数据类型,可以看到都已经转换成了Delphi中的别名。过程传递常量参数而函数则返回适当的类型(还会生成.tlb文件,但通常应该避免直接修改该文件)。下一步是实现接口。因为工作已经在程序中完成,每个接口的实现都很简单。
C.1.3 实现接口
大多数方法都只对服务器程序主窗体的简单文本进行操作,或者是每个特定控件所包含的TStrings特性。完整的实现代码如下,几乎不需要什么解释。
uses ComServ, UFormMain;
function TBuilder.Get_DatabaseName: WideString;
begin
result := Form1.ComboBox1.Text;
end;
function TBuilder.Get_TableName: WideString;
begin
result := Form1.ComboBox2.Text;
end;
function TBuilder.GetDatabaseNames: WideString;
begin
result := Form1.ComboBox1.Items.Text;
end;
function TBuilder.GetFieldsList: WideString;
begin
result := Form1.ValueListEditor1.Strings.Text;
end;
function TBuilder.GetSQLText: WideString;
begin
result := Form1.Memo1.Lines.Text;
end;
function TBuilder.GetTableNames: WideString;
begin
result := Form1.ComboBox2.Items.Text;
end;
procedure TBuilder.Set_DatabaseName(const Value: WideString);
begin
Form1.ComboBox1.Text := Value;
Form1.ComboBox1Change(Self);
end;
procedure TBuilder.Set_TableName(const Value: WideString);
begin
Form1.ComboBox2.Text := Value;
Form1.ComboBox2Change(Self);
end;
procedure TBuilder.SetFieldsList(const FieldsList: WideString);
begin
Form1.ValueListEditor1.Strings.Text := FieldsList;
end;
procedure TBuilder.TestQuery;
begin
Form1.RunSQL;
end;
function TBuilder.Get_QueryType: SYSINT;
begin
result := Form1.RadioGroup1.ItemIndex;
end;
procedure TBuilder.Set_QueryType(Value: SYSINT);
begin
Form1.RadioGroup1.ItemIndex := Value;
end;
从代码显然可以看出,每个方法的实现都只有一两行代码。例如,稍复杂的是Set_TableName方法,它是TableName特性的写方法。该方法直接把表名赋值给TComboBox.Items.Text特性,然后手工调用相关联的OnChange事件处理程序;该处理程序确保用新表的字段定义列表来更新TValueFieldList控件。
在具有工业水准的程序中,需要进行一些检查。例如,当设置TableName特性时,需要验证要设置的表名是有效的。虽然组合框已经提供了该功能并能够自动生成异常,但最好在客户端捕获异常并让用户重新选择另一个表。
C.1.4 运行服务器来注册类型库
在使用服务器之前,需要注册类型库。最简单的注册方法是将服务器作为单独的程序运行。也可以使用类型库编辑器中的Register Type Library工具栏按钮进行注册。
注意:Microsoft正致力在.NET框架中减少注册类型库的需要。这可是件好事情。
C.2 建立测试程序
在服务器程序中建立的自动化对象继承自TOleServer,它是TComponent的子类。定义了一个Register方法,这样可以方便地注册服务器组件,使得该组件易于使用。要把该自动化服务器安装到VCL中,可以选择Project | Import Type Library菜单项并单击Add按钮。找到服务器的.tlb文件,然后单击Open按钮。在Import Type Library对话框中,单击Install按钮。Install按钮将提示您选择一个包。对于我们的目的来说,dclusr60.bpl包是很方便的。建立该包。现在您就可以建立测试程序了。
任何程序都可以用于测试该服务器,关键在于要创建服务器的实例,然后调用方法并访问特性以确保一切都工作正确。下面的步骤提供了测试新的组件化服务器的一种可能的途径。
1. 缺省情况下,自动化服务器组件安装到组件面板的Servers属性页,除非修改自动生成的Register过程。
2. 在Delphi中,创建新的应用程序并将TBuilder组件拖放到主窗体上。
3. 在FormCreate事件方法中连接到服务器。例如,如果使用了缺省的名字Builder1,那么可以使用Builder1.Connect来启动自动化服务器程序。
4. 最后,添加代码来测试服务器。测试代码如下,对SQLBuilder自动化服务器的各项功能进行了简要测试。
Screen.Cursor := crHourglass;
try
try
Form2.Enabled := False;
Memo1.Lines.Text := Builder1.GetDatabaseNames;
Sleep( 2000 );
Builder1.DatabaseName := 'DBDEMOS';
Sleep( 1000 );
Memo1.Lines.Text := Builder1.GetTableNames;
Sleep(1000);
Builder1.QueryType := 0;
Builder1.TableName := 'customer.db';
Memo1.Lines.Text := Builder1.GetSQLText;
Builder1.TestQuery;
finally
Form2.Enabled := True;
end;
finally
Screen.Cursor := crDefault;
end;
将上述代码添加到响应鼠标单击的事件方法中,从服务器获取的数据则显示在TMemo控件中。代码中调用了Sleep暂停了一下,这样可以使用户跟上程序的响应。代码非常直观:上面列出的代码改变了DatabaseName、TableName,并请求所生成的SQL文本。最后调用了TestQuery方法。如果运行示例程序(定义在TestAuto.dpr中),可以先找到SQLBuilder程序的Data属性页,看一下从DBDEMOS数据库返回的顾客数据。
代码的其余部分用处不大。有用的代码不见得冗长。在掌握用法之后,TStrings对象是非常简单的;从示例可以看出,文本就存储在TStrings对象中。在设计类和服务器时保持小而扼要的接口,就能达到这种简单性。
C.3 小 结
如果您读到了这里,说明您已经顺利地创建并测试了服务器程序。Delphi通过隐藏类型库代码和IDL,使得创建和使用COM对象,如自动化服务器,变得相对容易。创建自动化服务器的实例也很容易,因为Delphi可以使自动化服务器组件化。如果把服务器变成组件,那么无论自己或他人使用,都会非常方便。
要记住,自动化是一种手段,您可以通过它在开发工具之间共享应用程序。在不久的将来,自动化将成为最大化的实现工具的重用的一条好途径。