《关于VisiBroker For Delphi的使用》
-CORBA技术实践(一)
宜昌市中心人民医院 赵普昉
email: 3boy@sohu.com
4、实例设计与分析
上一次我想大家介绍了一个在CORBA中使用Pooler的小程序,这段代码中使用了较为高级的编写方法,在分析这个实例之前,我想和大家谈一点设计工具的应用,当然我会在本文的后续文章中深入探讨关于这些辅助手段的应用,在这里我只想简要的描述一下,为正真的工程项目中实现CORBA技术做好相应的知识储备。
前面我介绍了接口描述语言(IDL)的一些较为基础语言要素,如果只是在Delphi中开发实际的基于VisiBroker 的ORB多层应用体系的基本应用可以大胆的说已经够用了(如果有人反对我不介意),CORBA是一一个不断补充发展的标准,而且每种ORB产品又会对CORBA进行自己的描述,所以前面的一些IDL只是有一部分和OMG的定义有一定的区别,但是这不会影响到我们理解CORBA,当然必须说明的是前面的IDL描述都是完全符合VisiBroker的规范的。在C/S模式的程序设计之中,我们中绝大部分人是不会去考虑接口设计的,偶尔的出现也不会是采用我们开发多层系统这样的模式,所以刚刚接触多层系统的程序员要付出比C/S模式下设计大的多地精力来设计它,而且会拖延开发的周期,我一直在思考一个问题能不能避免这样的事情呢?或者说去谋求一个好的方法来改进我们的设计呢?在我看来拖延工期的问题主要表现在我们对于接口的确定上,接口的不确定因素体现在增加接口,修改接口,废止接口等等诸多问题上,设计接口我觉的类似与我们在设计ER图中的实体间的关系一样,如何平衡【服务――-中介―――客户】三者之间的关系是多层的首要问题,也是使得我们反复的罪魁祸首,在这里我想向大家推荐强大的Rational家族系列产品(Rose,SODA,ClearCase…..)我们可以使用这样的一些辅助工具来缩短我们的反复频率,在问题的分解中明确接口的设计方向与设计原则,对于拥有微软企业版开发套间的朋友可以利用Virtual Moduler工具来设计(这个工具是微软捆绑的ROSE产品,是一个标准的多层体系)在这里我不对如何使用他们来设计CORBA的接口进行描述,原因只有一个这是一个高级课题不属于<VisiBroker for Delphi>的范畴之中,我会在《面向CORBA的设计实践》中与大家共同探讨。
好了闲话就聊到这里,下面开始正体。
记得我在本文的第一小节中提到过CORBA DataMoudel Object,如果使用过这个对象的朋友一定清楚的了解单线程模式与多线程模式,也就是CORBA 3.3为大家提供了两种来至于TDataMoudel对象,进行的整体的封装,然而现在的CRB4.0为程序员提供了更大的灵活性
让我们自己来编写这样的两种不同的对象,显然后者可以让我们更广泛的扩展应用面,上次的例程就是一个多线程模式的例子,在讲解多线程模式之前我想还是让我们来看一下单线层模式的实例:
/*IDL*/
module TypeLibCRB
{
interface InterfaceCRB;
interface InterfaceCRB
{
any GetSQLData(in wstring Script, in boolean ExecutOrActive);
};
//定义一个发送客户执行的SQL,返回一个any类型
};
//XXX_Impl.pas
interface
uses
SysUtils,
CORBA,
TypeLibCRB_i,
TypeLibCRB_c
;
type
TInterfaceCRB = class;
TInterfaceCRB = class(TInterfacedObject, TypeLibCRB_i.InterfaceCRB)
protected
public
constructor Create;
function GetSQLData ( const Script : WideString;
const ExecutOrActive : Boolean): Any;
end;
implementation
uses UmCRBServer, UnCRBServer;
constructor TInterfaceCRB.Create;
begin
inherited;
end;
function TInterfaceCRB.GetSQLData ( const Script : WideString;
const ExecutOrActive : Boolean): Any;
begin
Form1.Memo1.Lines.Text:=Script;
Result:=DataMCRB.GetData(Script,ExecutOrActive);
//调用TDataMoudel实例的方法请注意这个地方只有一个TDataMoudel实例
//下面的程序中会描述这个实例的方法定义
end;
initialization
end.
/********************/
/*动态创建数据实例的操作*/
unit UmCRBServer;
interface
uses
SysUtils, Classes, DB, DBTables,Provider;
type
TDataMCRB = class(TDataModule)
DatabCRB: TDatabase;
QueryCRB: TQuery;
DataSetPCRB: TDataSetProvider;
private
{ Private declarations }
public
function GetData(Script:String;ExecuteOrActicve:boolean):OleVariant; { Public declarations }
end;
var
DataMCRB: TDataMCRB;
function RunSQL(QueryX:TQuery;Script:String;ExecuteOrActive:boolean):boolean;
implementation
//这个代码中没有使用Session,也没有对事务进行控制,下一章节会给出一个使用Session的//例程,想一想这个方法的应用会有什么有点和缺点,如果愿意谈一谈請将您的想法发往我得//信箱
{$R *.dfm}
function RunSQL(QueryX:TQuery;Script:String;ExecuteOrActive:boolean):boolean;
begin
Result:=false;
with QueryX do
begin
if Active then Active:=false;
SQL.Clear;
SQL.Text:=Script;
if ExecuteOrActive then
ExecSQL else
Active:=true;
if not IsEmpty then
Result:=true;
end;
end;
//这段代码我不讲解
{ TDataMCRB }
function TDataMCRB.GetData(Script: String;
ExecuteOrActicve: boolean):OleVariant;
var
MyQry:TQuery;
MyPrv:TDataSetProvider;
begin
try
MyQry:=TQuery.Create(self);
MyPrv:=TDataSetProvider.Create(self);
MyQry.DatabaseName:=DatabCRB.DatabaseName;
//注意如果使用TDataBase对象請将ShareHandle设为True;
MyPrv.DataSet:=MyQry;
if RunSQL(MyQry,Script,ExecuteOrActicve) then
Result:=MyPrv.DATA;
finally
MyQry.Free;
MyQry:=nil;
MyPrv.Free;
MyPrv:=nil;
end;
end;
//动态创建数据集和数据提供者对象,在调用接口将数据返回给Client后释放动态创建的两//对象。这样可以避免在单线模式下客户端持久的拥有接口后的对象。
end.
/*****************************************/
/初始化CORBA实例,主单元
unit UnCRBServer;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Corba, TypeLibCRB_c, TypeLibCRB_i, TypeLibCRB_impl, TypeLibCRB_s,
StdCtrls;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ private declarations }
protected
{ protected declarations }
MyCRB:InterfaceCRB;
procedure InitCorba;
public
{ public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.InitCorba;
begin
CorbaInitialize;
MyCRB:=TInterfaceCRBSkeleton.Create('MyCRBServer',TInterfaceCRB.Create);
BOA.ObjIsReady(MyCRB as _Object);//CORBA服务就绪
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
InitCorba;//初始化调入
end;
end.
/******************************************************/
/客户端程序
nit UnClientMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Corba, TypeLibCRB_c, TypeLibCRB_i, Grids, DBGrids, DB, DBClient, StdCtrls;
type
TForm2 = class(TForm)
Edit1: TEdit;
Button1: TButton;
ClientDataSet1: TClientDataSet;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ private declarations }
protected
/ myCRB:InterfaceCRB;
{ protected declarations }
public
{ public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.DFM}
procedure TForm2.Button1Click(Sender: TObject);
begin
ClientDataSet1.Data:=myCRB.GetSQLData(Edit1.Text,false);
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
myCRB:=TInterfaceCRBHelper.Bind('MyCRBServer','127.0.0.1');
end;
end.
上面的程序让你无法使用ApplyUpdate方法来从客户端更新数据,如果要更新数据必须通过GetSQLData(SQL,True)来更新数据,相对来说客户端将不能使用数据感知类组件来编写插入删除,修改类程序,因为你的中间层的数据集与数据提供者不具有持久性,这就是下面代码使用DMPooler的精妙之处。
//
TDataModuleClass = class of TDataModule; // "class" reference
首先,由于客户请求数目的不确定性,我们必须为可能的请求提供一个用于存放独立实例请求单元的空间量(Pooler Size),假定我们考虑的可能有5个独立请求同时拥有这样持久对象,那么我们可以设定PoolSize=5,注意必须定义在全局的CONST中,因为每个对象是一个独立体,不过里也可以将这样的对象列入到TLIST对象中保存,当然还有更高级的构造方式,这些东西来源于程序员对Delphi的理解程度,例如:你可以将全部的实例使用TCollection类化处理,而不是使用下面的记录类型:
TPooledModule = record
Module: TDataModule;
InUse: Boolean;
end;
当然这个地方使用的比较基础的方式来处理每一个ITEM,这个单元中包含了一个TDataModule实例,何一个表示这个实例是否被起用的开关量(我是这么命名的,当然这仅是基本的表达,而根据业务需求的规则需要自己来定义比此更好的比编写方式,而这个东西同样来源于对于OO概念的理解,和面向对象编程的经验的积累).
当然,由于我们的客户对象的相互独立并确是一个持久的对象,那么也就是告诉我们整个对象池,是一个多线程的架构(如果是初次接触的朋友们最好先去读一下Thread).所以我们必须在对象的构造方法中申明为
constructor TModulePooler.Create;
begin
IsMultiThread := True;
……
end;
如果请求被客户中断,或则实例由于异常而停止我们必须将这个实例终止,释放实例对象:
procedure TModulePooler.FreeModule(DataModule: TDataModule);
var
I: Integer;
Begin
………
for I := 0 to Length(FModules) - 1 do
if FModules[I].Module = DataModule then
FModules[I].InUse := False;
ReleaseSemaphore(FSemaphore, 1, nil);
……..
end;
那么,创建和释放的方法在我们写好已后,那么我们如何来构造这个单元的实体方法呢?
请看:
function TModulePooler.GetModule: TDataModule;
var
I: Integer;
begin
Result := nil;
if WaitForSingleObject(FSemaphore, 5000) = WAIT_TIMEOUT then
//设定延时响应,假定服务超时,那么我们将引发一个异常类,表示服务器太忙而未能响应请求。
raise Exception.Create('Server too busy');
try
if Length(FModules) = 0 then
//如果这个对象记录数组为空我们将创建这样一个动态的对象记录数组,即创建一个链表,每个对象作为其中的一个单元而存在,当然在实际的开发中我认为这种方法不是最好的,我会采用TStrings或则Tlist以及TCollections来存放单元,具体的使用我会在下一节给出
begin
SetLength(FModules, PoolSize);
for I := 0 to PoolSize - 1 do
begin
FModules[I].InUse := False;
FModules[I].Module := FModuleClass.Create(Application);
end;
end;
for I := 0 to Length(FModules) - 1 do
if not FModules[I].InUse then
begin
FModules[I].InUse := True;
Result := FModules[I].Module;
Break;
end;
finally
FCSect.Leave;
end;
//Check if we ran out of connections
if not Assigned(Result) then
raise Exception.Create('Pool is out of capacity');
end;
在加载时创建
initialization
ModulePooler := TModulePooler.Create;
退出时释放
finalization
ModulePooler.Free;
千万要记住检测对象是否存在,一定要在不再使用的时候释放持久对象,否则就会不断的堆砌,最终溢出哦!
可能是工作的原因,最近总是抽不出太多的时间来写这样的文章,在我看来已后基于 C/S 的软件将会越来越少,而绝大多数会聚集于微软的 COM+ 体系中,这倒不是说 COM+ 一定比 CORBA 优秀,而是 COM+ 不要钱,而仅仅是需要一套 WIN2000/XP, 当然一向已 C++ 为设计语言的朋友们可以参考很多的用 C++ 写的资料,而 Delphi 的朋友们的资料少的可怜,建议还是学一点 C++ ,我相信“真正的程序员用 C++ ,聪明的程序员用 Object Pascal ,而大师 [ 绝对优秀的程序师 ] 是没有语言障碍的“,“有条件的用 CORBA( 因为跨平台 ), 没有条件的用 COM+ (因为 CORBA 收费 ,COM+ 免费) ”.
下一节谈讨更高级的话题(对象定位技术与接口异常处理)