четверг, 15 марта 2012 г.

Variant := TObject.Create ?

  Думаю многие сталкивались с тем, что иногда требуется в variant передавать blob, ну или TObject. В таких языках как Java и C# все проще, элементарные типы являются объектами ну или умеют упаковываться из значимых типов в объекты. Для всех типов в языке есть базовый тип. В Delphi же, когда тип на этапе компиляции не известен единственным универсальным решением является использование variant. 
  Но не все так гладко, тип Variant предназначен только для хранения простых типов и указатель на объект в него не поместить не используя грязных хаков. Хотя сам RTL ими так и кишит. Так для хранения ftBlob в variant используется строка. 
На самом деле есть "законный"  механизм как через Variant передавать любые данные, в том числе и объекты. Для этого есть базовый класс TCustomVariantType, на основе которого можно определить свой тип который можно будет передавать как Variant, перекрыть в нем бинарные арифметические  и логические операции. Т.е. реализовать например свое сравнению и сложение для экземпляров типа. 
Но вот тут мы подходим к самому интересному, в FireMonkey появились методы по преобразованию variant к TObject и обратно:

function VarIsObject(Value: Variant): Boolean;
function ObjectToVariant(const AObject: TObject): Variant;
function VariantToObject(const Value: Variant): TObject;

Выглядит интригующе, неправда ли? 
Но что внутри?
Совершенно обычный хак, все опять сделано через строки:

function ObjectToVariant(const AObject: TObject): Variant;
begin
  Result := 'vgobj' + IntToStr(Integer(Pointer(AObject)));
end;

function VariantToObject(const Value: Variant): TObject;
var
  S: string;
begin
  S := Value;
{$IFDEF FPCCOMP}
  if (S <> '') and (Pos(WideString('vgobj'), S) = 1) then
{$ELSE}
  if (S <> '') and (Pos('vgobj', S) = 1) then
{$ENDIF}
    Result := TObject(Pointer(StrToInt(Copy(S, 6, 10))))
  else
    Result := nil;
end;

...и расположено это все не в модули System или Variants, а в Types. Никакой поддержки на уровне компилятора нет. Т.е. все еще нельзя писать так
var 
  v: Variant;
begin
   v := TMyClass.Create();
end;

  Почему бы Embarcadero уже не реализовать нормальную поддержку объектов в variant, ну или хотя бы сделать на основе TCustomVariantType. Непонятно. Притом, что есть и наследники от него TInvokeableVariantType и TPublishableVariantType.


  Возможно, все ради совместимости с COM Variant, не хотят далеко от него уходить и пытаются сохранить Variant для типов передающихся по значению. Или, возможно, методы VariantToObject и ObjectToVariant были реализованы Евгением Крюковым KSDev, а в Embarcadero не стали ничего переделывать, работает и на том спасибо.

5 комментариев:

  1. интересно, почему используется Integer(Pointer(AObject)) с одной стороны AObject и так указатель, т.е. pointer(aObject) не нужен. с другой стороны, что является явной ошибкой, почему использован Integer, a не NativeInt?

    ОтветитьУдалить
  2. А зачем вам понадобилось через вариант передавать объекты?

    А вообще согласен, приведенная реализация довольно странная - во-первых, в переменную типа вариант сохраняется ссылка на строку, которая содержит текстовое представление адреса экземпляра объекта - слишком много действий для преобразований туда-обратно. Во-вторых, никакой автоматизации (копирование,удаление и т.п.), и никакой совместимости с внешним миром (COM).

    Кстати тоже заметил, что в исходниках FM встречаются вещи, явно сделанные на скорую руку. С одной стороны оно понятно - осознать такой объем кода и внести необходимые корректировки - нужно много времени, а продукт хочется выпустить уже сейчас (чтобы народ начал использовать и получать фидбэк). А с другой стороны, как-то боязно это начинать использовать в коммерческих продуктах.

    ОтветитьУдалить
    Ответы
    1. Это может понадобиться, например, если используется какой нибудь скриптовый движок с динамической типизацией.

      Удалить
  3. Варианты вполне себе поддерживают интерфейсы "из коробки". Поэтому вместо объектов можно просто интерфейсы использовать.

    P.S. Не уверен, но может в очень старых версиях Delphi поддержки интерфейсов у вариантов и нет...

    ОтветитьУдалить
  4. Для Delphi 2010+ любитель написал http://alex.ciobanu.org/?p=478

    ОтветитьУдалить