有没有办法在知道字段名称和值的记录中更新字段

Cha*_*les 20 delphi rtti delphi-7

鉴于记录:

MyRecord = record
    Company: string;
    Address: string;
    NumberOfEmplyees: integer;
Run Code Online (Sandbox Code Playgroud)

你能写一个函数调用吗?

function UpdateField(var FieldName: string; FieldValue: variant): bool;
Run Code Online (Sandbox Code Playgroud)

以便:

UpdateField('Company', 'ABC Co');
Run Code Online (Sandbox Code Playgroud)

将MyRecord.Company更新为'ABC Co'?

我找了一个例子,但我发现的一切都是为了数据库.任何指导我正确方向的帮助表示赞赏.

谢谢,查尔斯

Arn*_*hez 12

Delphi 7 RTTI知道并可以从中检索的TypeInfo(aRecordType)是:

  • 记录类型名称;
  • 记录全球规模;
  • 记录中每个引用计数变量的偏移量和类型(字符串/变体/宽带/动态数组/包含引用计数变量的其他嵌套记录).

必须使用最新信息释放记录中每个引用计数变量使用的内存,或者在运行时复制记录内容.记录的初始化也可以在编译器生成的代码中执行(如果在堆栈上创建记录),可以通过_InitializeRecord()方法执行,也可以在实例化类或动态数组时将全局填充设置为0.

在所有版本的Delphi中,对于两种类型recordobject类型都是一样的.

您可以注意到现代版Delphi中存在一个错误(至少包括Delphi 2009和2010),有时不会创建用于在堆栈上初始化对象的代码.您将不得不使用记录,但它将破坏与以前版本的Delphi的兼容性.:(

以下是用于存储此RTTI数据的结构:

type
  TFieldInfo = packed record
    TypeInfo: ^PDynArrayTypeInfo; // information of the reference-counted type
    Offset: Cardinal; // offset of the reference-counted type in the record
  end;
  TFieldTable = packed record
    Kind: byte;
    Name: string[0]; // you should use Name[0] to retrieve offset of Size field
    Size: cardinal;  // global size of the record = sizeof(aRecord)
    Count: integer;  // number of reference-counted field info
    Fields: array[0..0] of TFieldInfo; // array of reference-counted field info
  end;
  PFieldTable = ^TFieldTable;
Run Code Online (Sandbox Code Playgroud)

使用此数据,例如您可以执行以下操作:

例如,下面是使用此RTTI如何比较两个相同类型的记录:

/// check equality of two records by content
// - will handle packed records, with binaries (byte, word, integer...) and
// string types properties
// - will use binary-level comparison: it could fail to match two floating-point
// values because of rounding issues (Currency won't have this problem)
function RecordEquals(const RecA, RecB; TypeInfo: pointer): boolean;
var FieldTable: PFieldTable absolute TypeInfo;
    F: integer;
    Field: ^TFieldInfo;
    Diff: cardinal;
    A, B: PAnsiChar;
begin
  A := @RecA;
  B := @RecB;
  if A=B then begin // both nil or same pointer
    result := true;
    exit;
  end;
  result := false;
  if FieldTable^.Kind<>tkRecord then
    exit; // raise Exception.CreateFmt('%s is not a record',[Typ^.Name]);
  inc(PtrUInt(FieldTable),ord(FieldTable^.Name[0]));
  Field := @FieldTable^.Fields[0];
  Diff := 0;
  for F := 1 to FieldTable^.Count do begin
    Diff := Field^.Offset-Diff;
    if Diff<>0 then begin
      if not CompareMem(A,B,Diff) then
        exit; // binary block not equal
      inc(A,Diff);
      inc(B,Diff);
    end;
    case Field^.TypeInfo^^.Kind of
      tkLString:
        if PAnsiString(A)^<>PAnsiString(B)^ then
          exit;
      tkWString:
        if PWideString(A)^<>PWideString(B)^ then
          exit;
      {$ifdef UNICODE}
      tkUString:
        if PUnicodeString(A)^<>PUnicodeString(B)^ then
          exit;
      {$endif}
      else exit; // kind of field not handled
    end;
    Diff := sizeof(PtrUInt); // size of tkLString+tkWString+tkUString in record
    inc(A,Diff);
    inc(B,Diff);
    inc(Diff,Field^.Offset);
    inc(Field);
  end;
  if CompareMem(A,B,FieldTable.Size-Diff) then
    result := true;
end;
Run Code Online (Sandbox Code Playgroud)

因此,为了您的目的,Delphi 7 RTTI可以在运行时通知您,记录中每个字符串的位置.使用上面的代码,您可以使用字段索引轻松创建函数:

     procedure UpdateStringField(StringFieldIndex: integer; const FieldValue: string);
Run Code Online (Sandbox Code Playgroud)

但是您根本没有实现您的请求所需的信息:

  • 字段名称不存储在RTTI中(仅限全局记录类型名称,甚至不总是AFAIK);
  • 只有引用计数字段才有偏移量,而不是其他简单类型的字段(如整数/双精度...).

如果你真的需要这个功能,Delphi 7下唯一的解决方案就是不使用记录,而是使用类.

在Delphi 7上,如果您创建一个包含已发布字段的类,您将拥有所有已发布字段的所有必需信息.然后您可以更新此类已发布的字段内容.这是VCL运行时在将.dfm内容反序列化为类实例或使用ORM方法时所执行的操作.


Dav*_*nan 7

您需要现代版本的Delphi来执行您的要求,而无需手动编写查找代码,例如通过表格.

Delphi 2010中引入的更新的RTTI可以支持您正在寻找的内容,但Delphi 7中没有任何内容可以用于记录.