DWScript: Getting from IScriptObj to IInfo or TProgramInfo - delphi

Given a IScriptObj reference how does one get to a corresponding IInfo or TProgramInfo?
I have a script object that wraps a Delphi object.
In order to manage the life time of the script object the Delphi object stores a reference to the script object. The Script object is declared with a TdwsUnit component. It's pretty standard and goes something like this:
Delphi
type
TDelphiObject = class
private
FScriptObject: IScriptObj;
public
procedure DoSomething;
property ScriptObject: IScriptObj read FScriptObject write FScriptObject;
end;
Script
type
TScriptObject = class
protected
procedure DoSomething; virtual;
public
constructor Create;
end;
The instantiation of the Delphi object and setup of the Delphi/script links happens in the Delphi implementation of the script object constructor. Also pretty standard:
Delphi
// Implements TScriptObject.Create
procedure TMyForm.dwsUnitClassesTScriptObjectConstructorsCreateEval(Info: TProgramInfo; var ExtObject: TObject);
var
DelphiObject: TDelphiObject;
DelphiObjectInfo: IInfo;
begin
// Create the Delphi-side object
DelphiObject := TDelphiObject.Create;
// Get the script object "self" value
DelphiObjectInfo := Info.Vars['self'];
// Store the ScriptObject reference
DelphiObject.ScriptObject := DelphiObjectInfo.ScriptObj;
// Return the instance reference to the script
ExtObject := DelphiObject;
end;
Ideally I would have saved the IInfo reference rather that the IScriptObj since IInfo does everything I need later on, but from experience it seems the IInfo object is only valid for the duration of the method call.
Anyway, the problem occurs later on when TDelphiObject.DoSomething is called on the Delphi side.
TDelphiObject.DoSomething is meant to call the corresponding virtual method on the script object:
Delphi
procedure TDelphiObject.DoSomething;
var
Info: IInfo;
DoSomethingInfo: IInfo;
begin
// I have a IScriptObj but I need a IInfo...
Info := { what happens here? };
// Call the virtual DoSomething method
DoSomethingInfo := Info.Method['DoSomething'];
DoSomethingInfo.Call([]);
end;
I have tried a lot of different techniques to get a usable IInfo or TProgramInfo from the stored IScriptObj but every thing has failed. So what is the correct way of doing this?

The problem turned out to be that I assumed I needed an IInfo interface to encapsulate the object instance but apparently DWScript doesn't work that way. What I need is to create a temporary reference/pointer to the instance and then create an IInfo on that instead.
Here's how that is done:
procedure TDelphiObject.DoSomething;
var
ProgramExecution: TdwsProgramExecution;
ProgramInfo: TProgramInfo;
Data: TData;
DataContext: IDataContext;
Info: IInfo;
DoSomethingInfo: IInfo;
begin
(*
** Create an IInfo that lets me access the object represented by the IScriptObj pointer.
*)
// FProgramExecution is the IdwsProgramExecution reference that is returned by
// TdwsMainProgram.CreateNewExecution and BeginNewExecution. I have stored this
// elsewhere.
ProgramExecution := TdwsProgramExecution(FProgramExecution);
ProgramInfo := ProgramExecution.AcquireProgramInfo(nil);
try
// Create a temporary reference object
SetLength(Data, 1);
Data[0] := FScriptObject;
ProgramInfo.Execution.DataContext_Create(Data, 0, DataContext);
// Wrap the reference
Info := TInfoClassObj.Create(ProgramInfo, FScriptObject.ClassSym, DataContext);
// Call the virtual DoSomething method
DoSomethingInfo := Info.Method['DoSomething'];
DoSomethingInfo.Call([]);
finally
ProgramExecution.ReleaseProgramInfo(ProgramInfo);
end;
end;
What this does is enable object oriented call backs from Delphi to the script. Without this it is only possible to call global script functions from Delphi.
FWIW, the following two lines from the above:
ProgramInfo.Execution.DataContext_Create(Data, 0, DataContext);
Info := TInfoClassObj.Create(ProgramInfo, FScriptObject.ClassSym, DataContext);
can be replaced with a call to CreateInfoOnSymbol (declared in dwsInfo):
CreateInfoOnSymbol(Info, ProgramInfo, FScriptObject.ClassSym, Data, 0);

