unit ScriptObjectUtilsWithRTTI;
interface
{$DEFINE COMOBJ_FROMDLL}
uses
{$IFDEF Use_External_TLB}
MSScriptControl_TLB,
{ $ENDIF }
System . ObjAuto,
System . Classes, System . RTTI, System . Variants,
Winapi . Windows, Winapi . ActiveX, System . TypInfo;
type
{$REGION 'MSScriptControl_TLB'}
{$IFDEF Use_External_TLB}
IScriptControl = MSScriptControl_TLB . IScriptControl;
{ $ELSE }
ScriptControlStates = TOleEnum;
IScriptModuleCollection = IDispatch;
IScriptError = IDispatch;
IScriptProcedureCollection = IDispatch;
IScriptControl = interface (IDispatch)
[ '{0E59F1D3-1FBE-11D0-8FF2-00A0D10038BC}' ]
function Get_Language: WideString ; safecall;
procedure Set_Language( const pbstrLanguage: WideString ); safecall;
function Get_State: ScriptControlStates; safecall;
procedure Set_State(pssState: ScriptControlStates); safecall;
procedure Set_SitehWnd(phwnd: Integer ); safecall;
function Get_SitehWnd: Integer ; safecall;
function Get_Timeout: Integer ; safecall;
procedure Set_Timeout(plMilleseconds: Integer ); safecall;
function Get_AllowUI: WordBool; safecall;
procedure Set_AllowUI(pfAllowUI: WordBool); safecall;
function Get_UseSafeSubset: WordBool; safecall;
procedure Set_UseSafeSubset(pfUseSafeSubset: WordBool); safecall;
function Get_Modules: IScriptModuleCollection; safecall;
function Get_Error: IScriptError; safecall;
function Get_CodeObject: IDispatch; safecall;
function Get_Procedures: IScriptProcedureCollection; safecall;
procedure _AboutBox; safecall;
procedure AddObject( const Name: WideString ; const Object_: IDispatch;
AddMembers: WordBool); safecall;
procedure Reset; safecall;
procedure AddCode( const Code: WideString ); safecall;
function Eval( const Expression: WideString ): OleVariant; safecall;
procedure ExecuteStatement( const Statement: WideString ); safecall;
function Run( const ProcedureName: WideString ; var Parameters: PSafeArray)
: OleVariant; safecall;
property Language: WideString read Get_Language write Set_Language;
property State: ScriptControlStates read Get_State write Set_State;
property SitehWnd: Integer read Get_SitehWnd write Set_SitehWnd;
property Timeout: Integer read Get_Timeout write Set_Timeout;
property AllowUI: WordBool read Get_AllowUI write Set_AllowUI;
property UseSafeSubset: WordBool read Get_UseSafeSubset
write Set_UseSafeSubset;
property Modules: IScriptModuleCollection read Get_Modules;
property Error: IScriptError read Get_Error;
property CodeObject: IDispatch read Get_CodeObject;
property Procedures: IScriptProcedureCollection read Get_Procedures;
end ;
{ $ENDIF }
{$ENDREGION 'MSScriptControl_TLB'}
TEventDispatch = class (TComponent)
private
FScriptControl: IScriptControl;
FScriptFuncName: string ;
FInternalDispatcher: TMethod;
FRttiContext: TRttiContext;
FRttiType: TRttiMethodType;
procedure InternalInvoke(Params: PParameters; StackSize: Integer );
function ValueToVariant(Value: TValue): Variant;
constructor Create(AOwner: TComponent; ATTypeInfo: PTypeInfo);
reintroduce; overload;
public
class function Create<T>(AOwner: TComponent; ScriptControl: IScriptControl;
ScriptFuncName: String ): T; reintroduce; overload;
destructor Destroy; override;
end ;
function CreateScriptControl(ScriptName: String = 'javascript' ): IScriptControl;
function SA(Obj: TObject; Owned: Boolean ): IDispatch; overload;
function SA(Obj: TObject): IDispatch; overload;
implementation
uses
{$IFNDEF COMOBJ_FROMDLL}
System . Win . ComObj,
{ $ENDIF }
System . SysUtils;
function CreateScriptControl(ScriptName: String ): IScriptControl;
const
CLASS_ScriptControl: TGUID = '{0E59F1D5-1FBE-11D0-8FF2-00A0D10038BC}' ;
{$IFDEF COMOBJ_FROMDLL}
MSSCRIPTMODULE = 'msscript.ocx' ;
var
DllGetClassObject: function ( const clsid, IID: TGUID; var Obj)
: HRESULT; stdcall;
ClassFactory: IClassFactory;
hLibInst: HMODULE;
hr: HRESULT;
begin
Result := nil ;
hLibInst := GetModuleHandle(MSSCRIPTMODULE);
if hLibInst = 0 then
hLibInst := LoadLibrary(MSSCRIPTMODULE);
if hLibInst = 0 then
Exit;
DllGetClassObject := GetProcAddress(hLibInst, 'DllGetClassObject' );
if Assigned(DllGetClassObject) then
begin
hr := DllGetClassObject(CLASS_ScriptControl, IClassFactory, ClassFactory);
if hr = S_OK then
begin
hr := ClassFactory . CreateInstance( nil , IScriptControl, Result);
if (hr = S_OK) and (Result <> nil ) then
Result . Language := ScriptName;
end ;
end ;
end ;
{ $ELSE }
begin
Result := CreateComObject(CLASS_ScriptControl) as IScriptControl;
if Result <> nil then
Result . Language := ScriptName;
end ;
{ $ENDIF }
type
TDispatchKind = (dkMethod, dkProperty, dkSubComponent);
TDispatchInfo = record
Instance: TObject;
case Kind: TDispatchKind of
dkMethod:
(MethodInfo: TRttiMethod);
dkProperty:
(PropInfo: TRttiProperty);
dkSubComponent:
(ComponentInfo: NativeInt);
end ;
TDispatchInfos = array of TDispatchInfo;
TScriptObjectAdapter = class (TInterfacedObject, IDispatch)
private
FRttiContext: TRttiContext;
FRttiType: TRttiType;
FDispatchInfoCount: Integer ;
FDispatchInfos: TDispatchInfos;
FComponentNames: TStrings;
FInstance: TObject;
FOwned: Boolean ;
function AllocDispID(AKind: TDispatchKind; Value: Pointer ;
AInstance: TObject): TDispID;
protected
property Instance: TObject read FInstance;
public
function GetIDsOfNames( const IID: TGUID; Names: Pointer ; NameCount: Integer ;
LocaleID: Integer ; DispIDs: Pointer ): HRESULT; virtual; stdcall;
function GetTypeInfo(Index: Integer ; LocaleID: Integer ; out TypeInfo)
: HRESULT; stdcall;
function GetTypeInfoCount(out Count: Integer ): HRESULT; stdcall;
function Invoke(DispID: Integer ; const IID: TGUID; LocaleID: Integer ;
Flags: Word ; var Params; VarResult: Pointer ; ExcepInfo: Pointer ;
ArgErr: Pointer ): HRESULT; virtual; stdcall;
public
constructor Create(Instance: TObject; Owned: Boolean = False );
destructor Destroy; override;
end ;
function SA(Obj: TObject; Owned: Boolean ): IDispatch;
begin
Result := TScriptObjectAdapter . Create(Obj, Owned);
end ;
function SA(Obj: TObject): IDispatch;
begin
Result := TScriptObjectAdapter . Create(Obj, False );
end ;
const
ofDispIDOffset = 100 ;
function TScriptObjectAdapter . AllocDispID(AKind: TDispatchKind; Value: Pointer ;
AInstance: TObject): TDispID;
var
I: Integer ;
dispatchInfo: TDispatchInfo;
begin
for I := FDispatchInfoCount - 1 downto 0 do
with FDispatchInfos[I] do
if (Kind = AKind) and (MethodInfo = Value) then
begin
Result := ofDispIDOffset + I;
Exit;
end ;
if FDispatchInfoCount = Length(FDispatchInfos) then
SetLength(FDispatchInfos, Length(FDispatchInfos) + 10 );
Result := ofDispIDOffset + FDispatchInfoCount;
with dispatchInfo do
begin
Instance := AInstance;
Kind := AKind;
MethodInfo := Value;
end ;
FDispatchInfos[FDispatchInfoCount] := dispatchInfo;
Inc(FDispatchInfoCount);
end ;
constructor TScriptObjectAdapter . Create(Instance: TObject; Owned: Boolean );
begin
inherited Create;
FComponentNames := TStringList . Create;
FInstance := Instance;
FOwned := Owned;
FRttiContext := TRttiContext . Create;
FRttiType := FRttiContext . GetType(FInstance . ClassType);
end ;
destructor TScriptObjectAdapter . Destroy;
begin
if FOwned then
FInstance . Free;
FRttiContext . Free;
FComponentNames . Free;
inherited Destroy;
end ;
function TScriptObjectAdapter . GetIDsOfNames( const IID: TGUID; Names: Pointer ;
NameCount, LocaleID: Integer ; DispIDs: Pointer ): HRESULT;
type
PNames = ^TNames;
TNames = array [ 0 .. 100 ] of POleStr;
PDispIDs = ^TDispIDs;
TDispIDs = array [ 0 .. 100 ] of Cardinal ;
var
Name: String ;
MethodInfo: TRttiMethod;
PropertInfo: TRttiProperty;
ComponentInfo: TComponent;
lDispId: TDispID;
begin
Result := S_OK;
lDispId := - 1 ;
Name := WideCharToString(PNames(Names)^[ 0 ]);
MethodInfo := FRttiType . GetMethod(Name);
if MethodInfo <> nil then
begin
lDispId := AllocDispID(dkMethod, MethodInfo, FInstance);
end
else
begin
PropertInfo := FRttiType . GetProperty(Name);
if PropertInfo <> nil then
begin
lDispId := AllocDispID(dkProperty, PropertInfo, FInstance);
end
else if FInstance is TComponent then
begin
ComponentInfo := TComponent(FInstance).FindComponent(Name);
if ComponentInfo <> nil then
begin
lDispId := AllocDispID(dkSubComponent, Pointer (FComponentNames . Add(Name)
), FInstance);
end ;
end ;
end ;
if lDispId >= ofDispIDOffset then
begin
Result := S_OK;
PDispIDs(DispIDs)^[ 0 ] := lDispId;
end ;
end ;
function TScriptObjectAdapter . GetTypeInfo(Index, LocaleID: Integer ;
out TypeInfo): HRESULT;
begin
Result := E_NOTIMPL;
end ;
function TScriptObjectAdapter . GetTypeInfoCount(out Count: Integer ): HRESULT;
begin
Result := E_NOTIMPL;
end ;
function TScriptObjectAdapter . Invoke(DispID: Integer ; const IID: TGUID;
LocaleID: Integer ; Flags: Word ; var Params;
VarResult, ExcepInfo, ArgErr: Pointer ): HRESULT;
type
PVariantArray = ^TVariantArray;
TVariantArray = array [ 0 .. 65535 ] of Variant;
PIntegerArray = ^TIntegerArray;
TIntegerArray = array [ 0 .. 65535 ] of Integer ;
var
Parms: PDispParams;
TempRet: Variant;
dispatchInfo: TDispatchInfo;
lParams: TArray<TValue>;
paramInfos: TArray<TRttiParameter>;
I: Integer ;
component: TComponent;
propertyValue: TValue;
_SetValue: NativeInt;
tmpv: Variant;
begin
Result := S_OK;
Parms := @Params;
try
if VarResult = nil then
VarResult := @TempRet;
if (DispID - ofDispIDOffset >= 0 ) and
(DispID - ofDispIDOffset < FDispatchInfoCount) then
begin
dispatchInfo := FDispatchInfos[DispID - ofDispIDOffset];
case dispatchInfo . Kind of
dkProperty:
begin
if Flags and (DISPATCH_PROPERTYPUTREF or DISPATCH_PROPERTYPUT) <> 0
then
if (Parms . cNamedArgs <> 1 ) or
(PIntegerArray(Parms . rgdispidNamedArgs)^[ 0 ] <>
DISPID_PROPERTYPUT) then
Result := DISP_E_MEMBERNOTFOUND
else
begin
propertyValue := TValue . Empty;
case dispatchInfo . PropInfo . PropertyType . Handle^.Kind of
tkInt64, tkInteger:
propertyValue :=
TValue . FromOrdinal
(dispatchInfo . PropInfo . PropertyType . Handle,
PVariantArray(Parms . rgvarg)^[ 0 ]);
tkFloat:
propertyValue := TValue . From< Extended >
(PVariantArray(Parms . rgvarg)^[ 0 ]);
tkString, tkUString, tkLString, tkWString:
propertyValue :=
TValue . From< String >(PVariantArray(Parms . rgvarg)^[ 0 ]);
tkSet:
begin
_SetValue := PVariantArray(Parms . rgvarg)^[ 0 ];
TValue . Make(_SetValue,
dispatchInfo . PropInfo . PropertyType . Handle,
propertyValue);
end ;
else
propertyValue :=
TValue . FromVariant(PVariantArray(Parms . rgvarg)^[ 0 ]);
end ;
dispatchInfo . PropInfo . SetValue(dispatchInfo . Instance,
propertyValue);
end
else if Parms . cArgs <> 0 then
Result := DISP_E_BADPARAMCOUNT
else if dispatchInfo . PropInfo . PropertyType . Handle^.Kind = tkClass
then
POleVariant(VarResult)^ :=
SA(dispatchInfo . PropInfo . GetValue(dispatchInfo . Instance)
.AsObject()) as IDispatch
else
POleVariant(VarResult)^ := dispatchInfo . PropInfo . GetValue
(dispatchInfo . Instance).AsVariant;
end ;
dkMethod:
begin
paramInfos := dispatchInfo . MethodInfo . GetParameters;
SetLength(lParams, Length(paramInfos));
for I := Low(paramInfos) to High(paramInfos) do
if I < Parms . cArgs then
begin
tmpv := PVariantArray(Parms . rgvarg)^[Parms . cArgs - 1 - I];
lParams[I] := TValue . FromVariant(tmpv);
end
else
begin
TValue . Make( 0 , paramInfos[I].ParamType . Handle, lParams[I]);
end ;
if (dispatchInfo . MethodInfo . ReturnType <> nil ) and
(dispatchInfo . MethodInfo . ReturnType . Handle^.Kind = tkClass) then
begin
POleVariant(VarResult)^ :=
SA(dispatchInfo . MethodInfo . Invoke(dispatchInfo . Instance,
lParams).AsObject()) as IDispatch;
end
else
begin
POleVariant(VarResult)^ := dispatchInfo . MethodInfo . Invoke
(dispatchInfo . Instance, lParams).AsVariant();
end ;
end ;
dkSubComponent:
begin
component := TComponent(dispatchInfo . Instance)
.FindComponent(FComponentNames[dispatchInfo . ComponentInfo]);
if component = nil then
Result := DISP_E_MEMBERNOTFOUND;
POleVariant(VarResult)^ := SA(component) as IDispatch;
end ;
end ;
end
else
Result := DISP_E_MEMBERNOTFOUND;
except
if ExcepInfo <> nil then
begin
FillChar(ExcepInfo^, SizeOf(TExcepInfo), 0 );
with TExcepInfo(ExcepInfo^) do
begin
bstrSource := StringToOleStr(ClassName);
if ExceptObject is Exception then
bstrDescription := StringToOleStr(Exception(ExceptObject).Message);
scode := E_FAIL;
end ;
end ;
Result := DISP_E_EXCEPTION;
end ;
end ;
class function TEventDispatch . Create<T>(AOwner: TComponent;
ScriptControl: IScriptControl; ScriptFuncName: String ): T;
type
PT = ^T;
var
ed: TEventDispatch;
begin
ed := TEventDispatch . Create(AOwner, TypeInfo(T));
ed . FScriptControl := ScriptControl;
ed . FScriptFuncName := ScriptFuncName;
Result := PT(@ed . FInternalDispatcher)^;
end ;
constructor TEventDispatch . Create(AOwner: TComponent; ATTypeInfo: PTypeInfo);
var
LRttiType: TRttiType;
begin
FRttiContext := TRttiContext . Create;
LRttiType := FRttiContext . GetType(ATTypeInfo);
if not (LRttiType is TRttiMethodType) then
begin
raise Exception . Create( 'T only is Method(Member function)!' );
end ;
FRttiType := TRttiMethodType(LRttiType);
Inherited Create(AOwner);
FInternalDispatcher := CreateMethodPointer(InternalInvoke,
GetTypeData(FRttiType . Handle));
end ;
destructor TEventDispatch . Destroy;
begin
ReleaseMethodPointer(FInternalDispatcher);
inherited Destroy;
end ;
function TEventDispatch . ValueToVariant(Value: TValue): Variant;
var
_SetValue: Int64Rec;
begin
Result := EmptyParam;
case Value . TypeInfo^.Kind of
tkClass:
Result := SA(Value . AsObject);
tkInteger:
Result := Value . AsInteger;
tkString, tkLString, tkChar, tkUString:
Result := Value . AsString;
tkSet:
begin
Value . ExtractRawData(@_SetValue);
case Value . DataSize of
1 :
Result := _SetValue . Bytes[ 0 ];
2 :
Result := _SetValue . Words[ 0 ];
4 :
Result := _SetValue . Cardinals[ 0 ];
8 :
Result := Int64 (_SetValue);
end ;
end ;
else
Result := Value . AsVariant;
end ;
end ;
function GetParamSize(TypeInfo: PTypeInfo): Integer ;
begin
if TypeInfo = nil then
Exit( 0 );
case TypeInfo^.Kind of
tkInteger, tkEnumeration, tkChar, tkWChar, tkSet:
case GetTypeData(TypeInfo)^.OrdType of
otSByte, otUByte:
Exit( 1 );
otSWord, otUWord:
Exit( 2 );
otSLong, otULong:
Exit( 4 );
else
Exit( 0 );
end ;
tkFloat:
case GetTypeData(TypeInfo)^.FloatType of
ftSingle:
Exit( 4 );
ftDouble:
Exit( 8 );
ftExtended:
Exit(SizeOf( Extended ));
ftComp:
Exit( 8 );
ftCurr:
Exit( 8 );
else
Exit( 0 );
end ;
tkClass, tkClassRef:
Exit(SizeOf( Pointer ));
tkInterface:
Exit(-SizeOf( Pointer ));
tkMethod:
Exit(SizeOf(TMethod));
tkInt64:
Exit( 8 );
tkDynArray, tkUString, tkLString, tkWString:
Exit(-SizeOf( Pointer ));
tkString:
Exit(GetTypeData(TypeInfo)^.MaxLength + 1 );
tkPointer:
Exit(SizeOf( Pointer ));
tkRecord:
if IsManaged(TypeInfo) then
Exit(-GetTypeData(TypeInfo)^.RecSize)
else
Exit(GetTypeData(TypeInfo)^.RecSize);
tkArray:
Exit(GetTypeData(TypeInfo)^.ArrayData . Size);
tkVariant:
Exit(-SizeOf(Variant));
else
Exit( 0 );
end ;
end ;
procedure TEventDispatch . InternalInvoke(Params: PParameters;
StackSize: Integer );
var
lRttiParameters, tmp: TArray<TRttiParameter>;
lRttiParam: TRttiParameter;
lParamValues: TArray<TValue>;
I, ParamSize: Integer ;
PStack: PByte;
test: string ;
ParamIsByRef: Boolean ;
RegParamIndexs: array [ 0 .. 2 ] of Byte ;
RegParamIndex: Integer ;
v, tmpv: Variant;
ParameterArray: PSafeArray;
begin
tmp := FRttiType . GetParameters;
SetLength(lRttiParameters, Length(tmp) + 1 );
lRttiParameters[ 0 ] := nil ;
for I := Low(tmp) to High(tmp) do
lRttiParameters[I + 1 ] := tmp[I];
SetLength(lParamValues, Length(lRttiParameters));
PStack := @Params . Stack[ 0 ];
if (FRttiType . CallingConvention = ccReg) then
begin
FillChar(RegParamIndexs, SizeOf(RegParamIndexs), - 1 );
RegParamIndexs[ 0 ] := 0 ;
RegParamIndex := 1 ;
for I := 1 to High(lRttiParameters) do
begin
lRttiParam := lRttiParameters[I];
ParamSize := GetParamSize(lRttiParam . ParamType . Handle);
ParamIsByRef := (lRttiParam <> nil ) and
(([pfVar, pfConst, pfOut] * lRttiParam . Flags) <> []);
if ((ParamSize <= SizeOf( Pointer )) and
( not (lRttiParam . ParamType . Handle . Kind in [tkFloat]))) or (ParamIsByRef)
then
begin
RegParamIndexs[RegParamIndex] := I;
if (RegParamIndex = High(RegParamIndexs)) or (I = High(lRttiParameters))
then
Break;
Inc(RegParamIndex);
end ;
end ;
for I := High(lRttiParameters) downto Low(lRttiParameters) do
begin
lRttiParam := lRttiParameters[I];
if I = 0 then
TValue . Make(Params . EAXRegister, TypeInfo(TObject), lParamValues[I])
else
begin
ParamIsByRef := (lRttiParam <> nil ) and
(([pfVar, pfConst, pfOut] * lRttiParam . Flags) <> []);
ParamSize := GetParamSize(lRttiParam . ParamType . Handle);
if (ParamSize < SizeOf( Pointer )) or (ParamIsByRef) then
ParamSize := SizeOf( Pointer );
if (I in [RegParamIndexs[ 0 ], RegParamIndexs[ 1 ], RegParamIndexs[ 2 ]]) then
begin
if ParamIsByRef then
begin
TValue . Make( Pointer (Params . Registers[RegParamIndex]),
lRttiParameters[I].ParamType . Handle, lParamValues[I]);
end
else
begin
TValue . Make(Params . Registers[RegParamIndex],
lRttiParameters[I].ParamType . Handle, lParamValues[I]);
end ;
Dec(RegParamIndex);
end
else
begin
if ParamIsByRef then
TValue . Make(PPointer(PStack)^, lRttiParameters[I].ParamType . Handle,
lParamValues[I])
else
TValue . Make(PStack, lRttiParameters[I].ParamType . Handle,
lParamValues[I]);
Inc(PStack, ParamSize);
end ;
end ;
end ;
end
else
begin
for I := Low(lRttiParameters) to High(lRttiParameters) do
begin
ParamIsByRef := (lRttiParameters[I] <> nil ) and
(([pfVar, pfConst, pfOut] * lRttiParameters[I].Flags) <> []);
if I = 0 then
begin
ParamSize := SizeOf(TObject);
TValue . Make(PStack, TypeInfo(TObject), lParamValues[I]);
end
else
begin
ParamSize := GetParamSize(lRttiParameters[I].ParamType . Handle);
if ParamSize < SizeOf( Pointer ) then
ParamSize := SizeOf( Pointer );
if ParamIsByRef then
TValue . Make(PPointer(PStack)^, lRttiParameters[I].ParamType . Handle,
lParamValues[I])
else
TValue . Make(PStack, lRttiParameters[I].ParamType . Handle,
lParamValues[I]);
end ;
Inc(PStack, ParamSize);
end ;
end ;
if (FScriptControl <> nil ) and (FScriptFuncName <> '' ) then
begin
v := VarArrayCreate([ 0 , Length(lParamValues) - 1 ], varVariant);
for I := 1 to Length(lParamValues) - 1 do
begin
test := lRttiParameters[I].Name;
tmpv := ValueToVariant(lParamValues[I]);
v[I - 1 ] := tmpv;
end ;
ParameterArray := PSafeArray(TVarData(v).VArray);
FScriptControl . Run(FScriptFuncName, ParameterArray);
end ;
end ;
|