Sign in to follow this  
Followers 0
ResNullius

[solved] 32-bit Number to/from 4 bytes ?

15 posts in this topic

#1 ·  Posted (edited)

I have a file header that I am trying to extract information from but have hit a stumbling block.

According to the file structure definition, a 32-bit number is stored in bytes 4-7.

I am looking for a way to retrieve the 32-bit number from those 4 bytes, and conversley store a number back.

For example, the number "592" is stored as follows

Byte4 = 50 (hex)    080 (asc)
Byte5 = 02 (hex)    002 (asc)
Byte6 = 00 (hex)    000 (asc)
Byte7 = 00 (hex)    000 (asc)

And the number "17" as

Byte4 = 11 (hex)    017 (asc)
Byte5 = 00 (hex)    000 (asc)
Byte6 = 00 (hex)    000 (asc)
Byte7 = 00 (hex)    000 (asc)

I've googled this to death and can't get anything to work. I know(?) it involves BitShif-ting and BitOr-ing, but as I say, everything I'ved tried from other languages just doesn't seem to work.

Last thing I tried was adapting the code from http://www.freevbcode.com/ShowCode.asp?ID=1006, but all I got as a return was 0 ;)

Thanks in advance (and I think I'll cry if somebody shows me a simple way to do this using AutoIt's built-in functions Hex,Desc,Asc, etc)

EDIT: Updated the bytes from the header as I was off by one thanks to a TAB and using an ASCII viewer (see post #7 below)

Edited by ResNullius

Share this post


Link to post
Share on other sites

I'm not sure you have your first numbers right, according to the article you posted {255,127,0,0} gives 32767, this code gives you that:

$bytes=DllStructCreate("byte[4]")
DllStructSetData($bytes,1,255,1)
DllStructSetData($bytes,1,127,2)
DllStructSetData($bytes,1,0,3)
DllStructSetData($bytes,1,0,4)
$32bit=DllStructCreate("int",DllStructGetPtr($bytes))
MsgBox(0,"",DllStructGetData($32bit,1))

However it does not match your first numbers, {5,77,2,0} comes out as 150789.


Broken link? PM me and I'll send you the file!

Share this post


Link to post
Share on other sites

I'm not sure you have your first numbers right, according to the article you posted {255,127,0,0} gives 32767, this code gives you that:

$bytes=DllStructCreate("byte[4]")
DllStructSetData($bytes,1,255,1)
DllStructSetData($bytes,1,127,2)
DllStructSetData($bytes,1,0,3)
DllStructSetData($bytes,1,0,4)
$32bit=DllStructCreate("int",DllStructGetPtr($bytes))
MsgBox(0,"",DllStructGetData($32bit,1))

However it does not match your first numbers, {5,77,2,0} comes out as 150789.

Yes, I'm not even sure the article I linked to is using the appropriate method.

BTW, the file I am trying to read is a DBF file.

And no, don't everybody start telling me just to use COM/ADODB access: I know how/can do that. This is for something different.

Details on the DBF file structure is here: http://www.dbf2002.com/dbf-file-format.html

I am trying to read the "number of records in file".

One of the other DBF file structure documents says this is "least significant byte first". Would that make a difference to your code?

I'm completely lost with DllStruct, etc, so again, any assistance is greatly appreceated. ;)

Share this post


Link to post
Share on other sites

I use this... A generous AutoIt user dreamed it up for me.

Func Bin2Num($4bytes)
    $dllStruct2_Integer = DllStructCreate("int")
    $dllStruct2_Binary = DllStructCreate("byte[4]", DllStructGetPtr($dllStruct2_Integer))

    DllStructSetData($dllStruct2_Binary,1,$4bytes)
    Return DllStructGetData($dllStruct2_Integer, 1)
EndFunc

f_mrcleansmalm_77ce002.jpgAutoIt has helped make me wealthy

Share this post


Link to post
Share on other sites

@ResNullius: This might be a dumb question, but... are you sure you grabbed the correct bytes? Those bytes should be bytes 5-8, but they have offsets 4-7 (because the offsets are 0-based, as with arrays).


[ WinINet.au3 | Array.au3 (Optimized) | _UnixTimeParse() ]

Share this post


Link to post
Share on other sites

#7 ·  Posted (edited)

@ResNullius: This might be a dumb question, but... are you sure you grabbed the correct bytes? Those bytes should be bytes 5-8, but they have offsets 4-7 (because the offsets are 0-based, as with arrays).

No dumb questions, only dumb OPs ;)

