Есть ли способ обновить поле в записи, зная имя и значение поля - PullRequest
20 голосов
/ 23 июня 2011

Учитывая запись:

MyRecord = record
    Company: string;
    Address: string;
    NumberOfEmplyees: integer;

Вы можете написать вызов функции, как

function UpdateField(var FieldName: string; FieldValue: variant): bool;

так что:

UpdateField('Company', 'ABC Co');

будет обновлять MyRecord.Company до 'ABC Co'?

Я искал пример, но все, что я нашел, для базы данных. Любая помощь, указывающая мне правильное направление, приветствуется.

Спасибо, Charles

Ответы [ 2 ]

12 голосов
/ 23 июня 2011

Что Delphi 7 RTTI знает и может извлечь из TypeInfo(aRecordType), это:

  • Имя типа записи;
  • Рекордный глобальный размер;
  • Смещение и тип каждой переменной с подсчетом ссылок в записи (строка / вариант / широкая строка / динамический массив / другая вложенная запись, содержащая переменные с подсчетом ссылок).

Последняя информация необходима для освобождения памяти, используемой каждой переменной с подсчетом ссылок внутри записи, или для копирования содержимого записи во время выполнения. Инициализация записи также выполняется либо в сгенерированном компилятором коде (если запись создается в стеке), либо с помощью метода _InitializeRecord(), либо с глобальным заполнением до 0, когда создается экземпляр класса или динамического массива.

Это одинаково для типов record и object во всех версиях Delphi.

Вы можете заметить, что в современной версии 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;

Используя эти данные, вот, например, что вы можете сделать:

Например, вот как можно сравнить две записи одного типа, используя этот 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;

Итак, для вашей цели то, что Delphi 7 RTTI мог сообщить вам во время выполнения, - это положение каждой строки в записи. Используя приведенный выше код, вы можете легко создать функцию, используя индекс поля:

     procedure UpdateStringField(StringFieldIndex: integer; const FieldValue: string);

Но у вас просто нет необходимой информации для выполнения вашего запроса:

  • Имена полей не сохраняются в RTTI (только имя глобального типа записи и даже не всегда AFAIK);
  • Смещение имеют только поля с подсчетом ссылок, а не другие поля простого типа (например, целое / двойное ...).

Если вам действительно нужна эта функция, единственным решением в Delphi 7 является использование не записей, а классов.

В Delphi 7, если вы создадите класс с опубликованными полями, у вас будет вся необходимая информация для всех опубликованных полей. Затем вы можете обновить содержание такого опубликованного поля. Это то, что делает среда выполнения VCL при десериализации содержимого .dfm в экземпляры классов или при использовании подхода ORM .

.
7 голосов
/ 23 июня 2011

Вам нужны современные версии Delphi, чтобы делать то, что вы просите, не прибегая к ручному кодированию поисков, например. через стол.

Обновленный RTTI, представленный в Delphi 2010, может поддерживать то, что вы ищете, но в Delphi 7 нет ничего, что могло бы сделать это для записей.

...