type B02 = array [01..02] of byte ;
...
var b : B02;
...
//here i read from tcp socket
socket.ReadBuffer(b, 2);
The question is: how to convert B02 to an integer?
You could declare a Word/Smallint at the same memory location, like this:
var
b : B02;
myInt: smallint absolute B02;
Then again, is there any particular reason why you don't just create the smallint and pass it to ReadBuffer instead of an array? I don't know exactly what class you're using, but that looks a lot like the way you read from a TStream, and it'll accept variables of any type, along with a size in bytes. Why not just declare your buffer as the integer type you're looking for and cut out the middleman?
You can just cast it:
var
a: array[01..02] of Byte;
i: Integer;
begin
i := PWORD(#a)^;
end;
or if you need to change the byte order:
i := Swap(PWORD(#a)^);
If the data is being transmitted in "network" order (highest byte first) and not in "Intel" order (lowest byte first), you can do some byte shufling yourself.
uses
SysUtils;
var
b: B02;
w: word; //two bytes represent a word, not an integer
socket.ReadBuffer(b, 2);
WordRec(w).Hi := b[1];
WordRec(w).Lo := b[2];
Mghie suggested following approach in comments (and I agree with him):
uses Winsock;
var
w: word;
socket.ReadBuffer(w, 2);
w := ntohs(w);
Related
Q: Is it safe to cast dynamic array over a static one ?
type
Bytearray = array of byte;
function calcCRC(buf1: pointer) : dword;
var
buf: ByteArray ;
outbuffer : array [1..30] of byte;
begin
buf := bytearray(buf1^); // <- is it safe ?
outbuffer[1] := buf[0];
end;
procedure test;
var
testarr : array [1..30] of byte ;
begin
calccrc(#testarr);
end ;
Such snippet compiles and works correctly, even in quite large program with FastMM4. But something tells me, that it can be dangerous ...
Any advice ?
I guess you need to revert your logic. Casting a static array into a dynamic array is not safe at all (it has a counter and reference count before the actual data), whereas the opposite - casting a dynamic array into a static array pointer - is perfectly safe, if you take care of the buffer length.
Your initial code will trigger some random GPF (Access Violations) due to the fact that the reference counter will be modified in calcCRC() in some hidden code - look at the ASM using F2: there is more than you may expect, especially a call DynArrayClear which is pretty unsafe. You didn't have any problem yet because of what was in your memory. But if e.g. a 1 integer is stored before the data, it would trigger a GPF, since leaving calcCRC() would let the Delphi RTL try to release the dynamic array instance.
If you expect to access bytes of memory using indexes, you need to use a pointer to a static array, not a dynamic array.
The code could be the following:
Type
TByteDynArray = array of byte ;
TByteArray = array[0 .. (maxInt div sizeof(byte)) - 1] of byte;
PByteArray = ^TByteArray;
function calcCRCptr(buf1: PByteArray; buf1len: integer): dword;
var
outbuffer : array [1..30] of byte;
begin
result := 0;
// ensure you don't create any access violation by using big indexes
if buf1len < 1 then
exit; // avoid GPF
outbuffer[1] := buf1[0];
...
end;
function calcCRCdynarray(const buf1: TByteDynArray): dword;
begin
// you can use length(buf1) to get the number of items/bytes
result := calcCRCptr(pointer(buf1), length(buf1));
end;
procedure test ;
var
testdynarr: TByteDynArray;
teststaticarray: array[0..10] of byte;
begin
Setlength(testdynarr, 100);
calccrcdynarray(testdynarr) ; // safe
calccrcptr(pointer(testdynarr), length(testdynarr)); // direct call
calccrcptr(#teststaticarray, 11); // OK
end;
Also ensure that you don't mess with pointers, e.g. that you name your variables with proper Delphi convention about values (T...) and pointers (P....). Also follow the same convention to distinguish static arrays and dynamic arrays in your code.
This approach is certainly not safe. The static array has a different memory layout from the dynamic array. The dynamic array has metadata containing reference count and length. You might get away with it in this short excerpt but it is not a technique to be recommended.
Whatever the problem is, this is not the solution. Possible solutions might involve working with open arrays or pointers to byte.
I define a large buffer and pointer as an Array of Byte in Delphi 7.
This is the buffer for the Win32 ReadEventLog call.
I define a second pointer as a record structure for the buffer data.
This is an overlay to add structure to an array of bytes.
Problem: data at the two pointers is different.
Buffer^ data is not the same as RecordPtr^ data.
The addresses of both pointers are the same.
Question: what is the correct method of declaring an overlay pointer?
Type
TEventRecord = record
Length : dword;
Reserved : dword;
RecordNumber : dword;
end;
TByteArray = array of byte;
Var
Buffer : TByteArray;
RecordPtr : ^TEventRecord = #Buffer;
MyData : TEventRecord;
begin
Setlength (Buffer, $FFF0);
// ReadEventLog....
// Watch of Buffer^ and RecordPtr^ shows different data
end.
Dynamic array Buffer: array of byte is a pointer, so #Buffer is pointer to pointer.
To make per-byte access to the contents of TEventRecord, you can assign pointer to the beginning of byte array data:
type
PEventRecord = ^TEventRecord;
var
PEventRecord_Var: PEventRecord;
begin
PEventRecord_Var := #Buffer[0];
Why dont u do it like this?
Type
TEventRecord = record
Length : dword;
Reserved : dword;
RecordNumber : dword;
end;
TEventLog = array of TEventRecord ;
Var
Buffer : TEventLog;
begin
Setlength (Buffer, $FFF0);
// ReadEventLog(#buffer[0],...)
end.
You could do it like this:
type
TEventRecord = record
Length: DWORD;
Reserved: DWORD;
RecordNumber: DWORD;
end;
TByteArray = array of Byte;
Var
Buffer: TByteArray;
RecordPtr: ^TEventRecord = Pointer(Buffer); // Buffer is a pointer
MyData: TEventRecord;
begin
SetLength(Buffer, $FFF0); { Assuming that is big enough }
{ ReadEventLog(..., Pointer(Buffer), Length(Buffer), .... }
{ Watch of Buffer and RecordPtr^ shows same data. }
end.
Buffer is a pointer, and it points to the first of a (big) number of bytes. If you want to use this buffer to let ReadEventLog load into a byte array, simply cast Buffer to a Pointer and assign it to RecordPtr.
But...
if I understand you well, an alternative is:
Buffer: array of TEventRecord;
...
SetLength(Buffer, 6000); { 6000 event records! }
if ReadEventLog(..., Pointer(Buffer), Length(Buffer) * SizeOf(Buffer[0]), ...) then ...
Now you can easily access each event record from the Buffer. Why use a Byte array, if, instead, you can easily read an array of event records directly?
(The above would work for normal records, but I was told by #BruceGavin that event records have a variable size tail, so that won't work)
You can achieve what you are looking for like this :
Var
Buffer : TByteArray;
RecordPtr : ^TEventRecord absolute Buffer;
This make both variable occupy the same 4 bytes in memory (or 8 bytes in 64 bits).
I want to store to disk some data records. I want to have each data record of equal size so I can quickly compute and jump to a certain record.
So, I store the only string in my record as an array of chars:
type TFileNameIO = array[1..512] of Char;
After I read the string from disk, the content of my string is like this:
c:\windows#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 etc
I want to truncate the #0 characters, and I do something like this:
function GetFileName(DiskName: TFileNameIO): string;
VAR PC: PChar;
begin
SetString(Result, PChar(#DiskName), Length(DiskName)); <----- here Result is: c:\windows#0#0#0 etc
PC:= PChar(Result);
Result:= WideCharToString(PC);
end;
There is a better way to do it?
You can assign a null-terminated PChar directly to a String:
function GetFileName(DiskName: TFileNameIO): string;
begin
Result := PChar(#DiskName);
end;
Just make sure the TDiskName always contains a null terminator. If it can ever be completely full of characters without a null, you will have to do this instead:
function GetFileName(DiskName: TFileNameIO): string;
var
Len, I: Integer;
begin
Len := 0;
For I := Low(DiskName) to High(DiskName) do
begin
if DiskName[I] = #0 then Break;
Inc(Len);
end;
SetString(Result, PChar(#DiskName), Len);
end;
Your function is not necessary because you can simply assign a character array to a string variable.
type
TCharArray = array [1..512] of Char;
....
var
arr: TCharArray;
str: string;
....
arr := ...;
str := arr;
This results in str being assigned the contents of arr. The compiler implements this with a call to System._UStrFromWArray which first of all looks for a null-terminator. If it finds one, that determines the length of str. Otherwise, the entire contents of the array are copied, and Length(str)=Length(arr).
As a broader point, I would recommend that you change to using zero-based character arrays. From the documentation:
Zero-based character arrays are compatible with PChar and PWideChar. When you use a character array in place of a pointer value, the compiler converts the array to a pointer constant whose value corresponds to the address of the first element of the array.
This makes it easier for you to interop with functions that expect PChar arguments.
As an aside, passing TFileNameIO by value is inefficient because it involves a memory copy. Pass such types by reference. For instance as a const parameter.
I am having problem with the logic. I have no idea how to copy record to another record in Delphi.
TypeA = record
value1 : word;
value2 : word;
value3 : word;
end;
TypeB = record
b1 : byte;
b2 : byte;
end;
I have my two records TypeA and TypeB. For example, I found out that data in TypeA record is belong to TypeB records. Note: TypeA has longer Data Length.
Question: How do i copy TypeA memory and place it in TypeB record?
CopyMemory(#TypeA, #TypeB, Length(TypeB))
When i tried CopyMemory and got an error (Incompaible types).
PS: I don't want to copy or assign it like below.
TypeB.b1 := TypeA.value1 && $FF;
TypeA and TypeB are just example records. Most of thecases, record TypeA and TypeB may contain multple records and it
will be harder to allocate form TypeA and assign to TypeB record.
Thanks in advance
----Addition question:
Is there a way to copy Delphi record to Byte array and how? If there is,
TypeA record to Byte Array
Byte Array to Type B
Will this logic work?
CopyMemory(#a, #b, SizeOf(TypeB))
if a is of type TypeA and b is of type TypeB.
Variant records:
TypeA = packed record
value1 : word;
value2 : word;
value3 : word;
end;
TypeB = packed record
b1 : byte;
b2 : byte;
end;
TypeAB = packed record
case boolean of
false:(a:TypeA);
true:(b:TypeB);
end;
..
..
var someRec:TypeAB;
anotherRec:TypeAB;
..
..
anotherRec.b:=someRec.b
procedure CopyAtoB( const A: TypeA; var B: TypeB);
begin
// Assume A is bigger than B.
Move( A, B, SizeOf( TypeB))
end;
or (with Math unit)
procedure CopyAtoB( const A: TypeA; var B: TypeB);
begin
// No assumptions about A, B sizes.
FillChar( B, SizeOf( B), 0);
Move( A, B, Min( SizeOf( TypeA), SizeOf( TypeB)))
end;
to copy A record to B record there is a good way - operator overloading; in this case you can simply overload Implicit and Explicit operations and write code likeb := a:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses System.SysUtils;
type
TTypeA = record
value1 : integer;
value2 : integer;
value3 : integer;
end;
TTypeB = record
b1 : byte;
b2 : byte;
class operator Implicit(value : TTypeA):TTypeB;
end;
class operator TTypeB.Implicit(value: TTypeA): TTypeB;
begin
result.b1 := Hi(value.value1);
result.b2 := Lo(value.value1);
end;
var a : TTypeA;
b : TTypeB;
begin
b := a;
end.
you can use TBytesStream to copy record to byte array:
var a : TTypeA;
bs : TBytesStream;
bArr : TArray<byte>;//array of byte;
begin
bs := TBytesStream.Create();
bs.Write(a, sizeof(a));
bArr := bs.Bytes;
end;
or simply set size of byte array to sizeof(A) and then copyMemory
All the other answers are variations of copying memory directly, but I think that is the wrong approach. It's low-level and you're using a high-level language, it's easy to make mistakes, and it is risky if your records contain managed data types.
If you're trying to get the first two bytes of one element of a record, try using a variant record (in C, a union.) It is a section of a record that can be addressed several ways, and here you want to address it either as a word or series of bytes.
Try something like this (untested, just typed in the SO editor):
type
TTwoBytes : packed record
ByteA, ByteB : Byte;
end;
TValueRecord : packed record:
case Boolean of
True: (Value: SmallInt);
False: (ValueBytes : TTwoBytes);
end;
end;
TMyRecord = packed record
Value1, Value2, Value3 : TValueRecord;
end;
Then for code:
var
MyRecord: TMyRecord;
MyBytes: TTwoBytes;
begin
MyRecord := ...; // Fill it with data here
// Access the words / smallints by something like: MyRecord.Value1.Value
MyBytes := MyRecord.ValueBytes; // The key bit: type safe assignment
// Do something with MyBytes.ByteA or MyBytes.ByteB
end;
What does this give you that's better than directly copying memory?
It's safe: if you have more complicated records containing strings, interfaces etc, it will still work without breaking reference counts.
It's type safe: no direct copying of memory: you and the compiler both know what you have and are using.
It's canonical: this is probably 'the Delphi way' to do it. (Although there are several ways to structure the records - if you look in the answer history I had a worse one originally. Also I think the 'case ... of' syntax for this is silly, but that's another discussion... :))
Some notes:
I changed your Word to SmallInt since I find Word confusing - it's not actually the 'word size' of the machine, it's 16 bits.
Also, I think this is a good article on variant records.
I used 'packed record', the justification for this is... iffy.
You can omit CopyMemory() (Windows unit) use by:
type
PTypeA = ^TTypeA;
TTypeA = record
value1 : word;
value2 : word;
value3 : word;
end;
PTypeB = ^TTypeB;
TTypeB = record
b1 : byte;
b2 : byte;
end;
var
A: TTypeA = (value1 : 11; value2 : 22; value3 : 33);
B: TTypeB;
B1: TTypeB;
C: {packed?} array of Byte;
begin
Assert(SizeOf(TTypeA) >= SizeOf(TTypeB));
//...
B:= PTypeB(#A)^;
//...
SetLength(C, SizeOf(A));
// TTypeA record to Byte Array
PTypeA(#C[0])^:= A;
// Byte Array to TTypeB
B1:= PTypeB(#C[0])^
end;
Use MOVE instead of CopyMemory if you want to simply copy the bytes from one place to another.
And to get the size of a record use SizeOf instead of Length.
Ive written a program that communicate with some hardware using a serial connection.
It sends a lot of hexadecimal values my way (sensor readings) and every once in a while it sends a negative value.
ex.
i receive a hexadecimal value : FFFFF5D6
and i have to convert it into : -2602
another problem i have is that i can't convert a float into hex and back.
Are there any simple ways of doing this?
You can "convert" from hex to float by using an integer large enough to cover the float value used, then using the ABSOLUTE keyword. All that is really doing is encoding the memory of the value as an integer. Be very careful to use types which are exactly the same size (you can use SIZEOF to find the memory size of a value). If you need an odd size, then absolute against an array of byte and loop through and convert to/from each byte (which would be two characters hex).
the ABSOLUTE keyword forces two variables to START at the same memory address, any value written from one is immediately available in the other.
var
fDecimal : Double; // size = 8 bytes
fInteger : Int64 absolute fDecimal; // size = 8 bytes
begin
fDecimal := 3.14;
ShowMessage(format('%x=%f',[fInteger,fDecimal]));
fInteger := StrToInt64('$1234123412341234');
ShowMessage(FloatToStr(fDecimal)+'='+Format('%x',[fInteger]));
end;
here is the routine for floats with odd sizes:
var
fDecimal : extended;
fInteger : array[1..10] of byte absolute fDecimal;
sHex : string;
iX : integer;
begin
ShowMessage(IntToStr(SizeOf(fDecimal))+':'+IntToStr(SizeOf(fInteger)));
fDecimal := 3.14;
sHex := '';
for iX := 1 to 10 do
sHex := sHex + IntToHex(fInteger[iX],2);
ShowMessage(sHex);
// clear the value
fDecimal := 0.0;
// Reload the value
for iX := 1 to (Length(sHex) DIV 2) do
fInteger[iX] := StrToInt('$'+Copy(sHex,(Ix*2)-1,2));
ShowMessage(FloatToStr(fDecimal));
end;
to convert a hex string into a integer, you can use the StrToInt Function , also you can check the TryStrToInt function (wich returns False if string does not represent a valid number).
uses
SysUtils;
var
ivalue : integer;
begin
ivalue:=StrToInt('$FFFFF5D6'); // Hexadecimal values start with a '$' in Delphi
..
end;
For the Hexadecimal representation of a float number, you can check theses articles.
http://www.merlyn.demon.co.uk/pas-type.htm#Str
http://www.merlyn.demon.co.uk/pas-real.htm
http://www.merlyn.demon.co.uk/programs/hexfloat.pas (source code example)
I've never seen a float represented in hex, so you'd have to figure out exactly what format the device is using for that. For the negative number case, you'll need to use HexToInt to covert it to an integer, and then determine if that integer is larger than whatever threshold value represents MaxInt for the hardware's integer data type. If it's bigger than that, it's a negative number, which means you'll need to, IIRC, get the number's 1s complement and convert it to negative.
If you want to separate the exponent and the significand, you can use a variant record:
TExtRec = packed record
case Boolean of
false:
(AValue: Extended);
true:
(ASignificand: uint64; ASignExp: uint16;)
end;
I think this helps to understand the structure of the floating point number.
Example usage:
var
r: TExtRec;
begin
r.AValue := 123.567;
ShowMessage(IntToHex(r.ASignExp) + IntToHex(r.ASignificand));
end;
Output:
4005F7224DD2F1A9FBE7
You can calculate it back:
v = (-1)s * 2(e-16383) * (i.f)
With
e = $4005 = 16389 and
i.f = $F7224DD2F1A9FBE7
i.f = 1.930734374999999999954029827886614611998084001243114471435546875
v=123.566999999999999997057908984743335167877376079559326171875
To convert i.f, i've used a binary converter.