taoxl
taoxl

Reputation: 135

Invalid typecast: convert record to tobject on 64-bit platform

it works on 32-bit platform.but not 64-bit here is the exzample

  TVerbInfo = packed record
    Verb: Smallint;
    Flags: Word;
  end;

var
  VerbInfo: TVerbInfo;
  strList : TStringList;
  verb : Smallint;
  flags : Word;
begin
  strList := TStringList.create();
  .....
  verbInfo.verb := verb;
  verbInfo.flags := flags;
  strList.addObject('verb1',TObject(VerbInfo));  //invalid typecast happened here
end;

can anyone help me? thank you very much

Upvotes: 6

Views: 1802

Answers (4)

IceCold
IceCold

Reputation: 21154

MakeVerbInfoObject will not give you a valid pointer to the record. Instead, it copies the value of the Verb and Flags fields into the Result.

type
  PVerbInfo= ^TVerbInfo;
  TVerbInfo = packed record
    Verb: Smallint;
    Flags: Word;
  end;


function MakeVerbInfoObject(const AVerbInfo: TVerbInfo): TObject;
begin
  Result := nil;
  Move(AVerbInfo, Result, SizeOf(AVerbInfo));
end;


procedure Test;
var
   VerbInfo: TVerbInfo;
   List: TObjectList;
begin
  VerbInfo.Verb:= 7;
  VerbInfo.Flags:= 8;

  List:= TObjectList.Create(false);
  List.Add(MakeVerbInfoObject(VerbInfo));

  // AV here!!!!!!!!
  var i:= PVerbInfo(List[0])^.Verb;

  FreeAndNil(List);
end;


procedure TForm1.FormCreate(Sender: TObject);
begin
  Test2;
end;

If we want a real pointer to the record, this can be used:
(tested on 32/64 bit)

Type
 PVerbInfo2= ^TVerbInfo2;
 TVerbInfo2= packed record
   Verb: SmallInt;
   Flags: Word;
 end;


procedure Test;
var
   VerbInfo: TVerbInfo2;
   List: TObjectList;
begin
  VerbInfo.Flags:= 7;
  VerbInfo.Verb:= 8;

  List:= TObjectList.Create(false);

  //List.add(Pointer(VerbInfo));    // "E2089 Invalid typecast" when compiling on 64 bit

  List.Add(TObject(@VerbInfo));

  // This gives correct values:
  var i := PVerbInfo2(List[0])^.Verb; // Inspect "I"

  FreeAndNil(List);
end;

In a Delphi that supports generics, we should definitely use that. The code is much better and much safer:

procedure TestModern;
var
   VerbInfo: TVerbInfo2;
   List: TList<TVerbInfo2>;
begin
  VerbInfo.Flags:= 7;
  VerbInfo.Verb:= 8;

  List:= TList<TVerbInfo2>.Create;
  List.Add(VerbInfo);
  var i := List[0].Verb;

  FreeAndNil(List);
end;

Upvotes: 0

Uli Gerhardt
Uli Gerhardt

Reputation: 14001

You can try something like this:

function MakeVerbInfoObject(const AVerbInfo: TVerbInfo): TObject;
begin
  Result := nil;
  Move(AVerbInfo, Result, SizeOf(AVerbInfo));
end;

strList.addObject('verb1', MakeVerbInfoObject(VerbInfo));

To retrieve the value you can use a corresponding function like

function GetVerbInfoFromObject(AObject: TObject): TVerbInfo;
begin
  Move(AObject, Result, SizeOf(Result));
end;

VerbInfo := GetVerbInfoFromObject(strList.Objects[idx]);

Upvotes: 4

Arioch &#39;The
Arioch &#39;The

Reputation: 16045

I think you have to run this on different platforms and compare results

ShowMessage( IntToStr( SizeOf( Integer ) ) );
ShowMessage( IntToStr( SizeOf( Pointer ) ) );
ShowMessage( IntToStr( SizeOf( TVerbInfo ) ) );
ShowMessage( IntToStr( SizeOf( TObject ) ) );

I suspect you cannot do a hardcast, because the sizes differ.

You may try to use workarounds like

type TBoth = record
  case byte of
    0: ( rec: TVerbInfo);
    1: ( obj: TObject);
  end;

You can also try to use TDictionary<String, TVerbInfo> type instead of TStringList

Upvotes: 1

David Heffernan
David Heffernan

Reputation: 612964

Your cast TObject(VerbInfo) will compile provided that SizeOf(TObject) = SizeOf(TVerbInfo). But TObject is a pointer and so its size varies with architecture. On the other hand, SizeOf(TVerbInfo) does not vary with architecture. Hence the cast can only work on one architecture.

Using casts like this is how you had to do things in pre-generics Delphi. But nowadays, you should be using generic containers.

For instance, if you have a list and the strings are unique then you can use a dictionary:

TDictionary<string, TVerbInfo>

If it is possible for there to be duplicate strings then you would need a new record declaration:

type
  TVerbInfo = record
    Name: string
    Verb: Integer;
    Flags: Word;
  end;

And then store a list of these in

TList<TVerbInfo>

One final point is that you should avoid using packed records. These result in mis-aligned data structures and that in turn leads to poor performance.

Upvotes: 3

Related Questions