Related

Delphi Spring Mocking: Invalid Cast at `as` operation -- How do I solve this?

I want to test a method where an interface is cast to another interface. The cast is valid since the one interface is derived by the other. Unfortunately I receive an error at the marked line. I allready tried to mock QueryInterface but the function was not called and the error was still there. Is there any possibility to handle this within Spring.Mocking?
Tanks and keep healthy
unit Main;
{$M+}
interface
procedure Execute;
implementation
uses
Spring.Mocking,
System.SysUtils;
type
TRefFunc = reference to function: Boolean;
IHelper = interface
['{7950E166-1C93-47E4-8575-6B2CCEE05304}']
end;
IIntfToMock = interface
['{8D85A1CD-51E6-4135-B0E9-3E732400BA25}']
function DoSth(const AHelper: IHelper; const ARef: TRefFunc): Boolean;
end;
IYetAnotherIntf = interface(IIntfToMock)
['{95B54D3B-F573-4957-BDB3-367144270C3B}']
end;
IIntfProvider = interface
['{8B3E4B7B-1B2D-4E1F-942D-7E6EB4B9B585}']
function YetAnotherIntfFactory: IYetAnotherIntf;
end;
TClassToTest = class
private
FIntfProvider: IIntfProvider;
public
function MethodeToTest: Boolean;
constructor Create(const AIntfProvider: IIntfProvider);
end;
procedure Execute;
var
IntfMock : Mock<IIntfToMock>;
YetiMock : Mock<IYetAnotherIntf>;
ProvMock : Mock<IIntfProvider>;
Instance : TClassToTest;
OutObj : Pointer;
begin
IntfMock := Mock<IIntfToMock>.Create();
YetiMock := Mock<IYetAnotherIntf>.Create();
YetiMock.Setup.Returns(True).When.DoSth(Arg.IsAny<IHelper>, Arg.IsAny<TRefFunc>());
{
// Just a try. Did not work...
YetiMock.Setup.Executes(
function (const ACallInfo: TCallInfo): TValue
begin
ACallInfo.Args[1].From(IIntfToMock(IntfMock));
Result := TValue.From(True);
end
).When.QueryInterface(IIntfToMock, OutObj);
}
ProvMock := Mock<IIntfProvider>.Create();
ProvMock.Setup.Returns(TValue.From(IYetAnotherIntf(YetiMock))).When.YetAnotherIntfFactory;
Instance := TClassToTest.Create(ProvMock);
if Instance.MethodeToTest then
System.Writeln('everything works fine :)')
else
System.Writeln('that´s bad :(');
end;
{ TClassToTest }
constructor TClassToTest.Create(const AIntfProvider: IIntfProvider);
begin
Self.FIntfProvider := AIntfProvider;
end;
function TClassToTest.MethodeToTest: Boolean;
var
Instance : IIntfToMock;
YetAnother : IYetAnotherIntf;
begin
//
Result := False;
try
Instance := Self.FIntfProvider.YetAnotherIntfFactory;
Instance.DoSth(nil, nil);
YetAnother := Self.FIntfProvider.YetAnotherIntfFactory;
Instance := YetAnother; // works
Instance := IIntfToMock(YetAnother); // works
Instance := YetAnother as IIntfToMock; // BOOM: EIntfCastError
Result := True;
except
end;
end;
end.
Spring Mocks are more powerful than you think.
A mock automatically returns a mock from methods returning a mockable interface (*) - and always the same instance of it. That means for factory mocks you don't need to specify any expectations. You just need to get hold of the mock returned to specify its behavior.
Also found a small bug in there - it tries this on any interface regardless its "mockability" (is that a word? ^^). I will add a check here. Then in case it was not mockable the error will occur later if you really try to grab it as mock.
In order for a mock to also support other interfaces you simply have to tell it. This follows the same behavior as implementing interfaces in objects. If you only implement IYetAnotherIntf in a class and store it in an interface variable of that type but then call as, Supports or QueryInterface on it it will fail.
Here is the entire code - fwiw mocks are auto intialized so you don't have to call Create which nicely reduces the code to its essence: the behavior specification.
Also if you don't care for any of the parameters at all you can write this a litte shorter.
procedure Execute;
var
ProvMock: Mock<IIntfProvider>;
YetiMock: Mock<IYetAnotherIntf>;
Instance: TClassToTest;
begin
// using type inference here - <IYetAnotherIntf> on From not necessary
YetiMock := Mock.From(ProvMock.Instance.YetAnotherIntfFactory);
// lets make the behavior strict here
// so it does not return False when there is no match
YetiMock.Behavior := TMockBehavior.Strict;
YetiMock.Setup.Returns(True).When(Args.Any).DoSth(nil, nil);
// this will internally add the IIntfToMock to the intercepted interfaces
// as it returns a Mock<IIntfToMock> we can also specify its behavior
// more about this particular case below
YetiMock.AsType<IIntfToMock>;
Instance := TClassToTest.Create(ProvMock);
if Instance.MethodeToTest then
System.Writeln('everything works fine :)')
else
System.Writeln('that´s bad :(');
end;
function TClassToTest.MethodeToTest: Boolean;
var
Helper: THelper;
RefFunc: TRefFunc;
Instance: IIntfToMock;
YetAnother: IYetAnotherIntf;
begin
Result := False;
try
// just using some variables for this demo
// to verify that arg matching is working
Helper := THelper.Create;
RefFunc := function: Boolean begin Result := False end;
Instance := FIntfProvider.YetAnotherIntfFactory;
Assert(Instance.DoSth(Helper, RefFunc));
YetAnother := FIntfProvider.YetAnotherIntfFactory;
Assert(YetAnother.DoSth(Helper, RefFunc));
// same as directly assign YetAnotherIntfFactory
Instance := YetAnother;
Assert(Instance.DoSth(Helper, RefFunc));
// same as before, direct assignment no interface cast via QueryInterface
Instance := IIntfToMock(YetAnother);
Assert(Instance.DoSth(Helper, RefFunc));
// QueryInterface "cast" - the interface interceptor internally needs to know
// that it also should handle that interface
Instance := YetAnother as IIntfToMock;
// the following also returns true currently but I think this is a defect
// internally setup for a mock returned via the AsType goes to the same
// interceptor and thus finds the expectation defined on the mock it was
// called on. That means you cannot specify derived behavior on such a mock
// or even worse if they are completely unrelated types but have identical
// methods they would interfer with each other - I will look into this
Assert(Instance.DoSth(Helper, RefFunc));
Result := True;
except
end;
end;
While preparing this answer I found the issue I described as I wanted to demonstrate that you can define a different behavior on the other interface just like you can when implementing interfaces in classes. As I wrote I will look into this any time soon. I think its a general missing feature on the interface interceptor as existing interceptors are just being passed to the additionally handled interface which is not desired here.
Update 12.04.2021: The two mentioned bugs are fixed now:
methods returning an interface will only automatically return a mock when the interface has method info
when supporting other interfaces on a mock each interface will have its own behavior specifications

