unit uROBinaryHelpers; {----------------------------------------------------------------------------} { RemObjects SDK Library - Core Library } { } { compiler: Delphi 5 and up, Kylix 2 and up } { platform: Win32, Linux } { } { (c)opyright RemObjects Software. all rights reserved. } { } { Using this code requires a valid license of the RemObjects SDK } { which can be obtained at http://www.remobjects.com. } {----------------------------------------------------------------------------} {$I RemObjects.inc} interface {ToDo: -cRO3 clean up this mess, rename the methods appropriately and group them depending on what they do (ie handle complex variants; do conversions of *plain* binaries, etc. } uses Classes, {$IFDEF FPC}Variants, {$ELSE}{$IFDEF VER140UP}Variants,{$ENDIF}{$ENDIF} uROTypes, uROClasses, FMTBcd; type EROBinaryConversionException = class(EROException); {$IFDEF FPC}OleVariant = Variant; {$ENDIF} function BinaryFromVariant(const iVariant:OleVariant):Binary; function VariantFromBinary(const iBinary:TStream):OleVariant; procedure WriteVariantBinaryToBinary(const iVariant:OleVariant; ioBinary:TStream); procedure WriteVariantArrayToBinary(const iVariant:OleVariant; ioBinary:TStream); function VariantArrayFromBinary(const iBinary:TStream; iType:word):OleVariant; function VariantBinaryFromBinary(const iBinary:TStream):OleVariant; procedure WriteVariantToBinary(const iVariant:OleVariant; ioBinary:TStream); function ReadVariantFromBinary(const iBinary:TStream):OleVariant; function VariantBinaryFromRawBinary(const iBinary:TStream):OleVariant; procedure VariantBinaryToRawBinary(const iVariant:OleVariant; ioBinary:TStream); function VariantBinaryToString(const iVariant:OleVariant):string; function Stream_ReadStringWithLength(iStream:TStream; iMaxLength:integer=-1): string; procedure Stream_WriteStringWithLength(iStream:TStream; const iString: string); {$IFDEF DEBUG_REMOBJECTS_VARIANTS} function TestVariant(iVariant:OleVariant):OleVariant; {$ENDIF DEBUG_REMOBJECTS_VARIANTS} type PDecimal = ^TDecimal; TDecimal = array[0..3] of Cardinal; function BCDToDecimal(const aBcd: TBcd): TDecimal; function DecimalToBCD(const aDecimal: TDecimal): TBcd; function DecimalToString(const aDecimal: TDecimal; aDot: Char): string; function StringToDecimal(const aString: string; aDot: Char): TDecimal; function DecimalToVariant(const aDecimal: TDecimal): Variant; function VariantToDecimal(const aVariant: Variant): TDecimal; function VariantToBCD(const aVariant: Variant): TBCD; function BCDToVariant(const aBCD: TBCD; const StoreAsDecimal: Boolean = False): Variant; { ToDo: extend this to handle all common Variant types, and use it for marshaling OwnerData, instead of string. } function VarByteArrayToDecimal(const aVariant: Variant; var aDecimal: TDecimal): Boolean; implementation uses {$IFDEF DEBUG_REMOBJECTS_VARIANTS}eDebugServer,{$ENDIF} SysUtils, uRORes; procedure WriteVariantToBinary(const iVariant:OleVariant; ioBinary:TStream); var lType:integer; lIntegerValue:integer; {$IFNDEF DELPHI5} lShortIntValue:shortint; lSmallIntValue: smallint; lInt64Value:Int64; {$ENDIF DELPHI5} lByteValue:byte; lDoubleValue:double; lSingleValue:single; lCurrencyValue:currency; lStringValue:string; begin lType := VarType(iVariant); {$IFDEF DEBUG_REMOBJECTS_VARIANTS} DebugServer.Write('BinaryFromVariant type=%d',[lType]); {$ENDIF DEBUG_REMOBJECTS_VARIANTS} { Array types } if (lType and $2000) = $2000 then begin case lType of $2011:WriteVariantBinaryToBinary(iVariant,ioBinary); {8209; handle Array of Byte special. for now. } else WriteVariantArrayToBinary(iVariant,ioBinary); end; end { Plain Types } else begin case lType of varEmpty,varNull,varError:begin { 0, 1, A } ioBinary.Write(lType,sizeof(integer)); end; {$IFNDEF DELPHI5} varShortInt:begin { 2, 10, 12 } lShortIntValue := iVariant; ioBinary.Write(lType,sizeof(integer)); ioBinary.Write(lShortIntValue,sizeof(shortint)); end; varSmallInt,varWord : begin lSmallIntValue := iVariant; ioBinary.Write(lType,sizeof(integer)); ioBinary.Write(lSmallIntValue,sizeof(lSmallIntValue)); end; {$ENDIF DELPHI5} {$IFNDEF DELPHI5}varLongWord,{$ENDIF DELPHI5} varInteger:begin { 3, 13 } lIntegerValue := iVariant; ioBinary.Write(lType,sizeof(integer)); ioBinary.Write(lIntegerValue,sizeof(integer)); end; varSingle:begin { 4 } lSingleValue := iVariant; ioBinary.Write(lType,sizeof(integer)); ioBinary.Write(lSingleValue,sizeof(single)); end; varDouble, varDate:begin { 5, 7 } lDoubleValue := iVariant; ioBinary.Write(lType,sizeof(integer)); ioBinary.Write(lDoubleValue,sizeof(double)); end; varCurrency:begin { 6 } lCurrencyValue := iVariant; ioBinary.Write(lType,sizeof(integer)); ioBinary.Write(lCurrencyValue,sizeof(currency)); end; varDispatch:begin { 9 } raise EROBinaryConversionException.CreateFmt(err_IDispatchMarshalingNotSupported,[VarType(iVariant)]); end; varBoolean:begin { B } if iVariant then lByteValue := 1 else lByteValue := 0; ioBinary.Write(lType,sizeof(integer)); ioBinary.Write(lByteValue,sizeof(byte)); end; varByte:begin { 11 } lByteValue := iVariant; ioBinary.Write(lType,sizeof(integer)); ioBinary.Write(lByteValue,sizeof(byte)); end; {$IFNDEF DELPHI5} varInt64:begin { 14 } lInt64Value := iVariant; ioBinary.Write(lType,sizeof(integer)); ioBinary.Write(lInt64Value,sizeof(Int64)); end; {$ENDIF DELPHI5} varOleStr: begin lStringValue := UTF8Encode(iVariant); lIntegerValue := Length(lStringValue); ioBinary.Write(lType,sizeof(integer)); ioBinary.Write(lIntegerValue,sizeof(integer)); ioBinary.Write(lStringValue[1],lIntegerValue); end; varString:begin { 8, 100 } lStringValue := iVariant; lIntegerValue := Length(lStringValue); ioBinary.Write(lType,sizeof(integer)); ioBinary.Write(lIntegerValue,sizeof(integer)); ioBinary.Write(lStringValue[1],lIntegerValue); end; else raise EROBinaryConversionException.CreateFmt(err_UnsupportedVariantType, [VarType(iVariant)]); end; { case } end; end; function BinaryFromVariant(const iVariant:OleVariant):Binary; begin result := Binary.Create(); WriteVariantToBinary(iVariant,result); {$IFDEF DEBUG_REMOBJECTS_VARIANTS} DebugServer.WriteHexDump('Written Binary',result); {$ENDIF DEBUG_REMOBJECTS_VARIANTS} result.Seek(0,soFromBeginning) end; function StringFromBinary(const iBinary:TStream):string; var lSize:integer; begin iBinary.Read(lSize,sizeof(integer)); SetLength(Result,lSize); iBinary.read(Result[1],lSize); end; function ReadVariantFromBinary(const iBinary:TStream):OleVariant; var lType:integer; lIntegerValue:integer; {$IFNDEF DELPHI5} lShortInt:shortint; lSmallIntValue: smallint; lInt64Value:Int64; {$ENDIF DELPHI5} lByteValue:byte; lDoubleValue:double; lSingleValue:single; lCurrencyValue:currency; lString: string; begin iBinary.Read(lType,sizeof(integer)); {$IFDEF DEBUG_REMOBJECTS_VARIANTS} DebugServer.Write('VariantFromBinary type=%d',[lType]); {$ENDIF DEBUG_REMOBJECTS_VARIANTS} { Array types } if (lType and $2000) = $2000 then begin case lType of $2011:result := VariantBinaryFromBinary(iBinary); else result := VariantArrayFromBinary(iBinary,(lType and $fff)); end; end { Plain Types } else begin case lType of varEmpty:result := Unassigned; varNull:result := Null; varError:result := EmptyParam; {$IFNDEF DELPHI5} varShortInt:begin iBinary.Read(lShortInt,sizeof(shortint)); result := lShortInt; end; varSmallInt,varWord : begin iBinary.Read(lSmallIntValue,sizeof(lSmallIntValue)); result := lSmallIntValue; end; {$ENDIF DELPHI5} {$IFNDEF DELPHI5} varLongWord, {$ENDIF DELPHI5} varInteger:begin iBinary.Read(lIntegerValue,sizeof(integer)); result := lIntegerValue; end; varSingle:begin iBinary.Read(lSingleValue,sizeof(single)); result := lSingleValue; end; varDouble:begin iBinary.Read(lDoubleValue,sizeof(double)); result := lDoubleValue; end; varCurrency:begin iBinary.Read(lCurrencyValue,sizeof(currency)); result := lcurrencyValue; end; varDate:begin iBinary.Read(lDoubleValue,sizeof(double)); result := TDateTime(lDoubleValue); end; varBoolean:begin iBinary.Read(lByteValue,sizeof(byte)); result := (lByteValue <> 0); end; varByte:begin iBinary.Read(lByteValue,sizeof(byte)); result := lByteValue; end; {$IFNDEF DELPHI5} varInt64:begin iBinary.Read(lInt64Value,sizeof(Int64)); result := lInt64Value; end; {$ENDIF DELPHI5} varOleStr: begin iBinary.Read(lIntegerValue,sizeof(integer)); SetLength(lString, lIntegerValue); iBinary.Read(lString[1], lIntegerValue); Result := UTF8Decode(lString); end; varString:Result := StringFromBinary(iBinary); else raise EROBinaryConversionException.CreateFmt(err_UnsupportedVariantType, [lType]); end; { case } end; end; function VariantFromBinary(const iBinary:TStream):OleVariant; begin if iBinary.Size < 4 then raise EROBinaryConversionException.Create(err_InvalidBinaryFormat); {$IFDEF DEBUG_REMOBJECTS_VARIANTS} DebugServer.WriteHexDump('Read Binary',iBInary); {$ENDIF DEBUG_REMOBJECTS_VARIANTS} iBinary.Seek(0,soFromBeginning); result := ReadVariantFromBinary(iBinary); end; procedure CheckVariant(const iVariant:OleVariant); begin // Introduced by AleF to address part of this check mess if not VarIsArray(iVariant) then raise EROBinaryConversionException.CreateFmt(err_VariantIsNotArray,[VarType(iVariant)]); if VarArrayDimCount(iVariant) <> 1 then raise EROBinaryConversionException.CreateFmt(err_InvalidVarArrayDimCount, [VarArrayDimCount(iVariant)]); end; procedure WriteVariantBinaryToBinary(const iVariant:OleVariant; ioBinary:TStream); var //l:longword; lType,lSize:integer; p:pointer; begin CheckVariant(iVariant); lSize := VarArrayHighBound(iVariant,1)-VarArrayLowBound(iVariant,1)+1; p := VarArrayLock(iVariant); try lType := VarType(iVariant); {$IFDEF DEBUG_REMOBJECTS_VARIANTS} DebugServer.Write('BinaryFromVariantBinary type=%d',[lType]); {$ENDIF DEBUG_REMOBJECTS_VARIANTS} ioBinary.Write(lType,sizeof(integer)); ioBinary.Write(lSize,sizeof(integer)); ioBinary.Write(p^,lSize); finally VarArrayUnlock(iVariant); end; end; procedure WriteVariantArrayToBinary(const iVariant:OleVariant; ioBinary:TStream); var lLowBound,lHighBound:integer; i:Integer; lType:integer; begin CheckVariant(iVariant); lType := VarType(iVariant); lLowBound := VarArrayLowBound(iVariant,1); lHighBound := VarArrayHighBound(iVariant,1); //l := VarArrayHighBound(iVariant,1)-VarArrayLowBound(iVariant,1)+1; {$IFDEF DEBUG_REMOBJECTS_VARIANTS} DebugServer.Write('WriteVariantArrayToBinary type=%d, bounds=%d..%d',[lType,lLowBound,lHighBound]); {$ENDIF DEBUG_REMOBJECTS_VARIANTS} ioBinary.Write(lType,sizeof(integer)); ioBinary.Write(lLowBound,sizeof(integer)); ioBinary.Write(lHighBound,sizeof(integer)); for i := lLowBound to lHighBound do begin WriteVariantToBinary(iVariant[i],ioBinary); end; end; function VariantBinaryFromBinary(const iBinary:TStream):OleVariant; var p:pointer; lSize:integer; begin iBinary.Read(lSize,sizeof(integer)); result := VarArrayCreate([0,lSize-1],varByte); p := VarArrayLock(result); try iBinary.Read(p^,lSize); finally VarArrayUnlock(result); end; end; function VariantArrayFromBinary(const iBinary:TStream; iType:word):OleVariant; var lLowBound,lHighBound:integer; i:Integer; begin iType := (iType and $fff); iBinary.Read(lLowBound,sizeof(integer)); iBinary.Read(lHighBound,sizeof(integer)); result := VarArrayCreate([lLowBound,lHighBound],iType); for i := lLowBound to lHighBound do begin result[i] := ReadVariantFromBinary(iBinary); end; end; {$IFDEF DEBUG_REMOBJECTS_VARIANTS} function TestVariant(iVariant:OleVariant):OleVariant; var lBinary:Binary; begin DebugServer.EnterMethod('Original varType '+IntToStr(VarType(iVariant))); lBinary := BinaryFromVariant(iVariant); try result := VariantFromBinary(lBinary); DebugServer.ExitMethod('Result varType '+IntToStr(VarType(result))); finally FreeAndNil(lBinary); end; end; {$ENDIF DEBUG_REMOBJECTS_VARIANTS} {-----------------------------------------------------------------------------} { Conversion fromto Binary "Array of Bytes" Variants to other data types { containing *just* the binary data, no additional type info. {-----------------------------------------------------------------------------} { Description: Will read the *remainder* of the iBinary stream into a new binary variant (aka array of bytes). } function VariantBinaryFromRawBinary(const iBinary:TStream):OleVariant; var p:pointer; lSize:integer; begin lSize := iBinary.Size-iBinary.Position; result := VarArrayCreate([0,lSize-1],varByte); p := VarArrayLock(result); try iBinary.Read(p^,lSize); finally VarArrayUnlock(result); end; end; { Description: Will write the data from the binary variant (aka array of bytes) to the current position of the iBinary stream. } procedure VariantBinaryToRawBinary(const iVariant:OleVariant; ioBinary:TStream); var lSize:integer; p:pointer; begin if (VarType(iVariant)=varEmpty) then Exit; CheckVariant(iVariant); lSize := VarArrayHighBound(iVariant,1)-VarArrayLowBound(iVariant,1)+1; p := VarArrayLock(iVariant); try ioBinary.Write(p^,lSize); finally VarArrayUnlock(iVariant); end; end; { Description: Will return the data from the binary variant (aka array of bytes) as a binary AnsiString } function VariantBinaryToString(const iVariant:OleVariant):string; var lSize:integer; p:pointer; begin if (VarType(iVariant)=varEmpty) then Exit; if not VarIsArray(iVariant) then begin Result:= VarToStr(iVariant); Exit; end; CheckVariant(iVariant); lSize := VarArrayHighBound(iVariant,1)-VarArrayLowBound(iVariant,1)+1; p := VarArrayLock(iVariant); try SetLength(result,lSize); if lSize > 0 then Move(p^,result[1],lSize); finally VarArrayUnlock(iVariant); end; end; function Stream_ReadStringWithLength(iStream:TStream; iMaxLength:integer=-1): string; var lLength:integer; lBytesRead:Integer; begin lBytesRead := iStream.Read(lLength, sizeof(lLength)); if lBytesRead <> sizeof(lLength) then RaiseError(err_UnexpectedEndOfStream,[]); if (iMaxLength > -1) and (lLength > iMaxLength) then RaiseError(err_InvalidStringLength,[lLength]); SetLength(result, lLength); if (lLength > 0) then iStream.Read(result[1], lLength); //ToDo: find a save and FAST way to do this in .NET end; procedure Stream_WriteStringWithLength(iStream:TStream; const iString: string); var lLength:integer; begin lLength := Length(iString); iStream.Write(lLength, SizeOf(lLength)); if (lLength > 0) then iStream.Write(iString[1], lLength); //ToDo: find a save and FAST way to do this in .NET end; var numbers: array[0..9] of char = '0123456789'; function DecimalToString(const aDecimal: TDecimal; aDot: Char): string; var modres: Integer; d: Int64; pos: Integer; scale: Integer; sign: Boolean; aDec: TDecimal; aResult: array[0..31] of Char; begin aDec := aDecimal; sign := (aDecimal[3] and $80000000) <> 0; scale := (aDecimal[3] and $FF0000) shr 16; pos := 31; while (aDec[0] <> 0) or (aDec[1] <> 0) or (aDec[2] <> 0) or (31 - pos < scale) do begin modres := 0; d := Int64(Int64(ModRes) shl 32) or aDec[2]; ModRes := d mod 10; aDec[2] := d div 10; d := Int64(Int64(ModRes) shl 32) or aDec[1]; ModRes := d mod 10; aDec[1] := d div 10; d := Int64(Int64(ModRes) shl 32) or aDec[0]; ModRes := d mod 10; aDec[0] := d div 10; aResult[pos] := numbers[Modres]; Dec(pos); if 31 - pos = scale then begin aresult[pos] := aDot; dec(pos); if (aDec[0] = 0) and (aDec[1] = 0) and (aDec[2] = 0) then begin aresult[pos] := '0'; dec(pos); end; end; end; if pos = 31 then begin result := '0'; exit; end; if sign then begin aResult[pos] := '-'; dec(pos); end; SetString(Result, pchar(@aResult[pos+1]), 31-Pos); end; function StringToDecimal(const aString: string; aDot: Char): TDecimal; var scalepos, pos, i: Integer; mulres: Integer; d: Int64; aRes: TDecimal; begin Fillchar(aRes, sizeof(aRes), 0); pos := 0; scalepos := -1; for i := 0 to Length(aString) do begin mulres := 0; case aString[i] of '0': ; // already set '1': mulres := 1; '2': mulres := 2; '3': mulres := 3; '4': mulres := 4; '5': mulres := 5; '6': mulres := 6; '7': mulres := 7; '8': mulres := 8; '9': mulres := 9; else begin if aString[i] = '-' then begin aRes[3] := aRes[3] or $80000000; continue; end else if aString[i] = aDot then begin if scalepos = -1 then scalepos := pos; continue; end else continue; // ignore invalid chars for now end; end; d := Int64(aRes[0]) * 10 + mulres; mulres := d shr 32; aRes[0] := d; d := Int64(aRes[1]) * 10 + mulres; mulres := d shr 32; aRes[1] := d; aRes[2] := Int64(aRes[2]) * 10 + mulres; Inc(pos); end; if scalepos <> -1 then begin pos:= pos - scalepos; aRes[3] := aRes[3] or Cardinal(Pos shl 16); end; Result := aRes; end; function BCDToDecimal(const aBcd: TBcd): TDecimal; begin Result := StringToDecimal(BcdToStr(aBcd), DecimalSeparator); end; function DecimalToBCD(const aDecimal: TDecimal): TBcd; begin Result := StrToBcd(DecimalToString(aDecimal, DecimalSeparator)); end; function DecimalToVarByteArray(const aDecimal: TDecimal): Variant; var p: Pointer; begin Result := VarArrayCreate([0,SizeOf(TDecimal)-1],varByte); p := VarArrayLock(Result); try move(PDecimal(@aDecimal)^, p^ ,SizeOf(TDecimal)); finally VarArrayUnlock(Result); end; end; function VarByteArrayToDecimal(const aVariant: Variant; var aDecimal: TDecimal): Boolean; var p: Pointer; begin Result:= False; if (VarType(aVariant) = varByte or varArray) and (VarArrayDimCount(aVariant) = 1) and (VarArrayHighBound(aVariant,1)-VarArrayLowBound(aVariant,1)+1 = SizeOf(TDecimal)) then begin p := VarArrayLock(aVariant); try move(p^, PDecimal(@aDecimal)^, SizeOf(TDecimal)); finally VarArrayUnlock(aVariant); end; Result:=True; end; end; function DecimalToVariant(const aDecimal: TDecimal): Variant; begin Result := DecimalToVarByteArray(aDecimal); end; function VariantToDecimal(const aVariant: Variant): TDecimal; var s: string; begin if VarIsNull(aVariant) then FillChar(PDecimal(@result)^,SizeOf(Result),0) else if not VarByteArrayToDecimal(aVariant,Result) then begin if VarIsFMTBcd(aVariant) then s:=BcdToStr(VarToBcd(aVariant)) else s:=VarToStr(aVariant); Result:=StringToDecimal(s, DecimalSeparator); end; end; function VariantToBCD(const aVariant: Variant): TBCD; var aDecimal: TDecimal; begin if VarIsNull(aVariant) then Result := NullBcd else if VarByteArrayToDecimal(aVariant,aDecimal) then Result:=DecimalToBCD(aDecimal) else begin if VarIsFMTBcd(aVariant) then Result := VarToBcd(aVariant) else Result:= StrToBcd(VarToStr(aVariant)); end; end; function BCDToVariant(const aBCD: TBCD; const StoreAsDecimal: Boolean): Variant; begin if StoreAsDecimal then Result := DecimalToVarByteArray(BCDToDecimal(aBCD)) else Result := VarFMTBcdCreate(aBCD); end; end.