Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Binary file I/O with binary buffers


  • Please log in to reply
4 replies to this topic
Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
The simple binary file functions take and provide the data in hex form, such that the powerful string manipulating and search functions of AHK could be applied. If only pieces need to be cut and pasted, maybe into many files, there is no need for hex conversions. Large chunks of data can be manipulated much faster in binary buffers.

We could use two sets of functions with hex or with binary buffers, whichever fits the task better, or we can use the binary buffer version only, with simple functions converting binary data to hex and vice versa. The original functions should be renamed to HexWrite and HexRead.

Again, there are calling examples below. The conversion functions are simple enough (but tricky) that they did not deserve the fancy header, but you can add your own. The binary file I/O functions now store/load the data only to/from special AHK variables, which may contain NULL characters, therefore, they must not be manipulated as ordinary strings. Not even assignments or StrLen is allowed. If these variables are used as function parameters, they must be defined as ByRef, otherwise the strings get truncated at the first NULL.
file = c:\z.dat
FileDelete %file%
IfNotEqual ErrorLevel,0, MsgBox Can't delete file "%file%"`nErrorLevel = "%ErrorLevel%"

Hex2Bin(b,"000102030405060708090a0b0c0d0e0f00")
res := BinWrite(file,b,17)
MsgBox ErrorLevel = %ErrorLevel%`nBytes Written = %res%
res := BinRead(file,data)
MsgBox ErrorLevel = %ErrorLevel%`nBytes Read = %res%
Bin2Hex(h,data,res)
MsgBox Data = "%h%"

Hex2Bin(b,"aa00bb")
res := BinWrite(file,b,3,-2)
MsgBox ErrorLevel = %ErrorLevel%`nBytes Written = %res%
res := BinRead(file,data)
MsgBox ErrorLevel = %ErrorLevel%`nBytes Read = %res%
Bin2Hex(h,data,res)
MsgBox Data = "%h%"

ExitApp

Bin2Hex(ByRef h, ByRef b, n=0)      ; n bytes binary data -> stream of 2-digit hex
{                                   ; n = 0: all (SetCapacity can be larger than used!)
   format = %A_FormatInteger%       ; save original integer format
   SetFormat Integer, Hex           ; for converting bytes to hex

   m := VarSetCapacity(b)
   If (n < 1 or n > m)
       n := m
   Address := &b
   h =
   Loop %n%
   {
      x := *Address                 ; get byte in hex
      StringTrimLeft x, x, 2        ; remove 0x
      x = 0%x%                      ; pad left
      StringRight x, x, 2           ; 2 hex digits
      h = %h%%x%
      Address++
   }
   SetFormat Integer, %format%      ; restore original format
}

Hex2Bin(ByRef b, h, n=0)            ; n hex digit-pairs -> binary data
{                                   ; n = 0: all. (Only ByRef can handle binaries)
   m := Ceil(StrLen(h)/2)
   If (n < 1 or n > m)
       n := m
   Granted := VarSetCapacity(b, n, 0)
   IfLess Granted,%n%, {
      ErrorLevel = Mem=%Granted%
      Return
   }
   Address := &b
   Loop %n%
   {
      StringLeft  x, h, 2
      StringTrimLeft h, h, 2
      x = 0x%x%
      DllCall("RtlFillMemory", "UInt", Address, "UInt", 1, "UChar", x)
      Address++
   }
}

/* ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; BinWrite ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|  - Open binary file
|  - (Over)Write n bytes (n = 0: all)
|  - From offset (offset < 0: counted from end)
|  - Close file
|  (Binary)data -> file[offset + 0..n-1], rest of file unchanged
|  Return #bytes actually written
*/ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

