Delphi で CreateOleObject のイベント処理
Delphi の CreateOleObject で呼び出したオートメーションサーバのイベント処理
タイプライブラリから .pas を生成しろというコメントが散見されるほど、資料が少なく面倒でした.該当する GUID や DispID を調べる必要があります
ScriptControl を例に、TScriptControlEventSink クラスを作成して、OnError と OnTimeout イベントを関連付けしています
CreateOleObject と TScriptControlEventSink を利用したイベント追加
- var
- ScriptControl: OleVariant;
- ScriptControlEventSink: TScriptControlEventSink;
- begin
- ScriptControl := CreateOleObject('ScriptControl');
- try
- ScriptControl.Language := 'VBScript';
- ScriptControl.Timeout := 2000;
- ScriptControl.AllowUI := False;
- // Event
- ScriptControlEventSink := TScriptControlEventSink.Create(ScriptControl);
- try
- ScriptControlEventSink.OnError := Form1.OnScriptError;
- ScriptControlEventSink.OnTimeout := Form1.OnScriptTimeout;
- // ScriptControl.Run(...);
- finally
- ScriptControlEventSink.Free;
- end;
- finally
- ScriptControl := Unassigned;
- end;
- end;
ScriptControlEventSync.pas
- unit ScriptControlEventSync;
- interface
- uses
- ActiveX, Windows, ComObj, SysUtils, Classes;
- type
- IScriptControlEvents = interface(IDispatch)
- ['{8B167D60-8605-11D0-ABCB-00A0C90FFFC0}']
- // procedure Error; safecall;
- // procedure Timeout; safecall;
- end;
- TScriptControlEventSink = class(TInterfacedObject, IUnknown, IDispatch)
- private
- FDispatch: IDispatch;
- FIID: TGUID;
- FConnection: integer;
- FOnError, FOnTimeout: TNotifyEvent;
- protected
- function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
- function _AddRef: integer; stdcall;
- function _Release: integer; stdcall;
- function GetTypeInfoCount(out Count: integer): HResult; stdcall;
- function GetTypeInfo(Index, LocaleID: integer; out TypeInfo): HResult; stdcall;
- function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: integer; DispIDs: Pointer): HResult; stdcall;
- function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word;
- var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
- public
- constructor Create(ADispatch: IDispatch);
- destructor Destroy; override;
- property OwnerDispatch: IDispatch read FDispatch;
- property OnError: TNotifyEvent read FOnError write FOnError;
- property OnTimeout: TNotifyEvent read FOnTimeout write FOnTimeout;
- end;
- implementation
- { TScriptControlEventSink }
- function TScriptControlEventSink.QueryInterface(const IID: TGUID; out Obj): HResult;
- begin
- Result := E_NOINTERFACE;
- if GetInterface(IID, Obj) then begin
- Result := S_OK;
- end;
- if IsEqualGUID(IID, FIID) and GetInterface(IDispatch, Obj) then begin
- Result := S_OK;
- end;
- end;
- function TScriptControlEventSink._AddRef: integer;
- begin
- Result := 2;
- end;
- function TScriptControlEventSink._Release: integer;
- begin
- Result := 1;
- end;
- function TScriptControlEventSink.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: integer; DispIDs: Pointer): HResult;
- begin
- Result := E_NOTIMPL;
- end;
- function TScriptControlEventSink.GetTypeInfo(Index, LocaleID: integer; out TypeInfo): HResult;
- begin
- Result := E_NOTIMPL;
- end;
- function TScriptControlEventSink.GetTypeInfoCount(out Count: integer): HResult;
- begin
- Count := 0;
- Result := S_OK;
- end;
- constructor TScriptControlEventSink.Create(ADispatch: IDispatch);
- var
- ConnectionPointContainer: IConnectionPointContainer;
- ConnectionPoint: IConnectionPoint;
- begin
- inherited Create;
- FDispatch := ADispatch;
- FIID := IScriptControlEvents;
- // InterfaceConnect(FDispatch, FIID, Self, FConnection);
- OleCheck(FDispatch.QueryInterface(IConnectionPointContainer, ConnectionPointContainer));
- OleCheck(ConnectionPointContainer.FindConnectionPoint(FIID, ConnectionPoint));
- OleCheck(ConnectionPoint.Advise(Self, FConnection));
- end;
- destructor TScriptControlEventSink.Destroy;
- begin
- InterfaceDisconnect(FDispatch, FIID, FConnection);
- inherited Destroy;
- end;
- function TScriptControlEventSink.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
- Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult;
- begin
- case DispID of
- 3000:
- if Assigned(FOnError) then begin
- FOnError(Self);
- end;
- 3001:
- if Assigned(FOnTimeout) then begin
- FOnTimeout(Self);
- end;
- end;
- Result := S_OK;
- end;
- end.
EventSink 作成に必要なもの
サーバごとの イベント GUIDと各イベント名(無くても動きます)
(IScriptControlEvents で使用)
各イベントに割り当てられた DispID
(Invokeメソッドで使用)
これらの情報は、ScriptControl の場合、タイプライブラリから生成した MSScriptControl_TLB.pas のイベントのみが記載された部分にあります
イベントの Sender
基本的に イベント発生時の Sender は任意で、元のフォームでもよいです.
上記例ではイベント発生時の Sender を Self = TScriptControlEventSink として、
TScriptControlEventSink.OwnerDispatch で元の Dispatchインターフェイスを呼び出せるようにしてます.イベント関数から元の ScriptControl インターフェイスへ容易に接続できるのが目的です.
- procedure TForm1.OnScriptTimeout(Sender: TObject);
- var Timeout: Integer;
- begin
- Timeout := OleVariant(TScriptControlEventSink(Sender).OwnerDispatch).Timeout;
- MsgDlg(Format('Script Timeout: %d msec', [Timeout]));
- end;
引数のあるイベント
上記例では引数のないイベントのため、System.Classes.TNotifyEvent を使用していますが、引数がある場合、イベントの引数定義とInvokeでパラメータ処理が必要になります → 参考
InterfaceConnect の利用
System.Win.ComObj.InterfaceConnect は、QueryInterface, FindConnectionPoint, Advise を一度に処理できる便利な関数ですが、GUIDやイベント名が正しくない場合にエラーとして上がってきません.
処理が完了したかどうかは、最後の引数(Connection <> 0) を調べる必要があります.
参考文献
コメント