加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

delphi – Rtti访问复杂数据结构中的字段和属性

发布时间:2020-12-15 04:12:44 所属栏目:大数据 来源:网络整理
导读:正如在 Rtti data manipulation and consistency in Delphi 2010中已经讨论的那样,通过使用一对TRttiField和一个实例指针访问成员,可以达到原始数据和rtti值之间的一致性.在仅具有基本成员类型(例如整数或字符串)的简单类的情况下,这将是非常容易的. 但是,如
正如在 Rtti data manipulation and consistency in Delphi 2010中已经讨论的那样,通过使用一对TRttiField和一个实例指针访问成员,可以达到原始数据和rtti值之间的一致性.在仅具有基本成员类型(例如整数或字符串)的简单类的情况下,这将是非常容易的.
但是,如果我们有结构化的字段类型呢?

这是一个例子:

TIntArray = array [0..1] of Integer;

TPointArray = array [0..1] of Point;

TExampleClass = class
  private
    FPoint : TPoint;
    FAnotherClass : TAnotherClass;
    FIntArray : TIntArray;
    FPointArray : TPointArray;
  public  
    property Point : TPoint read FPoint write FPoint; 
    //.... and so on
end;

为了方便成员访问,我想构建一个成员节点树,它提供了获取和设置值,获取属性,序列化/反序列化值等的接口.

TMemberNode = class
  private
    FMember : TRttiMember;
    FParent : TMemberNode;
    FInstance : Pointer;
  public
    property Value : TValue read GetValue write SetValue; //uses FInstance
end;

因此,最重要的是获取/设置值,这是通过使用TRttiField的GetValue和SetValue函数完成的 – 如前所述.

那么FPoint成员的实例是什么?假设Parent是TExample类的Node,其中实例是已知的,成员是一个字段,那么Instance将是:

FInstance := Pointer (Integer (Parent.Instance) + TRttiField (FMember).Offset);

但是,如果我想知道记录属性的实例呢?在这种情况下没有偏移.那么有一个更好的解决方案来获取指向数据的指针吗?

对于FAnotherClass成员,实例将是:

FInstance := Parent.Value.AsObject;

到目前为止,该解决方案仍然有效,并且可以使用rtti或原始类型完成数据操作,而不会丢失信息.

但是在处理数组时事情会变得更难.特别是第二个点数组.在这种情况下,如何获得积分成员的实例?

解决方法

TRttiField.GetValue,其中字段的类型是值类型,可以获得副本.这是设计的. TValue.MakeWithoutCopy用于管理接口和字符串等内容的引用计数;它不是为了避免这种复制行为. TValue故意不是为了模仿Variant的ByRef行为而设计的,在这种行为中,您最终可能会引用(例如)TValue中的堆栈对象,从而增加过时指针的风险.这也是违反直觉的;当你说GetValue时,你应该期待一个值,而不是一个引用.

当存储在其他结构中时,操纵值类型值的最有效方法可能是退后一步并添加另一个间接级别:通过计算偏移而不是直接使用TValue为沿着路径的所有中间值类型步骤项目.

这可以相当简单地封装.我花了大约一个小时写了一个小的TLocation记录,它使用RTTI来做到这一点:

type
  TLocation = record
    Addr: Pointer;
    Typ: TRttiType;
    class function FromValue(C: TRttiContext; const AValue: TValue): TLocation; static;
    function GetValue: TValue;
    procedure SetValue(const AValue: TValue);
    function Follow(const APath: string): TLocation;
    procedure Dereference;
    procedure Index(n: Integer);
    procedure FieldRef(const name: string);
  end;

function GetPathLocation(const APath: string; ARoot: TLocation): TLocation; forward;

{ TLocation }

type
  PPByte = ^PByte;

procedure TLocation.Dereference;
begin
  if not (Typ is TRttiPointerType) then
    raise Exception.CreateFmt('^ applied to non-pointer type %s',[Typ.Name]);
  Addr := PPointer(Addr)^;
  Typ := TRttiPointerType(Typ).ReferredType;