The answer is yes and no! I was pulling the correct bytes by position, but I was looking at the returned string as plain text not in a hex viewer.

So guess what happens when your 3rd byte is a TAB character: your columns jump off by one.

Corrections as follows:

"592" is

Byte4 = 50 (hex)    080 (asc)
Byte5 = 02 (hex)    002 (asc)
Byte6 = 00 (hex)    000 (asc)
Byte7 = 00 (hex)    000 (asc)

"17" is

Byte4 = 11 (hex)    017 (asc)
Byte5 = 00 (hex)    000 (asc)
Byte6 = 00 (hex)    000 (asc)
Byte7 = 00 (hex)    000 (asc)

I will retry my original attempts as well as monoceres's & Larry's

EDIT: corrected the "17" example, also updatedthe first post

Edited by ResNullius

Share this post


Link to post
Share on other sites

OK, thanks to monoceres & Larry & -Ultima- !

-Ultima- caused me to reexamine my bytes, and subsequent to that both monoceres's & Larry's code worked as planned.

I'll be using Larry's version and also the coresponding function from this thread http://www.autoitscript.com/forum/index.php?showtopic=78605 to write the number to 4 bytes

For the sake of completeness, here is the result of this little exercise:

$dbf = FileOpen("test.dbf", 16)
FileRead($dbf, 4);move past/ignore the First 4 bytes, 

$4Bytes = FileRead($dbf, 4 & @CRLF);capture the 4 bytes (5-8) we're interested in
ConsoleWrite($4Bytes & @CRLF)

$num = Bin2Num($4Bytes)

MsgBox(0, "Number of Records", $num)

Func Bin2Num($4Bytes)
    $dllStruct2_Integer = DllStructCreate("int")
    $dllStruct2_Binary = DllStructCreate("byte[4]", DllStructGetPtr($dllStruct2_Integer))

    DllStructSetData($dllStruct2_Binary, 1, $4Bytes)
    Return DllStructGetData($dllStruct2_Integer, 1)
EndFunc

Thanks all (and it makes me feel a little better seeing Larry was struggling with the same thing in that other thread!)

Share this post


Link to post
Share on other sites

Hello ResNullius, try this:

$dbf = FileOpen("test.dbf", 16)
$4Bytes = BinaryMid(FileRead($dbf, 8), 5);capture the 4 bytes (5-8) we're interested in
ConsoleWrite($4Bytes & @CRLF)

$num = AscW(BinaryToString(BinaryMid($4Bytes, 1, 2), 2)) + _
       AscW(BinaryToString(BinaryMid($4Bytes, 3, 2), 2)) * 0x10000
MsgBox(0, "Number of Records", $num)

Paul

Share this post


Link to post
Share on other sites

Hello ResNullius, try this:

$dbf = FileOpen("test.dbf", 16)
$4Bytes = BinaryMid(FileRead($dbf, 8), 5);capture the 4 bytes (5-8) we're interested in
ConsoleWrite($4Bytes & @CRLF)

$num = AscW(BinaryToString(BinaryMid($4Bytes, 1, 2), 2)) + _
       AscW(BinaryToString(BinaryMid($4Bytes, 3, 2), 2)) * 0x10000
MsgBox(0, "Number of Records", $num)

Paul

Thanks Paul, that works great too!

What about going the other way, from a number to 4 bytes?

Share this post


Link to post
Share on other sites

Hello ResNullius,

In DBase number of records are stored least significant byte first (Little-Endian).

Data File Header Structure

Endianness

You must first convert the Binary value to Int.

And than convert Little-Endian to Big-Endian.

$dbf = FileOpen("test.dbf", 16)
$4Bytes = BinaryMid(FileRead($dbf, 8), 5);capture the 4 bytes (5-8) we're interested in
ConsoleWrite($4Bytes & @CRLF)

; Binary to Int conversion
$4BytesInt = Dec(Hex($4Bytes))
ConsoleWrite($4BytesInt & ", Type: " & VarGetType($4BytesInt) & @CRLF)

; Little-Endian to Big-Endian conversion
$num = BitShift(BitAND($4BytesInt, 0x000000FF), -24) + BitShift(BitAND($4BytesInt, 0x0000FF00), -8) + _
        BitShift(BitAND($4BytesInt, 0x00FF0000), +8) + BitShift(BitAND($4BytesInt, 0xFF000000), +24)
