3

I'm having trouble using meta classes in DWScript.

We are using scripting to enable VARs and end users to customize our application.

Our application data basically consist of a lot of small objects in a tree structure. Each object can either be "dumb" in that it just displays data or it can be intelligent in some way. The intelligence is implemented with scripting by associating different script classes with the tree objects.

The problem I'm having is that the script needs to communicate to the Delphi side framework what script class it should use to implement the object. Basically I need to pass a script meta class to the Delphi side and store the information there in a format that can be safely persisted (by type name, as a string probably). I also need to be able to go the other way; I.e. return the meta class to the script from the Delphi side.

TdwsUnit declaration

type
  // Base class of all tree objects
  TItem = class
    ...
  end;


  // The meta class
  // This is actually declared in code since TdwsUnit doesn't have design time support for meta classes.
  // Shown here for readability.
  TItemClass = class of TItem;


// The procedure that passes the meta class to the Delphi side.
// I cannot use a TItemClass parameter as that isn't declared until run time (after the TdwsUnit has initialized its tables).
procedure RegisterItemClass(AClass: TClass);

The script

type
  TMyItem = class(TItem)
    ...
  end;

begin
  // Pass the meta class to the Delphi side.
  // The Delphi side will use this to create a script object of the specified type
  // and attach it to the Delphi side object.

  RegisterItemClass(TMyItem);
end;

Delphi implementation

Declaration of the meta class, TItemClass. Done in TdwsUnit.OnAfterInitUnitTable.

procedure TMyDataModule.dwsUnitMyClassesAfterInitUnitTable(Sender: TObject);
var
  ItemClass: TClassSymbol;
  MetaClass: TClassOfSymbol;
begin
  // Find the base class symbol
  ItemClass := dwsUnitMyClasses.Table.FindTypeLocal('TItem') as TClassSymbol;
  // Create a meta class symbol
  MetaClass := TClassOfSymbol.Create('TItemClass', ItemClass);
  dwsUnitMyClasses.Table.AddSymbol(MetaClass);
end;

RegisterItemClass implementation

procedure TMyDataModule.dwsUnitMyClassesFunctionsRegisterItemClassEval(info: TProgramInfo);
var
  ItemClassSymbol: TSymbol;
  ItemClassName: string;
begin
  ItemClassSymbol := TSymbol(Info.Params[0].ValueAsInteger);
  ItemClassName := ItemClassSymbol.Name;
  ...
end;

So the question is How does one get a TSymbol from a meta class parameter?
Edit: I found the answer to one part of the problem in this old question.
In short the solution is to cast the parameter value to a TSymbol:

However...

Now assuming that I store the class name as a string. How do I get from this class name back to a symbol? I need this because, just as the script can set the item class (using the code above), the script can also ask for an items' class.

I have tried looking in the symbol table with any of the four different methods that seem to do what I need but none of them can find the symbol.

var
  ItemClassName: string;
  ItemClassSymbol: TSymbol;
...
  ItemClassName := 'TMyItem';
...
  ItemClassSymbol := Info.Table.FindTypeSymbol(ItemClassName, cvMagic);
  if (ItemClassSymbol = nil) then
    ItemClassSymbol := Info.Table.FindSymbol(ItemClassName, cvMagic);
  if (ItemClassSymbol = nil) then
    ItemClassSymbol := Info.Table.FindTypeLocal(ItemClassName);
  if (ItemClassSymbol = nil) then
    ItemClassSymbol := Info.Table.FindLocal(ItemClassName);

  // ItemClassSymbol is nil at this point :-(

So the question is Given the name of meta class, declared in script, how does one get the corresponding TSymbol from the Delphi side?

Edit: I have now found one possible solution to the last part.

The following seems to work but I'm unsure if that is the correct way to do it. I would have thought that I would need to limit the scope of the symbol search to the current script unit.

var
  ItemClassName: string;
  ItemClassSymbol: TSymbol;
...
  ItemClassName := 'TMyItem';
...
  ItemClassSymbol := Info.Execution.Prog.RootTable.FindSymbol(ItemClassName, cvMagic);
  if (ItemClassSymbol = nil) then
    raise EScriptException.CreateFmt('ItemClass not found: %s', [ItemClassName]);

  Info.ResultAsInteger := Int64(ItemClassSymbol);
Community
  • 1
  • 1
SpeedFreak
  • 876
  • 6
  • 16

1 Answers1

2

Unless I misundertood, you probably shouldn't be looking up the symbol table for your last part, but instead maintain a table of the registered item classes in dwsUnitMyClassesFunctionsRegisterItemClassEval.

The rationale behind that could be that the user could have two 'TMyItem' symbols, in two different contexts, but only one registered. The registered one is the one you want, and I don't think there is a reliable way to figure out the relevant symbol otherwise (as the context that matter wouldn't be the one where you're trying to resolve the string back to a symbol, but the one where the symbol & string were associated, ie. where it was registered)

Eric Grange
  • 5,931
  • 1
  • 39
  • 61
  • I see your point. I would have expected the symbol lookup to use the normal scoping rules, so any later declaration of a class of the same name would override the previous one (provided of course that the previous was still in scope). I cannot maintain a symbol table on the unit, because the unit can be shared among many different independent scripts, but I might be able to simple store a pointer to the symbol on the object that already store the classname. I'm a bit worried though that the symbol might get deleted behind my back, leaving me with an invalid reference. – SpeedFreak Apr 16 '14 at 11:14
  • [...continuing...]There also seems to be something missing when it comes to creating an instance of TMyItem. Since the symbol cannot be found by a normal symbol lookup I cannot use the usual `Info.Vars['TMyItem'].GetConstructor`. Instead I've had to call the protected `TProgramInfo.GetSymbolInfo` to create an IInfo object and call the constructor on that (which in turn lead me to [this bug](https://code.google.com/p/dwscript/issues/detail?id=465)). – SpeedFreak Apr 16 '14 at 11:15
  • You're not finding it because of the normal scoping rule, the function where you're trying to look it up is part of a unit where the user class isn't in scope, hence you have to go back to the program's root table to see it. You can pass that root table to a new TProgramInfo.Table property if you want to have an IInfo at the program level. As for the lists, you can have a per-execution Environment, where you can store custom stuff (see the WebEnvironement for an example) – Eric Grange Apr 17 '14 at 12:01
  • I'm trying to resolve the symbol from the same script where I declared it, so I would expect it to be in scope. Maybe it would be better if I made a small sample that reproduces the problem and submitted it as a bug? I don't understand what you mean wrt `TProgramInfo.Table`. I'm already using a custom environment to provide context to the script but I don't think that is the correct place to store the symbol information in this case. I'm AFK ATM but I'll explainn why when I get back. – SpeedFreak Apr 17 '14 at 13:08
  • I thought you were trying to resolve from a function in a TdwsUnit? (if so your code is in the context of the TdwsUnit, ie. where it's implemented, not the unit that calls the function). Anyway, you might want to submit an issue in the googlecode tracker with details, that'll be more convenient than comments . – Eric Grange Apr 18 '14 at 15:23
  • I _am_ resolving the symbol name from a TdswUnit declared function so it makes sense that the context passed to the function implementation is that of the unit. Is there any way to gain access to the caller scope (i.e. the script scope) instead? I've now accepted your answer as it seems like a possible solution although it isn't the one I will end up using. – SpeedFreak Apr 18 '14 at 20:55