end;

procedure TLocation.FieldRef(const name: string);
var
  f: TRttiField;
begin
  if Typ is TRttiRecordType then
  begin
    f := Typ.GetField(name);
    Addr := PByte(Addr) + f.Offset;
    Typ := f.FieldType;
  end
  else if Typ is TRttiInstanceType then
  begin
    f := Typ.GetField(name);
    Addr := PPByte(Addr)^ + f.Offset;
    Typ := f.FieldType;
  end
  else
    raise Exception.CreateFmt('. applied to type %s,which is not a record or class',[Typ.Name]);
end;

function TLocation.Follow(const APath: string): TLocation;
begin
  Result := GetPathLocation(APath,Self);
end;

class function TLocation.FromValue(C: TRttiContext; const AValue: TValue): TLocation;
begin
  Result.Typ := C.GetType(AValue.TypeInfo);
  Result.Addr := AValue.GetReferenceToRawData;
end;

function TLocation.GetValue: TValue;
begin
  TValue.Make(Addr,Typ.Handle,Result);
end;

procedure TLocation.Index(n: Integer);
var
  sa: TRttiArrayType;
  da: TRttiDynamicArrayType;
begin
  if Typ is TRttiArrayType then
  begin
    // extending this to work with multi-dimensional arrays and non-zero
    // based arrays is left as an exercise for the reader ... :)
    sa := TRttiArrayType(Typ);
    Addr := PByte(Addr) + sa.ElementType.TypeSize * n;
    Typ := sa.ElementType;
  end
  else if Typ is TRttiDynamicArrayType then
  begin
    da := TRttiDynamicArrayType(Typ);
    Addr := PPByte(Addr)^ + da.ElementType.TypeSize * n;
    Typ := da.ElementType;
  end
  else
    raise Exception.CreateFmt('[] applied to non-array type %s',[Typ.Name]);
end;

procedure TLocation.SetValue(const AValue: TValue);
begin
  AValue.Cast(Typ.Handle).ExtractRawData(Addr);
end;

此类型可用于使用RTTI在值内导航位置.为了使它更容易使用,我写的更有趣,我还写了一个解析器 – Follow方法:

function GetPathLocation(const APath: string; ARoot: TLocation): TLocation;

  { Lexer }

  function SkipWhite(p: PChar): PChar;
  begin
    while IsWhiteSpace(p^) do
      Inc(p);
    Result := p;
  end;

  function ScanName(p: PChar; out s: string): PChar;
  begin
    Result := p;
    while IsLetterOrDigit(Result^) do
      Inc(Result);
    SetString(s,p,Result - p);
  end;

  function ScanNumber(p: PChar; out n: Integer): PChar;
  var
    v: Integer;
  begin
    v := 0;
    while (p >= '0') and (p <= '9') do
    begin
      v := v * 10 + Ord(p^) - Ord('0');
      Inc(p);
    end;
    n := v;
    Result := p;
  end;

const
  tkEof = #0;
  tkNumber = #1;
  tkName = #2;
  tkDot = '.';
  tkLBracket = '[';
  tkRBracket = ']';

