How to pass the interfaced object to the Pascal Sc

2019-06-18 12:32发布

问题:

Delphi part:

I have a class with the event and from that event I need to call a procedure passing the interfaced object to it. It works fine in Delphi but I have problems with declaring it in Pascal Script.

To the background - the IGPGraphics interface is a part of the Delphi GDI+ library and without methods is defined like this:

type
  IGdiplusBase = interface
  ['{24A5D3F5-4A9B-42A2-9F60-20825E2740F5}']
  IGPGraphics = interface(IGdiPlusBase)
  ['{57F85BA4-CB01-4466-8441-948D03588F54}']

The following is just a simplified Delphi pseudocode of what I need to do in Pascal Script:

type
  TRenderEvent = procedure(Sender: TObject; const GPGraphics: IGPGraphics) of object;
  TRenderClass = class(TGraphicControl)
  private
    FOnRender: TRenderEvent;
  public
    property OnRender: TRenderEvent read FOnRender write FOnRender;
  end;

// when the TRenderClass object instance fires its OnRender event I want to call 
// the RenderObject procedure passing the IGPGraphics interfaced object to it; I
// hope I'm doing it right, I'm just a newbie to this stuff - but it works so far
// in Delphi (since I didn't get it to work in Pascal Script)

procedure TForm1.RenderClass1Render(Sender: TObject; const GPGraphics: IGPGraphics);
begin
  RenderObject(GPGraphics, 10, 10);
end;

// what I need in Pascal Script is between these two lines; just pass the interface
// object from the event fired by component to the procedure called from inside it

procedure RenderObject(const GPGraphics: IGPGraphics; X, Y);
begin
  // and here to work with the interfaced object somehow
end;

Pascal Script compilation part:

My aim is to have the class with event available in Pascal Script and need to pass that interfaced object to that procedure just like above, so the first I've tried to declare for the compilation time this (but I'm not even sure if that's the right way to do so):

// the interface
PS.AddInterface(Cl.FindInterface('IUnknown'), StringToGuid('{57F85BA4-CB01-4466-8441-948D03588F54}'), 'IGPGraphics');
// the type for the event
PS.AddTypeS('TRenderEvent', 'procedure(Sender: TObject; const GPGraphics: IGPGraphics)');
// and the class with the event itself
with PS.AddClassN(PS.FindClass('TGraphicControl'), 'TRenderClass') do
begin
  RegisterProperty('OnRender', 'TRenderEvent', iptrw);
end;

Pascal Script runtime part:

Where I'm definitely lost is the runtime part. I can't figure out how to get the interfaced object from the call stack and pass it to my RenderObject procedure:

function RenderClassProc(Caller: TPSExec; Proc: TPSExternalProcRec; Global, 
  Stack: TPSStack): Boolean;
var
  PStart: Cardinal;
begin
  PStart := Stack.Count-1;
  Result := True;
  if Proc.Name = 'RENDEROBJECT' then
  begin
    // how do I get the interfaced object from Stack (or whatever else) and pass 
    // it to the RenderObject proc here ? I can't find anything related about it
    // except functions that has no parameter index
    RenderObject(Stack.Get ?, Stack.GetInt(PStart-2), Stack.GetInt(PStart-3));
  end;
end;

And the question is:

Can anyone suggest me how to correctly define compilation and runtime part for this case or correct me with passing the interfaced object somehow ?

P.S. sorry for that Inno-Setup tag but maybe someone from there tried to customize InnoSetup this way.

Thanks a lot!

回答1:

If I understand what you're asking, you want to pass an interface as a parameter to a method. Unfortunately, I don't have an exact answer to that, but I do know how to assign an interface to a global variable for PascalScript. Here's how I do it in Castalia:

In the PSScript OnCompile event, add the interface with PS.Comp.AddInterface, and then add each of the necessary methods. After that, add a variable of the interface type. It looks like this, for example:

with PS.Comp.AddInterface(ARunner.Comp.FindInterface('IUnknown'),
  StringToGUID('{0346F7DF-CA7B-4B15-AEC9-2BDD680EE7AD}'),
  'ICastaliaMacroClipboardAccess') do
begin
  RegisterMethod('function GetText: string', cdRegister);
  RegisterMethod('procedure SetText(AText: string)', cdRegister);
end;
PS.AddRegisteredVariable('Clipboard', 'ICastaliaMacroClipboardAccess');

Then, in the OnExectute event, bind the previously created variable to the instance:

P := PS.GetVariable('Clipboard'); //P is type PIFVariant
SetVariantToInterface(P, Implementing object as ICastaliaMacroClipboardAccess);    

Having done that, the script has access to the interfaced object through the variable, so in this instance, the script could contain a call to Clipboard.GetText, and it works as you would expect.

That is tested and works.

Now, I would speculate that you might be able to use TPSScript.ExecuteFunction, passing in the PIFVariant from above, to get closer to what you want. That is not something I've tested, however.

Good luck!



回答2:

It is hard to believe BUT I found out how to do it

procedure TApp.CallProcedureWithClassArg(x: TPSExec);
var
  o: TSampleObject;
  argumentList: TPSList;
  arg1: TPSVariantClass;
  proc: Integer;
begin
  o := TSampleObject.Create;
  o.X := 1; // do something with the object maybe
  proc := x.GetProc('TakeThis');
  argumentList := TPSList.Create;
  arg1.VI.FType := x.FindType2(btClass);
  arg1.Data := o;
  argumentList.Add(@arg1);
  x.RunProc(argumentList, proc);
  argumentList.Free;
end;

This is basically what should be done.

  • User your TPSExec instance, let's say x
  • Then obtain procedure number using x.GetProc method
  • then create the argument list of TPSList type
  • Create TPSVariantClass var, assign your class instance (which should be passed) to its Data field
  • also assign x.FindType2(btClass) to its VI.FType field (have absolutely no idea why this works)
  • add a pointer to TPSVariantClass variable to the TPSList list
  • And....... Call the procedure x.RunProc(argList, proc); where proc is the number of the procedure obtained earlier

This works for classes, but it should be not much different for interfaces, just use TPSVariantInterface type for the argument variable instead of TPSVariantClass; everything else should be the same.

I hope this will help someone maybe.