delphi DWScript - change script variable value of type TObject at scriptruntime - delphi

I have a DWScript like this
var Outputter: TOutputter;
procedure OutputterTester;
begin
Outputter.Print;
end;
TOutputter (is only a example for a complex class) is declared and created in delphi code and exposed to the DWScript via Rtti.
TOutputter = class
procedure Print;
end;
I want to use the compiled script for several instances, but change the value Outputter that it link to current instance.
I know i can access a script variable with:
var Exec : IdwsProgramExecution;
...
AVar := Exec.Info.Vars['Outputter'].Value;
But the value is a Variant so i can't assigne a object. How can I change the value? If i first create the class in script like:
procedure Init;
Outputter := TOutputter.Create;
end;
I can assign Exec.Info.Vars['Outputter'].ScriptObj.ExtObject a arbitrary instance of TOutputter (created in delphi code) and access them in scriptcode over Outputter. But i want to assign a delphi code created instance of TOutputter without the init part.
Thank you for help!

If I understood correctly, you want to skip the Init procedure, but if that means you have to make Outputter either an external variable or a magic name or you won't be able to recognize which variable it is.
One approach for the above could be to just prepend your boiler plate code to the user script:
var Outputter := TOutputter.Create;
Another option would be to create an external variable in a TdwsUnit, you'll then be responsible for creating the script-side object from the Delphi side when the user accesses that external variable, and can handle what happens if the user assign something to the variable as well.
But if Outputter is meant to be read-only by the user, you could just declare an Outputter() function in a TdwsUnit (and create and return the script object there).
Last option would be to use an RTTI Environment, depending on what you want to do with it, that could be the simplest option, as you can change the instance in the environment directly (one of the unit tests for RTTI environment does that).

Related

Providing data to Mock for Unit Testing

The class I am doing Unit Testing scrolls each record of a DB table and sum the value in a field to the previous value. The following is the class reduced to the bone:
procedure TSumList.Sum;
var
FSum:integer;
begin
FSum:=0;
FDB.First;
while not FDB.EOF do
begin
FSum:=FSum+FDB.GetAmount;
FDB.Next;
end;
end;
FDB refers to the DB mock interface named IIDBTable.
The following is the DB mock for the dependency injection:
IIDBTable = interface
['{A299D1D6-93AF-45CC-8DE2-9A4EE188C352}']
procedure First;
procedure Next;
function EOF : boolean;
function GetAmount:integer;
end;
TMockDBTable = class (TInterfacedObject,IDBTable)
procedure First;
procedure Next;
function EOF : boolean;
function GetAmount:integer;
end;
Problem is I don't know how to provide data to the mock for the test. Of course I can add an extra procedure, say AddValues(aAmount:integer), that does the job but in that case I would end up with this extra procedure in production too and I don't need it.
What is the best practice for this ?
I use Spring for Delphi framework
You can also use DSharp mocks (or Delphi Mocks).
This will be the setup code for DSharp (Delphi Mocks should be similar)
var
mockDBTable: Mock<IIDBTable>;
begin
mockDBTable.Setup.WillExecute.Once.WhenCalling.First;
mockDBTable.Setup.WillReturn(False).Exactly(5).WhenCalling.EOF;
mockDBTable.Setup.WillReturn(True).Once.WhenCalling.EOF;
mockDBTable.Setup.WillReturn(5).Once.WhenCalling.GetAmount;
mockDBTable.Setup.WillReturn(4).Once.WhenCalling.GetAmount;
mockDBTable.Setup.WillReturn(3).Once.WhenCalling.GetAmount;
mockDBTable.Setup.WillReturn(2).Once.WhenCalling.GetAmount;
mockDBTable.Setup.WillReturn(1).Once.WhenCalling.GetAmount;
mockDBTable.Setup.WillExecute.Exactly(5).WhenCalling.Next;
What you do here is specify what you expect to be called and what to be returned.
This saves you from manually writing a mock class and feed it with data.
Add an extra method to the mock class, AddValues. It will exist only in that mock class, not in the real class, so your objection that the extra code would appear in production is unfounded.
I suspect you thought the code would need to be in production because you're dealing with the mock object exclusively through the interface. The extra method would exist in the interface, and would therefore need to be implemented in the production version of the class, even though it doesn't belong there.
Instead, instantiate the TMockDBTable and access it through the object reference to set it up however it's needed for the test. Once it's ready, then switch to using it via the IIDBTable interface.
Another option is to feed the data into the class through a parameter you add to its constructor. Then you don't even need the extra method, and so there's no temptation to define that method on the interface or on the production class. There's no reason your mock class's constructor needs to look like the production class's constructor since the classes aren't related at all. The constructor isn't part of the interface definition.