var
  cp: PChar;
  currToken: Char;
  nameToken: string;
  numToken: Integer;

  function NextToken: Char;
    function SetToken(p: PChar): PChar;
    begin
      currToken := p^;
      Result := p + 1;
    end;
  var
    p: PChar;
  begin
    p := cp;
    p := SkipWhite(p);
    if p^ = #0 then
    begin
      cp := p;
      currToken := tkEof;
      Exit(currToken);
    end;

    case p^ of
      '0'..'9':
      begin
        cp := ScanNumber(p,numToken);
        currToken := tkNumber;
      end;

      '^','[',']','.': cp := SetToken(p);

    else
      cp := ScanName(p,nameToken);
      if nameToken = '' then
        raise Exception.Create('Invalid path - expected a name');
      currToken := tkName;
    end;

    Result := currToken;
  end;

  function Describe(tok: Char): string;
  begin
    case tok of
      tkEof: Result := 'end of string';
      tkNumber: Result := 'number';
      tkName: Result := 'name';
    else
      Result := '''' + tok + '''';
    end;
  end;

  procedure Expect(tok: Char);
  begin
    if tok <> currToken then
      raise Exception.CreateFmt('Expected %s but got %s',[Describe(tok),Describe(currToken)]);
  end;

  { Semantic actions are methods on TLocation }
var
  loc: TLocation;

  { Driver and parser }

begin
  cp := PChar(APath);
  NextToken;

  loc := ARoot;

  // Syntax:
  // path ::= ( '.' <name> | '[' <num> ']' | '^' )+ ;;

  // Semantics:

  // '<name>' are field names,'[]' is array indexing,'^' is pointer
  // indirection.

  // Parser continuously calculates the address of the value in question,// starting from the root.

  // When we see a name,we look that up as a field on the current type,// then add its offset to our current location if the current location is 
  // a value type,or indirect (PPointer(x)^) the current location before 
  // adding the offset if the current location is a reference type. If not
  // a record or class type,then it's an error.

  // When we see an indexing,we expect the current location to be an array
  // and we update the location to the address of the element inside the array.
  // All dimensions are flattened (multiplied out) and zero-based.

  // When we see indirection,we expect the current location to be a pointer,// and dereference it.

  while True do
  begin
    case currToken of
      tkEof: Break;

      '.':
      begin
        NextToken;
        Expect(tkName);
        loc.FieldRef(nameToken);
        NextToken;
      end;

      '[':
      begin
        NextToken;
        Expect(tkNumber);
        loc.Index(numToken);
        NextToken;
        Expect(']');
        NextToken;
      end;

      '^':
      begin
        loc.Dereference;
        NextToken;
      end;

    else
      raise Exception.Create('Invalid path syntax: expected ".","[" or "^"');
    end;
  end;

  Result := loc;
end;

这是一个示例类型,以及一个操作它的例程(P):

type
  TPoint = record
    X,Y: Integer;
  end;
  TArr = array[0..9] of TPoint;

  TFoo = class
  private
    FArr: TArr;
    constructor Create;
    function ToString: string; override;
  end;

{ TFoo }

constructor TFoo.Create;
var
  i: Integer;
begin
  for i := Low(FArr) to High(FArr) do
  begin
    FArr[i].X := i;
    FArr[i].Y := -i;
  end;
end;

function TFoo.ToString: string;
var
  i: Integer;
begin
  Result := '';
  for i := Low(FArr) to High(FArr) do
    Result := Result + Format('(%d,%d) ',[FArr[i].X,FArr[i].Y]);
end;

procedure P;
var
  obj: TFoo;
  loc: TLocation;
  ctx: TRttiContext;
begin
  obj := TFoo.Create;
  Writeln(obj.ToString);

  ctx := TRttiContext.Create;

  loc := TLocation.FromValue(ctx,obj);
  Writeln(loc.Follow('.FArr[2].X').GetValue.ToString);
  Writeln(obj.FArr[2].X);

  loc.Follow('.FArr[2].X').SetValue(42);
  Writeln(obj.FArr[2].X); // observe value changed

  // alternate syntax,not using path parser,but location destructive updates
  loc.FieldRef('FArr');
  loc.Index(2);
  loc.FieldRef('X');
  loc.SetValue(24);
  Writeln(obj.FArr[2].X); // observe value changed again

  Writeln(obj.ToString);
end;

原则可以扩展到其他类型和Delphi表达式语法,或者可以更改TLocation以返回新的TLocation实例而不是破坏性自我更新,或者可以支持非平面数组索引等.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读