delphidwscript

DWScript: How to read a meta class parameter from Delphi side


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);

Solution

  • 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)