Class doesn't work when defined as a global variable in delphi

I created a simple class to explain my problem:
ttest =class
private
val:boolean;
published
function get:boolean;
end;
...
function ttest.get: boolean;
begin
val:=not val;
result:=val;
end;
Now if I declare a local ttest variable and call my_var.get; then everything works, but if I declare it as a global variable then it can't access the val field anymore, it shows an error message which says "Access violation...".
I read some articles about classes in Delphi but still can't find my mistake.
You've neglected to instantiate the class.
Global class-reference variables are initialized to nil, whereas local variables are not initialized at all. The local variable has a value determined by whatever happened to be on the stack at the time you called your function, and your program is interpreting that value as though it were a TTest reference even though it's really not. Your program then reads the value at that memory address to get the value that would represent the val field.
The only reason your code appears to work with a non-global variable is luck. Whether it's good luck or bad is another matter. (Good luck, since your code appeared to work, and working code is always nice. Bad luck, since you'd have been alerted to your mistake earlier if your code had crashed.)
Instantiate a class before you use references to it.
x := TTest.Create;
Now you can access fields, methods, and properties of the object via the x variable.
You should have gotten a compiler warning when you attempted to use a local variable without assigning a value to it first. Although they're just warnings, and your program will still run, never ignore a warning or even a hint. When the compiler bothers to complain about something, it's usually right.
In Delphi object variables are always pointers. Before you can use the variable you need to initialize it with a reference to an object. The most common way to do that is to create a new object of the particular class.
procedure Foo;
var
Obj: TObject;
begin
Obj := TObject.Create;
try
// Do stuff with Obj
finally
Obj.Free;
end;
end;
In this case Obj starts out as an uninitialized pointer (it will point to random memory). It is only after we assign the newly created TObject that Obj is a valid object reference.
In Delphi there is no automatic garbage collection for objects, so you always need to call free on them when you are done using them. If you declare a global or local object variable, you can initialize it the special initialization section of the unit and free the object in the finalization section.
unit myunit;
interface
var
Obj: TObject;
implementation
initialization
Obj := TObject.Create;
finalization
Obj.Free;
end.
Variables declared in the interface section are globally visible, variables declared in the implementation section are only visible inside the unit. It should be noted that declaring a global object variable means that any unit can overwrite the variable with a reference to a new object without freeing the existing object first. This would cause a memory leak as again there is no automatic garbage collection.
A delphi class is basically just a description, not the object itself. You describe the properties and methods the final object should have. And the missing piece of the puzzle is that you havent really told Delphi to create an object from your class.
This is done by calling the constructor:
mMyInstance:=TTest.Create;
The constructor takes the class description and builds an object instance for you in memory. It returns a pointer to the object which you must store in a variable (myInstance in the above example) of the same type.
Reading your question, I suspect you want to create an object that is "always there", a bit like the printer object. This is easy to do, but just like the printer object - you must include that unit before you can access the object. I think Anders E. Andersen above has shown how most people would initialize an object from a unit centric point of view.
If you want the object to be reachable from another unit, say your mainform or any other unit, first add "myunit" to the uses list. Then to make it visible you add a function, like this:
function test:ttest;
Begin
result:=obj;
end;
And remember to add "function test:TTest" to the interface section of the unit. Then you can use the object from another unit as such:
myUnit.test.get;
But be warned! This is pretty old school programming, and you run the risk of your unit being released (which calls finalization and thus destroys your object) before the other units are done with it. Thus you risk calling a function in an object which no longer exists in memory - causing a spectacular access violation when your program closes.
If you want to learn Delphi properly, head over to Delphi Basics and read up on the basic principles. It takes a while to learn a new language but you will soon get the hang of it.
Good luck!