Variable required error (Delphi) - How to take the address of a procedure?

Using Delphi 7 here. When I take the address of a procedure (with the purpose of sending this method address to an external C++ DLL as a callback) the Delphi 7 compiler reports Variable required. Why? How do you take the address of a method with or without a parameter list?
Here's my simplified code which shows the compiler error.
// ...
type
PTProcedureCallback = ^TProcedureCallback;
TProcedureCallback = procedure() of object;
// ...
TTestCallback = class
constructor Create();
procedure MyCallback();
end;
//...
implementation
constructor TTestCallback.Create();
var
pCallback: PTProcedureCallback;
begin
// Constructor
inherited;
// Test callback
pCallback := #MyCallback; // <- [Error] Variable required
end;
procedure TTestCallback.MyCallback();
begin
// Do something
end;
end;
You don't need PTProcedureCallback at all, as TProcedureCallback is already a pointer type.
constructor TTestCallback.Create();
var
pCallback: TProcedureCallback;
begin
// Constructor
inherited;
// Test callback
pCallback := MyCallback;
end;
That being said, you can't use a procedure of object as a C/C++ callback, unless the C/C++ code was written in C++Builder specifically, and is actually expecting a procedure of object via the __closure compiler extension. If not, you will not be able to use a non-static class method as the callback. However, if the callback allows you to pass in a user-defined value, you can use that to pass in your object's Self pointer so your callback can access its non-static members.
Also, your TProcedureCallback is using Delphi's default register calling convention (__fastcall in C++Builder), which does not exist in non-C++Builder compilers. Only cdecl and stdcall are portable calling conventions.

