Variant Types


O Help do Delphi 7, cita dois tipos de Variants: Variant arrays e OleVariant.

Abaixo extraímos da unit Variants as principais funções para trabalhar com tipos Variants, ou seja, as funções abaixo servem para trabalhar com tipos variants que armazenam tipos primitivos de dados, que armazenam dados em Variant Arrays (que é um tipo especial de array estático criado com chamadas as funções VarArrayCreate ou VarArrayOf) ou dados armazenados em OleVariant.


function VarType(const V: Variant): TVarType;
function VarAsType(const V: Variant; AVarType: TVarType): Variant;
function VarIsType(const V: Variant; AVarType: TVarType): Boolean; overload;
function VarIsType(const V: Variant; const AVarTypes: array of TVarType): Boolean; overload;
function VarIsByRef(const V: Variant): Boolean;

function VarIsEmpty(const V: Variant): Boolean;
procedure VarCheckEmpty(const V: Variant);
function VarIsNull(const V: Variant): Boolean;
function VarIsClear(const V: Variant): Boolean;

function VarIsCustom(const V: Variant): Boolean;
function VarIsOrdinal(const V: Variant): Boolean;
function VarIsFloat(const V: Variant): Boolean;
function VarIsNumeric(const V: Variant): Boolean;
function VarIsStr(const V: Variant): Boolean;

function VarToStr(const V: Variant): string;
function VarToStrDef(const V: Variant; const ADefault: string): string;
function VarToWideStr(const V: Variant): WideString;
function VarToWideStrDef(const V: Variant; const ADefault: WideString): WideString;

function VarToDateTime(const V: Variant): TDateTime;
function VarFromDateTime(const DateTime: TDateTime): Variant;

function VarInRange(const AValue, AMin, AMax: Variant): Boolean;
function VarEnsureRange(const AValue, AMin, AMax: Variant): Variant;

function VarSameValue(const A, B: Variant): Boolean;
function VarCompareValue(const A, B: Variant): TVariantRelationship;

function VarIsEmptyParam(const V: Variant): Boolean;

function VarIsError(const V: Variant; out AResult: HRESULT): Boolean; overload;
function VarIsError(const V: Variant): Boolean; overload;
function VarAsError(AResult: HRESULT): Variant;

function VarSupports(const V: Variant; const IID: TGUID; out Intf): Boolean; overload;
function VarSupports(const V: Variant; const IID: TGUID): Boolean; overload;

procedure VarCopyNoInd(var Dest: Variant; const Source: Variant);

function VarIsArray(const A: Variant): Boolean; overload;
function VarIsArray(const A: Variant; AResolveByRef: Boolean): Boolean; overload;

function VarArrayCreate(const Bounds: array of Integer; AVarType: TVarType): Variant;
function VarArrayOf(const Values: array of Variant): Variant;

function VarArrayRef(const A: Variant): Variant;

function VarTypeIsValidArrayType(const AVarType: TVarType): Boolean;
function VarTypeIsValidElementType(const AVarType: TVarType): Boolean;

function VarArrayDimCount(const A: Variant): Integer;
function VarArrayLowBound(const A: Variant; Dim: Integer): Integer;
function VarArrayHighBound(const A: Variant; Dim: Integer): Integer;

function VarArrayLock(const A: Variant): Pointer;
procedure VarArrayUnlock(const A: Variant);

function VarArrayAsPSafeArray(const A: Variant): PVarArray;

function VarArrayGet(const A: Variant; const Indices: array of Integer): Variant;
procedure VarArrayPut(var A: Variant; const Value: Variant; const Indices: array of Integer);

procedure DynArrayToVariant(var V: Variant; const DynArray: Pointer; TypeInfo: Pointer);
procedure DynArrayFromVariant(var DynArray: Pointer; const V: Variant; TypeInfo: Pointer);

function Unassigned: Variant;
function Null: Variant;



Neste mini-artigo queremos falar especialmente sobre a utilidade da função VarType.
A função VarType retorna um código ou constante de tipo para o variant passado como parâmetro para a função VarType.

Veja a sintaxe:
function VarType(const V: Variant): TVarType;

E abaixo segue uma lista de contants que representam o código do tipo retornado

           
VarTypeDescrição do tipo retornado
varEmptyThe variant is Unassigned.
varNullThe variant is Null.
varSmallint16-bit signed integer (type Smallint in Delphi, short in C++ ).
varInteger32-bit signed integer (type Integer in Delphi, int in C++).
varSingleSingle-precision floating-point value (type Single in Delphi, float in C++).
varDoubleDouble-precision floating-point value (type double).
varCurrencyCurrency floating-point value (type Currency).
varDate
Date and time value (type TDateTime).
varOleStrReference to a dynamically allocated UNICODE string.
varDispatchReference to an Automation object (an IDispatch interface pointer).
varErrorOperating system error code.
varBoolean16-bit boolean (type WordBool).
varVariantA variant.
varUnknownReference to an unknown object (an IInterface or IUnknown interface pointer).
varShortInt8-bit signed integer (type ShortInt in Delphi or signed char in C++)
varByteA Byte
varWordunsigned 16-bit value (Word)
varLongWordunsigned 32-bit value (type LongWord in Delphi or unsigned long in C++)
varInt6464-bit signed integer (Int64 in Delphi or __int64 in C++)
varStrArgCOM-compatible string.
varStringReference to a dynamically allocated string (not COM compatible).
varAny A CORBA Any value.


E estas constantes estão definidas na Unit System;