Is it possible for a managed local variable to transparently "travel to" another local scope?

This question is related to my other one and I hope to get some ideas from it:
Is it possible for a local managed variable (record, interface, ...) to survice the local scope and "travel to" another one without using any explicit out/var parameters or result values?
Sorry if this sounds strange, but this would allow me to create a managed object inside a called method which will only be destroyed when the calling method ends, not the one it has been created in, while the whole process is entirely transparent to the caller (this is the main goal). The caller doesn't have to declare anything.
First hacky idea comes here:
(Mis-)Use the automagically created wrapper object for anonymous methods and attach data to it. My assumption is: this object is created in the callers local scope, lives during the callees local scope (so the callee can attach data to it), and lives on until the end; of the caller.
Is it possible to attach data to this wrapper object? Apart from hackyness: has it any chance of working?
Edit: Maybe an easier phrasing for the question could be: "How to pass a result value from a function without using any parameters or function result?"
Edit2: Writing some code makes me wonder whether I should let it go:
function TForm1.L<T>(Func: TFunc<T>):T;
var
Value: T;
begin
Result := Func;
// now attach something to the anon wrapper of Func
end;
function TForm1.O<T>(Value: T): T;
begin
Result := T;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
List: TList;
begin
for Item in L(O<TList>(List)) do
begin
end;
// List should be destroyed here
end;
I think I should.
Sorry if this sounds strange, but this would allow me to create a managed object inside a called method which will only be destroyed when the calling method ends, not the one it has been created in. This way I don't have to use out/var variables or return values (which is effectively my goal).
The managed local variable from the CALLED method need to, well, "travel" to the calling method. The only defined methodologies for something like that to happen is to use var, out or return the actual value. That's because all "managed" data types that can be "transported" are reference-counted. This includes Interfaces and strings.
Use the automagically created wrapper object for anonymous methods and attach data to it. My assumption is: this object is created in the callers local scope, lives during the callees local scope (so the callee can attach data to it), and lives on until the end; of the caller.
Delphi generates an actual TInterfacedObject descendents for anonymous methods. It'll generate ONE such descendent for each method/procedure that declares anonymous methods. The name of the generated object will be based on the name of the procedure where the anonymous method is declared. This objects has methods, one method for each anonymous methods used. It also has data fields: one field for each local variable used in the anonymous method, plus a reference to the object you're operating on.
See here for a detailed explanation: How and when are variables referenced in Delphi's anonymous methods captured?
The idea is, you can attach data fields to the anonymous method by simply declaring local variables in the same procedure that's declaring the anonymous method, and using them within that anonymous method. You'll then be able to get hold of that data, but it would be a hacky, difficult way: you'd need to cast the anonymous method to the implementing object (the anonymous method is actually an interface, so it can be done). Then you'd need to use RTTI to get hold of the fields holding your data. Doesn't seem very useful to me.

How can I store a COM object in Inno Setup's TNewComboBox.Objects property?