Instantiated COM Component gets invalid after leaving method (but not its scope)

I am currently testing two external COM components. I have big issue with one of them, but I cannot really find reason behind such behavior. Let me provide some example.
const
CLASS_SomeClas: TGUID = '{SomeGUID}';
type
ISomeInterface = interface(IDispatch)
['{SomeGUID}']
function SomeMethod(const AInput: WideString): WideString; safecall;
end;
TWrappingClass = class(TObject)
strict private
FInstance: ISomeInterface;
procedure CreateInstance;
public
procedure DoYourActualJob;
end;
procedure TWrappingClass.CreateInstance;
begin
FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
dbg(FInstance._AddRef); // Debugs 3
dbg(FInstance._AddRef); // Debugs 4
dbg(FInstance.Release); // Debugs 3
dbg(FInstance._AddRef); // Debugs 4
FInstance.SomeMethod(''); //Runs as expected
end;
procedure TWrappingClass.DoYourActualJob;
begin
CreateInstance;
dbg(FInstance._AddRef); //Debugs -1!
FInstance.SomeMethod(''); //AV
end;
As provided with example instance gets invalid after it leaves CreateInstance method. Component is designed to work with many sequential calls of SomeMethod and it does work when called inside single method.
Could someone give me clue what is actually happening there, why my instance gets invalid? Is it problem with my code, with Delphi or with component's code? When I change the implementation of TWrappingClass to another vendor (that is I change both ISomeInterface and CLASS_SomeClass) then everything works fine.
EDIT:
Behaviour does not change when I don't even call SomeMethod. That is after I leave CreateInstance, call to _AddRef returns -1. Component I am testing is here CadEditorX Probably I am not allowed to attach the OCX without violating its license.
You state clearly in the question that the erroneous behaviour only occurs with one specific COM object. Given this fact, and that Delphi's COM reference counting is known to work correctly, the only reasonable conclusion is that the fault lies in this specific COM object.
Your only recourse of action is to contact the vendor of this COM object and file a bug report with them.
One thing to look at, with a view to a possible work around, is how you are creating the object. You use CreateComObject. This receives a class ID and returns IUnknown. It calls CoCreateInstance passing the class ID, and requesting the IUnknown interface. You then need to query for your interface, ISomeInterface. So your code looks like this:
var
iunk: IUnknown;
intf: ISomeInteface;
....
CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER,
IUnknown, iunk);
iunk.QueryInterface(ISomeInterface, intf);
The fact that you have two interface variables, one IUnknown and one ISomeInterface explains why you see the reference count that you do. Now, you might think that you only have one interface variable, but that's not the case. There are two, only one of them is an implicit local. You can see this by looking at the compiled code and stepping through under the debugger.
This code:
procedure TWrappingClass.CreateInstance;
begin
FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
end;
is compiled as if it were this (ignoring error checking):
procedure TWrappingClass.CreateInstance;
var
iunk: IUnknown;
begin
iunk := CreateComObject(CLASS_SomeClass);
try
FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
finally
iunk := nil;
end;
end;
Perhaps the COM component cannot handle the call to Release made on its IUnknown interface.
So, you could try to work around this by using CoCreateInstance instead of CreateComObject. Pass ISomeInterface as the riid parameter.
OleCheck(CoCreateInstance(CLASS_SomeClass, nil, CLSCTX_INPROC_SERVER
or CLSCTX_LOCAL_SERVER, ISomeInterface, FInstance));

Implementing Observer Pattern in Delphi with Interface