BinWrite(file, ByRef data, n=0, offset=0)
{
   ; Open file for WRITE (0x40..), OPEN_ALWAYS (4): creates only if it does not exists
   h := DllCall("CreateFile","str",file,"Uint",0x40000000,"Uint",0,"UInt",0,"UInt",4,"Uint",0,"UInt",0)
   IfEqual h,-1, SetEnv, ErrorLevel, -1
   IfNotEqual ErrorLevel,0,Return,0 ; couldn't create the file

   m = 0                            ; seek to offset
   IfLess offset,0, SetEnv,m,2
   r := DllCall("SetFilePointerEx","Uint",h,"Int64",offset,"UInt *",p,"Int",m)
   IfEqual r,0, SetEnv, ErrorLevel, -3
   IfNotEqual ErrorLevel,0, {
      t = %ErrorLevel%              ; save ErrorLevel to be returned
      DllCall("CloseHandle", "Uint", h)
      ErrorLevel = %t%              ; return seek error
      Return 0
   }

   m := VarSetCapacity(data)        ; get the capacity ( >= used length )
   If (n < 1 or n > m)
       n := m
   result := DllCall("WriteFile","UInt",h,"Str",data,"UInt",n,"UInt *",Written,"UInt",0)
   if (!result or Written < n)
       ErrorLevel = -3
   IfNotEqual ErrorLevel,0, SetEnv,t,%ErrorLevel%

   h := DllCall("CloseHandle", "Uint", h)
   IfEqual h,-1, SetEnv, ErrorLevel, -2
   IfNotEqual t,,SetEnv, ErrorLevel, %t%-%ErrorLevel%

   Return Written
}

/* ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; BinRead ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|  - Open binary file
|  - Read n bytes (n = 0: file size)
|  - From offset (offset < 0: counted from end)
|  - Close file
|  (Binary)data (replaced) <- file[offset + 0..n-1]
|  Return #bytes actually read
*/ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

BinRead(file, ByRef data, n=0, offset=0)
{
   h := DllCall("CreateFile","Str",file,"Uint",0x80000000,"Uint",3,"UInt",0,"UInt",3,"Uint",0,"UInt",0)
   IfEqual h,-1, SetEnv, ErrorLevel, -1
   IfNotEqual ErrorLevel,0,Return,0 ; couldn't open the file

   m = 0                            ; seek to offset
   IfLess offset,0, SetEnv,m,2
   r := DllCall("SetFilePointerEx","Uint",h,"Int64",offset,"UInt *",p,"Int",m)
   IfEqual r,0, SetEnv, ErrorLevel, -3
   IfNotEqual ErrorLevel,0, {
      t = %ErrorLevel%              ; save ErrorLevel to be returned
      DllCall("CloseHandle", "Uint", h)
      ErrorLevel = %t%              ; return seek error
      Return 0
   }

   m := DllCall("GetFileSize","UInt",h,"Int64 *",r)
   If (n < 1 or n > m)
       n := m
   Granted := VarSetCapacity(data, n, 0)
   IfLess Granted,%n%, {
      ErrorLevel = Mem=%Granted%
      Return 0
   }

   result := DllCall("ReadFile","UInt",h,"Str",data,"UInt",n,"UInt *",Read,"UInt",0)

   if (!result or Read < n)
       t = -3
   IfNotEqual ErrorLevel,0, SetEnv,t,%ErrorLevel%

   h := DllCall("CloseHandle", "Uint", h)
   IfEqual h,-1, SetEnv, ErrorLevel, -2
   IfNotEqual t,,SetEnv, ErrorLevel, %t%-%ErrorLevel%

   Return Read
}
Edit 2005.07.03: StrLen and assignment don’t work with binary buffers. We cannot even compare them, If (a=B)... only compares the data until the first NULL, and ignores the rest. We have to use DLL calls to copy or compare buffers, or Bin2Hex, Hex2Bin combinations and work with the hex strings in between.

Rajat
  • Members
  • 1904 posts
  • Last active: Jul 17 2015 07:45 AM
  • Joined: 28 Mar 2004
very nice work!

MIA

CleanNews.in : Bite sized latest news headlines from India with zero bloat


Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Thanks, Rajat! These functions are tricky, so they could still have bugs. Please post any abnormal behavior of them, like formatting your hard drive, corrupting the file system or sending harassing emails to your ex girlfriend.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
StrLen and assignment don’t work with binary buffers. We cannot even compare them, If (a=B)... only compares the data until the first NULL, and ignores the rest. We have to use DLL calls to copy or compare buffers, or Bin2Hex, Hex2Bin combinations and work with the hex strings in between.

Rajat
  • Members
  • 1904 posts
  • Last active: Jul 17 2015 07:45 AM
  • Joined: 28 Mar 2004
Bin2Hex is sending harassing emails to my current girlfriend!! fix it!!!

(ok i had nothing to say!!) ;)

MIA

CleanNews.in : Bite sized latest news headlines from India with zero bloat