I'm using Inno Setup to create an installer for my application. I'm currently filling a combobox (TNewComboBox) with the names of the Web sites on the current machine's IIS install. Now what I really want to do is store the COM object alongside the string in the objects property of the combo but keep getting type mismatch errors, even when wrapping the COM object in a TObject(xxx) call.
I've read in other places that the TStrings object should have an AddObject method but it doesn't seem to be present in Inno Setup/Pascal Script.
Do not cast, just wrap it in an object.
Type
TMyObjectForStringList = class
fCOMThingy : variant; // or ole variant
constructor create(comthingy:variant);
end;
constructor TMyObjectForStringList.Create(comthingy:variant);
begin
fcomthingy:=comthingy;
end;
myStringList.addobject(astring,TMyObjectForStringList.Create(avariant));
Do not forget to free it afterwards (Delphi's tstringlist lacks "deallocate all" functionality)
Delphi's TStrings class does have AddObject method but it seems that Inno's PascalScript TStrings wrapper doesn't. However, you should be able to set it like this:
Index := Strings.Add('text');
Strings.Objects[Index] := TObject(xxx);

Delphi Unit local variables - how to make each instance unique?

In the unit below I have a variable declared in the IMPLEMENTATION section - local to the unit. I also have a procedure, declared in the TYPE section which takes an argument and assigns that argument to the local variable in question. Each instance of this TFrame gets passed a unique variable via passMeTheVar.
What I want it to do is for each instance of the frame to keep its own version of that variable, different from the others, and use that to define how it operates. What seems to be happening, however, is that all instances are using the same value, even if I explicitly pass each instance a different variable.
ie:
Unit FlexibleUnit;
interface
uses
//the uses stuff
type
TFlexibleUnit=class(TFrame)
//declarations including
procedure makeThisInstanceX(passMeTheVar:integer);
private
//
public
//
end;
implementation
uses //the uses
var myLocalVar;
procedure makeThisInstanceX(passMeTheVar:integer);
begin
myLocalVar:=passMeTheVar;
end;
//other procedures using myLocalVar
//etc to the
end;
Now somewhere in another Form I've dropped this Frame onto the Design pane, sometimes two of these frames on one Form, and have it declared in the proper places, etc. Each is unique in that :
ThisFlexibleUnit : TFlexibleUnit;
ThatFlexibleUnit : TFlexibleUnit;
and when I do a:
ThisFlexibleUnit.makeThisInstanceX(var1); //want to behave in way "var1"
ThatFlexibleUnit.makeThisInstanceX(var2); //want to behave in way "var2"
it seems that they both share the same variable "myLocalVar".
Am I doing this wrong, in principle? If this is the correct method then it's a matter of debugging what I have (which is too huge to post) but if this is not correct in principle then is there a way to do what I am suggesting?
EDIT:
Ok, so the lesson learned here is that the class definition is just that. Many classes can go in one unit and all instances of all classes in the Type section share the implementation section of the unit.
myLocalVar is a global variable, but only visible within the unit.
A local variable would be in a procedure/function, like
procedure makeThisInstanceX(passMeTheVar: integer);
var
myLocalVar: Integer;
begin
myLocalVar := passMeTheVar;
end;
if you want an instance variable, that is each frame has its own copy, put it in the class:
type
TFlexibleUnit = class(TFrame)
procedure makeThisInstanceX(passMeTheVar:integer);
private
myLocalVar: Integer;
...
end;
You are calling the makeThisInstanceX method as a class (static) method rather than creating an instance of the class and calling it as an object method. Take a look at this reference:
http://oreilly.com/catalog/delphi/chapter/ch02.html
frame / unit / class / control
I applaud your heroic attempt to better the code. However, judging by your questions and comments I regret to inform you that your understanding is very limited.
A frame is not a unit which is not a class. A frame is a class but not every class is a frame. A frame is a control but not every control is a frame. Units have interface and implementation (and initialization and finalization) sections. Classes have private and public (and protected and published) parts.
I did not put the last paragraph in to try to teach but to allow you to gauge your understanding level. A Delphi developer ought to have no problem with the paragraph. I'm not trying to make you feel bad or to show off - just trying to help. Perhaps Stack Overflow is not the right tool for you at this time.
As somebody just learning Delphi for the first time, I might be confused by some of the seemingly redundant features. But the product has a long history and each addition made sense at the time it was added. It was also easier to learn when you only had to learn it a piece at a time.

Resources