First of all, Hello for everybody and thanks for your help.
I'm trying to implement the Observer Pattern in Delphi using Interfaces, so an Object could be a Subject and an Observer at the same time.
I have a class that implements ISubject, with the following method:
procedure TSomeClass.Attach(const observer: IObserver);
var
I: Integer;
begin
if Fobservers = nil then
begin
Fobservers := TInterfaceList.Create;
end;
if Fobservers.IndexOf(Observer) < 0 then
Fobservers.Add(Observer);
end;
I followed Joanna Carter's example at http://blogs.teamb.com/joannacarter/2004/06/30/690.
In the application, I instantiate an object that implements the IObserver, and attach it to the TSomeClass object (which implements ISubject as well).
Then I call the Notify method from the TSomeClass object and it works correctly. My problem occurs when I try to FreeAndNil my Observer object, because I get an Invalid Pointer Operation, even though I'm using 'const' in the parameter and when I reach the FreeAndNil line in debug mode, the object is properly assigned, with all properties set and with a random property changed inside the notify.
I noted that I couldn't Free my object anymore when I call this line:
Fobservers.Add(Observer);
If I comment this line, then I can free my object. The code inside the application looks like this:
procedure TfrmAlisson.Button2Click(Sender: TObject);
var
locSomeClass: TSomeClass;
locObserver: TSomeObserverClass;
I: Integer;
begin
locObserver:= TSomeObserverClass.create(394693);
try
locSomeClass:= TSomeClass.create(263151);
try
locSomeClass.Attach(locObserver);
locSomeClass.NotifyObservers;
finally
FreeAndNil(locSomeClass);
end;
ShowMessage(IntToStr(locObserver.SomeProperty)); // This property is changed inside the notify
finally
locObserver.Free; // error
end;
end;
I would like to know why adding the IObserver to the TInterfaceList causes this (I'm using Delphi 2009).
Your TSomeObserverClass is most likely inheriting from TInterfacedObject.
When you pass it in Attach it gets passed as IObserver and this is where the reference counting kicks in. The RefCount goes to 1 when it gets added to Fobservers and when you destroy locSomeClass and with it the Fobservers list it gets removed again which causes the RefCount to drop to 0. Then the instance behind the IObserver interface reference is being destroyed.
To show the problem here is the minimal code to reproduce it:
var
obj: TInterfacedObject;
list: TInterfaceList;
begin
o := TInterfacedObject.Create;
try
list := TInterfaceList.Create;
list.Add(o);
list.Free;
finally
FreeAndNil(o);
end;
end;
If you execute this you see the EInvalidError in the FreeAndNil which is caused because the instance was already destroyed by the automatic reference counting implemented in TInterfacedObject.
As already commented you should not mix object and interface references or inherit from a class that does not implement automatic reference counting.

Accessing Sub functions /procedures from DPR or other function / procedure in Delphi

As much I know - Subroutines are with Private access mode to its parent unction / procedure, right?
Is there any way to access them from "outer-world" - dpr or other function / procedure in unit?
Also - which way takes more calcualtion and space to compiled file?
for example:
function blablabla(parameter : tparameter) : abcde;
procedure xyz(par_ : tpar_);
begin
// ...
end;
begin
// ...
end;
procedure albalbalb(param : tparam) : www;
begin
xyz(par_ : tpar_); // is there any way to make this function public / published to access it therefore enabling to call it this way?
end;
// all text is random.
// also, is there way to call it from DPR in this manner?
// in C++ this can be done by specifing access mode and/or using "Friend" class .. but in DELPHI?
Nested procedures/functions - those declared inside another procedure or function, are a special type, because they can access the stack (and thereby parameters/local variables) of the procedure they are nested in. Because of this, and Delphi scope rules, there is no way to access them outside the "parent" procedure. You use them only if you need to take advantage of their special features. AFAIK Delphi/Pascal is one of the few languages to have this feature. From a compiler point of view the call has some extra code to allow accessing the parent stack frame, IIRC.
AFAIK "friend" class/functions in C++ are different - they are class access methods, while in your example you are using plain procedures/functions.
In Delphi all procedure/classes declared in the same unit are automatically "friend", unless strict private declarations are used in latest Delphi releases. For example this code snippets will work, as long everything is in the same unit:
type
TExample = class
private
procedure HelloWorld;
public
...
end;
implementation
function DoSomething(AExample: TExample);
begin
// Calling a private method here works
AExample.HelloWordl;
end;
Note: Embedded Routines <> Private/Protected Methods.
Embedded routines i.e. routines inside routines can not be accessed by external routines.
You have posted an example of an Embedded routine, I also heard them called Internal Routines.
Here is another example:
procedure DoThis;
function DoThat : Boolean;
begin
// This Routine is embedded or internal routine.
end;
begin
// DoThat() can only be accessed from here no other place.
end;
Regardless of visibility, methods on classes, can be called using Delphi 2010 via RTTI. I have detailed how to do this in this article.
If you are in the same Unit methods on a class can be accessed by any other code regardless of visibility, unless they are marked with Strict Private. This Question has more details and good example code in the accepted answer.
If you are in two different units you can use the Protected Method Hack to access the protected methods. Which is detailed in detailed in this article.
Yes, you can access a subroutine, which is nested in other (parent) subroutine, from the outer world. Though it's somewhat tricky. I've found this howto in the web.
How to pass nested routine as a procedural parameter (32 bit)
Delphi normally does not support passing nested routines as procedural parameters:
// This code does not compile:
procedure testpass(p: tprocedure);
begin
p;
end;
procedure calltestpass;
procedure inner;
begin
showmessage('hello');
end;
begin
testpass(inner);
end;
The obvious workaround is to pass procedure address and typecast it within testpass:
// This code compiles and runs OK
procedure testpass(p: pointer);
begin
tProcedure(p);
end;
procedure calltestpass;
procedure inner;
begin
showmessage('hello');
end;
begin
testpass(#inner);
end;
There is, however, a pitfall in the above example - if the "inner" routine references any variable that was pushed onto the stack before the "inner" procedure was called from testpass (calltestpass parameters - if there were any, or local variables in calltestpass - if there were any), your system most probably crashes:
// This code compiles OK but generates runtime exception (could even be
// EMachineHangs :-) )
procedure testpass(p: pointer);
begin
tProcedure(p);
end;
procedure calltestpass;
var msg: string;
procedure inner;
begin
msg := 'hello';
showmessage(msg);
end;
begin
testpass(#inner);
end;
The reason is, in simple words, that the stack frame arrangement
was "broken" by the call to testpass routine and "inner" procedure
incorrectly calculates parameters and local variables location
(do not blame Delphi, please).
The workaround is to set up the correct stack context before
"inner" is called from within "testpass".
// This code compiles and runs OK
{$O-}
procedure testpass(p: pointer);
var callersBP: longint;
begin
asm // get caller's base pointer value at the very beginning
push dword ptr [ebp]
pop callersBP
end;
// here we can have some other OP code
asm // pushes caller's base pointer value onto stack and calls tProcedure(p)
push CallersBP
Call p
Pop CallersBP
end;
// here we can have some other OP code
end;
{$O+}
procedure calltestpass;
var msg: string;
procedure inner;
begin
msg := 'hello';
showmessage(msg);
end;
begin
testpass(#inner);
end;
Please note the optimization is switched OFF for testpass routine - optimization generally does not handle mixed OP/assembler code very well.
No, there is no way to do what you're asking. The xyz function is callable only by the enclosing blablabla function. Outside that function, xyz is not in scope and there is no way to name it. If C++ allowed nested function, there wouldn't be any way to refer to it, either, just like there's no way to refer to functions with static linkage from outside the current translation unit.
If you need to call xyz from outside the blablabla function, then move xyz outside. If you need to call it from outside the current unit, then you need to declare that function in the unit's interface section. Then, add that unit to the external code's uses clause and you can call xyz from wherever you want, even the DPR file.
If xyz refers to variables or parameters of the blablabla function, then you'll need to pass them in as parameters since xyz will no longer have access to them otherwise.
The concept of access specifiers isn't really relevant here since we're not talking about classes. Units have interface and implementation sections, which aren't really the same as public and private sections of a class.

Resources