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) を調べる必要があります.
参考文献
コメント