MsgBox(0, "Number of Records", $num)

Paul

Share this post


Link to post
Share on other sites

#12 ·  Posted (edited)

Hello ResNullius,

In DBase number of records are stored least significant byte first (Little-Endian).

Data File Header Structure

Endianness

You must first convert the Binary value to Int.

And than convert Little-Endian to Big-Endian.

$dbf = FileOpen("test.dbf", 16)
$4Bytes = BinaryMid(FileRead($dbf, 8), 5);capture the 4 bytes (5-8) we're interested in
ConsoleWrite($4Bytes & @CRLF)

; Binary to Int conversion
$4BytesInt = Dec(Hex($4Bytes))
ConsoleWrite($4BytesInt & ", Type: " & VarGetType($4BytesInt) & @CRLF)

; Little-Endian to Big-Endian conversion
$num = BitShift(BitAND($4BytesInt, 0x000000FF), -24) + BitShift(BitAND($4BytesInt, 0x0000FF00), -8) + _
        BitShift(BitAND($4BytesInt, 0x00FF0000), +8) + BitShift(BitAND($4BytesInt, 0xFF000000), +24)
MsgBox(0, "Number of Records", $num)

Paul

Paul,

Now I'se confuzed! ;)

Is this a reworking of your first post?

Or is this supposed to answer my last question - How to convert a 32bit number into its 4-Byte equivalent

I see it gives me the same results as your first post but goes about it in a different way.

Thanks for your input, help, and patience.

Edit: Actually, your second method (with the Endian stuff) doesn't return the same result if I test on a dbf with more records. On one with 592 records the results are the same, but on one that contains 286,416 records, your endian method returns 286,160, whereas your first method is correct, as are the others posted by monoceres & Larry.

?

Edited by ResNullius

Share this post


Link to post
Share on other sites

Thanks Paul, that works great too!

What about going the other way, from a number to 4 bytes?

It's very easy:

$int=DllStructCreate("int")
DllStructSetData($int,1,120123)
$bytes=DllStructCreate("ubyte [4]",DllStructGetPtr($int))

ConsoleWrite("Byte 1: "&DllStructGetData($bytes,1,1)&@CRLF)
ConsoleWrite("Byte 2: "&DllStructGetData($bytes,1,2)&@CRLF)
ConsoleWrite("Byte 3: "&DllStructGetData($bytes,1,3)&@CRLF)
ConsoleWrite("Byte 4: "&DllStructGetData($bytes,1,4)&@CRLF)

;)


Broken link? PM me and I'll send you the file!

Share this post


Link to post
Share on other sites

It's very easy:

$int=DllStructCreate("int")
DllStructSetData($int,1,120123)
$bytes=DllStructCreate("ubyte [4]",DllStructGetPtr($int))

ConsoleWrite("Byte 1: "&DllStructGetData($bytes,1,1)&@CRLF)
ConsoleWrite("Byte 2: "&DllStructGetData($bytes,1,2)&@CRLF)
ConsoleWrite("Byte 3: "&DllStructGetData($bytes,1,3)&@CRLF)
ConsoleWrite("Byte 4: "&DllStructGetData($bytes,1,4)&@CRLF)

;)

You can also just use DllStructGetData on $bytes, 1, and that will retrieve a Binary (suitable for writing to a file in a whole block) with all 4 bytes already in it.

AutoIt/Windows stores most of it's information I've seen in little endian order, so that is very easy to work with by default. For Big Endian, I've been using (at least with 32-bit values) a Dec(StringTrimLeft($BinaryValue), 2)) to go from 4 bytes to an int, and Binary("0x" & Hex($IntValue)) to go from an int to 4 bytes (all ints are signed ints this way).

If the need arises, I think windows has some built in functions for dealing with endianess swapping, but I normally do it by hand myself.

Share this post


Link to post
Share on other sites

It's very easy:

Yeah, for you it is ;)

You can also just use DllStructGetData on $bytes, 1, and that will retrieve a Binary (suitable for writing to a file in a whole block) with all 4 bytes already in it.

That works a treat too!

Thanks again everybody.

One day I'll understand this DllStruct/BitShift/Endian stuff, it's just not going to be in the next week/month/year or so !

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0