const

  varEmpty    = $0000; { vt_empty        0 }
  varNull     = $0001; { vt_null         1 }
  varSmallint = $0002; { vt_i2           2 }
  varInteger  = $0003; { vt_i4           3 }
  varSingle   = $0004; { vt_r4           4 }
  varDouble   = $0005; { vt_r8           5 }
  varCurrency = $0006; { vt_cy           6 }
  varDate     = $0007; { vt_date         7 }
  varOleStr   = $0008; { vt_bstr         8 }
  varDispatch = $0009; { vt_dispatch     9 }
  varError    = $000A; { vt_error       10 }
  varBoolean  = $000B; { vt_bool        11 }
  varVariant  = $000C; { vt_variant     12 }
  varUnknown  = $000D; { vt_unknown     13 }
  varShortInt = $0010; { vt_i1          16 }
  varByte     = $0011; { vt_ui1         17 }
  varWord     = $0012; { vt_ui2         18 }
  varLongWord = $0013; { vt_ui4         19 }
  varInt64    = $0014; { vt_i8          20 }
  varStrArg   = $0048; { vt_clsid       72 }
  varString   = $0100; { Pascal string 256 } {not OLE compatible }
  varAny      = $0101; { Corba any     257 } {not OLE compatible }


Onde o tema pode ser útil?

Uma situação que enfrentamos foi a seguinte: em uma aplicação datasnap multicamadas com delphi 2010 (tradicional com uso de componentes) passamos parâmetros no evento BeforeGetRecords do TClientDataSet. Veja o código:

procedure TnbaseGrade.ClientDataSetBeforeGetRecords(Sender: TObject; var OwnerData: OleVariant);
var
   Params: TParams;
begin
   Params := TParams.Create();
   Params.CreateParam(ftString, 'TABELA', ptInput).AsString := FTabela;
   Params.CreateParam(ftString, 'OUTROSCAMPOSSELECT', ptInput).AsString := '';
   Params.CreateParam(ftString, FTabela, ptInput).AsString := EditConsulta.Text;
   OwnerData := PackageParams(Params, [ptInput]);  
end;

E no lado servidor, recebemos os parâmetros no evento BeforeGetRecords do DataSetProvider, onde temos uma rotina que monta uma sql dinamicamente com estes parâmetros passados e seta a propriedade CommandText do SqlDataSet, no qual deixamos fixo em tempo de projeto um "select * from tabela". Nosso objetivo não é mostrar aqui a rotina, e sim, expôr um problema para o qual demoramos encontrar a solucão, pois achávamos que era um bug do delphi 2010. Veja o código:

procedure TsmCadastro.DspDisciplinaBeforeGetRecords(Sender: TObject; var OwnerData: OleVariant);
begin
   DstDisciplina.CommandText := MontaSql(OwnerData);
end;


A instrução acima funciona perfeitamente em tempo de execução. Porém em tempo de projeto, as vezes, ativamos no lado cliente nosso TClientDataSet para testar se tudo ficou configurado corretamente. Porém. em tempo de projeto os parâmetros não são passados e aí acontecia erros do tipo: "Remote Error: List index out of Bounds(0)", pois dentro de nossa rotina MontaSql estávamos tentando acessar o params[0] que deveria ter sempre nossa tabela, o params[1] que deveria ter os campos do select e os demais índices de params[2] até params[n-1] em que deveriam estar as condições para o where.

Como tratar este problema?

Poderíamos passar a sql original mudando a assinatura de nossa MontaSql para:
DstDisciplina.CommandText := MontaSql(OwnerData, DstDisciplina.CommandText) onde também passaríamos a sql definida em tempo de projeto e dentro de nossa rotina MontaSql podíamos fazer um teste para verificar se params.Count é maior que zero.

Ex.
   if params.Count > 0 then
   begin
     //processa params
   end
   else
      Result := sqlOriginal; //isto manteria a instrução sql fixada em tempo de projeto "select * from tabela"

Mas gostaríamos de não chamar a rotina MontaSql se o OwnerData não tiver nenhum parâmetro.

Como resolver então?

Aí que entra as funções para operar sobre tipos variants.

Mudamos então o código do evento BeforeGetRecords do DataSetProvider para:

procedure TsmCadastro.DspDisciplinaBeforeGetRecords(Sender: TObject; var OwnerData: OleVariant);
begin
   if VarType(OwnerData) <> varEmpty then //leia se VarType de (OwnerData) diferente de Unassigned então
      DstDisciplina.CommandText := MontaSql(OwnerData, DstDisciplina.CommandText);

   //ou esta solução que é mais direta
   //if TVarData(OwnerData).VType <> varEmpty then
      //DstDisciplina.CommandText := MontaSql(OwnerData, DstDisciplina.CommandText);

   //ou esta solução
   //if OwnerData <> varEmpty then
      //DstDisciplina.CommandText := MontaSql(OwnerData, DstDisciplina.CommandText);
end;

Com isto, garantimos que em tempo de projeto, ao ativar o ClientDataSet no lado cliente, nossa rotina MontaSql não será invocada no lado servidor, e o SqlDataSet será executado com a instrução definida em tempo de projeto.

Espero poder ajudar outros programadores iniciantes em DataSnap, que certamente iriam colher este mesmo erro ao implementar este tipo de solução com querys dinâmicas, independente se passar parâmetros com Variants Arrays, com TParams ou via TClientDataSet especializados, sendo que neste último, se não testássemos dentro do MontaSql se o TClientDataSet é vazio, quando ele não tiver registro o erro seria: "Invalid data packet", ao tentar fazer esta atribuição: DstDisciplina.Data := AOwnerData;

Em resumo, se testar-mos sempre o OwnerData antes de chamar a linha de comando DstDisciplina.CommandText := MontaSql(OwnerData) estaremos impedindo o erro em tempo de projeto indepente se os parâmetros são passados com Variants Arrays, com TParams, com TClientDataSet ou outra solução. E teremos uma solução unificada para validação dos parâmetros.

Abraços e até a próxima