Delphi2010以后增加了新的RTTI信息,也就是通过RTTI可以在运行时获取/调用对象的公开成员或者函数.
ScriptControl可以添加外部的对象,这个对象是个IDispatch接口,脚本调用的时候实际上是调用IDispatch的Invoke方法.
那么我们只要实现了IDispatch的Invoke方法,在里面通过RTTI再转而调用Delphi对象的Public方法即可.通过这个可以代理任何Delphi的对象.
仅仅调用Delphi对象似乎还不够完美,对象事件如果能关联到脚本的函数就更好了.那好,封装一个事件代理的类就可以.
例子如下:
procedure TForm1.FormCreate(Sender: TObject); begin Fscript := CreateScriptControl(); // 把Form1当成一个对象添加到Script中 Fscript.AddObject(Self.Name, SA(Self), true); Fscript.AddCode('function Form1_OnMouseMove(Sender, shift, x, y)' // + '{' // 在JS里面直接调用Form1上的任何Public的东西就都可以了,JS里面几乎没有类型的概念.事件的参数随便.计算也随便 + 'Form1.Button1.Caption = "x:"+x+";"+"y:"+y +";" + "shift:" + shift;' // + '}' // + 'function Button1_Click(Sender)' // + '{' //调用Delphi对象的方法 + 'Form1.SetBounds(0,0,800,480);' // + '}' // ); //关联Delphi的事件到JS的函数 Self.OnMouseMove := TEventDispatch.Create<TMouseMoveEvent>(Self, Fscript, 'Form1_OnMouseMove'); Button1.OnClick := TEventDispatch.Create<TNotifyEvent>(Button1, Fscript, 'Button1_Click'); end;
看上去很爽吧.
不过这个仅供我自己玩的,代码实现的比较毛糙,也没有经过严格的测试,甚至自己也没从头到尾再检查一次.如果有需要实用的朋友最好谨慎,肯定有细节问题要解决.
另外这个ScriptControl仅仅有32位的,在64位Windows上的system32里面并没有这个DLL,仅仅在SysWow64中才有.也就是说如果你要开发64位Windows程序就不能用了.当然如果是在64位Windows中运行的32位程序则没问题.
下面是代码,写的比较丑.
{ 让Delphi使用windows自带的scriptcontrol,在javascript中可以调用delphi的对象, 并且可以使用事件. wr960204武稀松 2013 } unit ScriptObjectUtilsWithRTTI; interface { 是否使用外部的MSScriptControl_TLB单元.我把这个单元的接口声明都放在后面了, 可以避免引入ActiveX等单元 如果觉得我的声明太旧或者有问题,可以打开这个开关,使用外部自己Import生成的单元 } { .$DEFINE Use_External_TLB } { 这个开关是使用LoadLibrary方式加载COM DLL,也就及时COM组件没有注册也可以创建COM对象 } {$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'} { 事件代理的泛型类,可以把Delphi的事件映射到Javascript的函数上. 注意,这是一个TComponent的派生类.如果不指定Ownder的话要手工释放的. } 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; { 很普通,创建一个MSWindows自带的ScriptControl实例,默认脚本是Javascript } function CreateScriptControl(ScriptName: String = 'javascript'): IScriptControl; { 创建对象的IDispatch的代理, Owned表示这个IDispatch拥有代理对象的生杀大权,当代理的IDispatch 释放的时候这个Obj也会被释放掉 } function SA(Obj: TObject; Owned: Boolean): IDispatch; overload; { 创建对象的IDispatch的代理 } 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; { IDispatch代理类.通过RTTI可以把Delphi对象的成员/属性/函数映射给IDispatch. 而且忽略调用协议. } 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 { IDispatch } 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; { TScriptObjectAdapter } 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 // Already have a dispid for this methodinfo 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); // MethodInfo.Invoke(FInstance, ['']); 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 //因为IDispatch是COM对象,一般是stdcall或者safecall,参数是由右到左传递的 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; { TEventDispatch<T> } 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 // Self 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); // TValue.Make(PStack, lRttiParameters[I].ParamType.Handle, lParamValues[I]); 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; end.
很喜欢你的文章
paxscript 现成的
很喜欢楼主的文章,有个不明白的地方想请教一下:如何实现在脚本里面给控件事件赋值呢? 比如在js脚本里面写 Button1.onclick = “Button1_Click”; 总是报错,求楼主大神指点