COM 原理和编程(1) - -- Automation 的应用之调试工具 TraceLog

在现在这个属于 WebService 的时代里, COM 已经快要变成一项过时技术了, 但过时不表示没用,即使在.NET中,COM仍有其用武之地。

    本文将以一个实用的例子程序来说明 Automation 程序的编写。我在《用 C++Builder 写 COM 版的 Hello world!》一文中也是以 Automation 为例来说明 COM 的,有人便会问我,你不是说写 COM 的吗?怎么又做 Automation 呢?我在这里解释一下: Automation 是一类 COM 程序,也是最常用的 COM 程序类型;基本的 COM 是从 IUnknown 接口派生的,而 Automation 是从 IDispatch 接口派生的;像 Word Excel 等都是 Automation , ADO 也是一种 Automation 。
    如题目所说,本例子程序(TraceLog)是一个调试工具,它虽然很简单,但是可以辅助进行一些不方便用开发工具进行调试的情况,如 Windows Service 或 ISAPI 等。
    TraceLog 的功能是:以一个 Automation 服务运行,并且注册到 ROT (运行对象表)中,因为只有注册到 ROT 中的 COM 对象才能被 GetActiveObject 取得;被调试的程序在初始化时用 GetActiveObject 取得 TraceLog 的接口,在被调试程序需要显示调试信息(如显示变量的值等)的地方加入一行代码,调用 TraceLog 的成员方法 AddLog 将信息输出到 TraceLog ,即可在 TraceLog 中显示出来。

    TraceLog 的编写:
1.New|Application ,然后放上几个控件,如下图:

其中一片空白的是一个 TMemo 控件;
2.SaveAll , Unit1 命名为: Main , Project1 命名为: TraceLog ;
3.New|ActiveX|Automation Object ,如下图;

4.新建一个名为 TraceLogObject 的 Automation 对象,如下图:

5.从 View|Type Library 打开 TypeLib Editor ,新增一个接口方法,如下图中红圈所示:

方法名为: AddLog ,带一个 BSTR 类型的输入参数。如下图:

6.单击刷新按钮(如上图红圈所示)后,打开 Unit1 单元,保存为 TrLogObj ,在其 Interface 部分的 Uses 中加入 SysUtils 和在 Implementation 部分的 Uses 中加入 Main 单元,然后找到 AddLog 的实现部分,编写代码如下:

procedure TTraceLogObject.AddLog(const aMessage: WideString);
begin
MainForm.memoLog.Lines.Insert( 0, FormatDateTime( 'hh":"nn":"ss":"zzz', Time )
+ aMessage );
end;

7.增加注册到 ROT 的功能:首先在 Interface 部分的 Uses 中加入 ActiveX, ComObj, TrLogObj 单元,增加两个私有成员变量:

  private
{ Private declarations }
FTrLogObj : TTraceLogObject;
FRegInfo : Integer;

8.在 Implementation 部分的 Uses 中加入 TraceLog_TLB 单元,响应主窗体的 OnCreate 和 OnCloseQuery 事件:

procedure TMainForm.FormCreate(Sender: TObject);
begin
FTrLogObj := TTraceLogObject.Create;
OleCheck( RegisterActiveObject( FTrLogObj, CLASS_TraceLogObject,
ACTIVEOBJECT_STRONG, FRegInfo ) );
If ( FileExists( ChangeFileExt( Application.ExeName, '.txt' ) ) ) Then
memoLog.Lines.LoadFromFile( ChangeFileExt( Application.ExeName, '.txt' ) ) );
end;


procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
OleCheck( RevokeActiveObject( FRegInfo, Nil ) );
If ( ( FTrLogObj.RefCount > 0 )
AND ( Application.MessageBox( '还有其它程序在使用本程序,你确定要退出吗?', '警告',
MB_YESNO + MB_ICONWARNING ) <> IDYES ) ) Then
Begin
OleCheck( RegisterActiveObject( FTrLogObj, CLASS_TraceLogObject,
ACTIVEOBJECT_STRONG, FRegInfo ) );
CanClose := false;
End;
If ( CanClose ) Then
memoLog.Lines.SaveToFile( ChangeFileExt( Application.ExeName, '.txt' ) );
end;

本来不需要使用对象类型,只要使用接口类型即可,那样还简单一些,但因为需要在退出时检查引用计数,所以才用了对象类型。这样当程序退出时,如果还有其它程序在引用本程序,就会弹出提示。
9.实现主窗体的两个按钮响应:

procedure TMainForm.btnClearClick(Sender: TObject);
begin
memoLog.Lines.Clear;
end;

procedure TMainForm.btnSaveasClick(Sender: TObject);
begin
If ( SaveDialog1.Execute ) Then
memoLog.Lines.SaveToFile( SaveDialog1.FileName );
end;

TraceLog 的使用:
1、在被调试程序中 Uses 两个单元: ComObj, TraceLog_TLB ;
2、定义一个全局变量: F_Debugger : ITraceLogObject;
3、定义一个全局过程:

Procedure AddLog( aMessage : String );
Begin
If ( Assigned( F_Debugger ) ) Then
F_Debugger.AddLog( aMessage );
End;

4、在被调试程序初始化时取得 TraceLog 对象的接口,如下:

procedure TForm1.FormCreate(Sender: TObject);
begin
Try
F_Debugger := GetActiveOleObject( 'TraceLog.TraceLogObject' )
As ITraceLogObject;
Except
F_Debugger := Nil;
End;
end;
如有必要,也可以在 TraceLog 未运行时用 CreateOleObject 创建一个实例。
5、以后只要在要输出调试信息地方用全局过程 AddLog 输出即可。