ResNullius Posted September 7, 2008 Posted September 7, 2008 (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 followsByte4 = 50 (hex) 080 (asc) Byte5 = 02 (hex) 002 (asc) Byte6 = 00 (hex) 000 (asc) Byte7 = 00 (hex) 000 (asc)And the number "17" asByte4 = 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 September 8, 2008 by ResNullius
monoceres Posted September 7, 2008 Posted September 7, 2008 (edited) If you're reading a file it's easiest to read the data directly into a struct, like I did in this post: http://www.autoitscript.com/forum/index.php?showtopic=79986 Edited September 7, 2008 by monoceres Broken link? PM me and I'll send you the file!
monoceres Posted September 7, 2008 Posted September 7, 2008 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!
ResNullius Posted September 8, 2008 Author Posted September 8, 2008 monoceres said: 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.
LarryDalooza Posted September 8, 2008 Posted September 8, 2008 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 AutoIt has helped make me wealthy
-Ultima- Posted September 8, 2008 Posted September 8, 2008 @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() ]
ResNullius Posted September 8, 2008 Author Posted September 8, 2008 (edited) -Ultima- said: @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 September 8, 2008 by ResNullius
ResNullius Posted September 8, 2008 Author Posted September 8, 2008 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 bytesFor 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) EndFuncThanks all (and it makes me feel a little better seeing Larry was struggling with the same thing in that other thread!)
paulpmeier Posted September 8, 2008 Posted September 8, 2008 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
ResNullius Posted September 8, 2008 Author Posted September 8, 2008 paulpmeier said: 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) PaulThanks Paul, that works great too! What about going the other way, from a number to 4 bytes?
paulpmeier Posted September 8, 2008 Posted September 8, 2008 Hello ResNullius,In DBase number of records are stored least significant byte first (Little-Endian).Data File Header Structure EndiannessYou 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
ResNullius Posted September 8, 2008 Author Posted September 8, 2008 (edited) paulpmeier said: 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) PaulPaul, 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 September 8, 2008 by ResNullius
monoceres Posted September 8, 2008 Posted September 8, 2008 ResNullius said: 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!
SkinnyWhiteGuy Posted September 8, 2008 Posted September 8, 2008 monoceres said: 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.
ResNullius Posted September 8, 2008 Author Posted September 8, 2008 monoceres said: It's very easy:Yeah, for you it is SkinnyWhiteGuy said: 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 !
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now