Buffer() for AHK v1.1

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
TheArkive
Posts: 1028
Joined: 05 Aug 2016, 08:06
Location: The Construct
Contact:

Buffer() for AHK v1.1

Post by TheArkive » 07 Dec 2023, 11:17

User @just me has made a similar script here. His work is always good, so if you want a 2nd opinion on doing this in AHK v1.1, then I suggest you check out his script also.



Pick one, NOT both, to use in your scripts. Returned Buffer objects are the size they should be, since malloc is not used to allocate the memory, but rather the memory is allocated directly from the heap of the AHK process.

Pros:
  • instead of &var, use var.ptr or var.p
  • you can get the size of var with var.size or var.s
  • these buffers self destruct when out of scope
  • resulting size is usually the requested size, even when very small
  • tracking of buffers is less sketchy than vars by VarSetCapacity()
Cons:
  • String concatenation is not a normal consideration for this class.
*** WARNING ***

NEVER EVER under ANY circumstances should you EVER overwrite the size or s, ptr or p, or hp (heap handle) properties!!!



Buffer class (lite)

Code: Select all

; ========================================================================
; Buffer class for AHK v1
;
;   Usage:
;
;   var := new Buffer(size, zero := true, chk_err := true)
;
;       By default, all new buffers require a size to be given, and the
;       memory is filled with zeros.  If you do not want to fill the
;       memory with zeros, then set the [zero] param to FALSE.
;
;       Error checking is enabled by default.  If you want to disable it
;       then set the last param to false.  Basically, when a buffer is
;       freed (in __Delete() meta func) then, if error checkig is enabled,
;       an error is thrown.
;
;       In recent tests, and in normal circumstances, errors are not thrown.
;
;   Properties:
;
;       ptr  (or p) = the pointer to the memory block
;       size (or s) = the size of the memory block
;       chk_err     = TRUE by default, checks for errors on __Delete()
;                     NOTE:  This will may throw on script exit if an
;                            active buffer is not first destroyed.
;
;   NOTES:
;
;   These class objects will self destruct when they go out of scope.
; ========================================================================
class Buffer {
    __New(size, zero := true, chk_err := true) {
        If !(this.hp := DllCall("Kernel32\GetProcessHeap","UPtr")) ; process heap handle
            throw Exception("Failed to get default process heap handle.",-1)
        If !(ptr := DllCall("Kernel32\HeapAlloc","UPtr",this.hp,"UInt",(zero?8:0),"UPtr",size,"UPtr"))
            throw Exception("Failed to allocate memory.",-1)
        this["ptr"] := this["p"] := ptr, this["size"] := this["s"] := size, this["chk_err"] := chk_err
    }
    __Delete() {
        If !DllCall("Kernel32\HeapFree","UPtr",this.hp,"UInt",0,"UPtr",this.ptr) && this["chk_err"]
            throw Exception("Freeing of memory block failed.",-1)
    }
}

Use the "lite" class if you intend to mostly use Buffers as single-use, and frequently throw them away.



Buffer class (functional)

Code: Select all

; ========================================================================
; Buffer class for AHK v1
;
;   Usage:
;
;   var := new Buffer(size, zero := true, chk_err := true)
;
;       By default, all new buffers require a size to be given, and the
;       memory is filled with zeros.  If you do not want to fill the
;       memory with zeros, then set the [zero] param to FALSE.
;
;       Error checking is enabled by default.  If you want to disable it
;       then set the last param to false.  Basically, when a buffer is
;       freed (in __Delete() meta func) then, if error checkig is enabled,
;       an error is thrown.
;
;       In recent tests, and in normal circumstances, errors are not thrown.
;
;   Methods:
;
;       Free(err := true)
;
;           This will manually free the memory block, and returns the
;           error code.
;
;           NOTE: Simply freeing the var with [var := ""] also works, but
;                 no error checking is done in this case.  Usually, as long
;                 as the script is not about to exit, there is no issue
;                 with freeing memory, especially in a single thread context.
;
;       Realloc(size := "", zero := true)
;
;           Reuse the same memory block.  If no params are given, then the
;           original size is used, and the memory is zeroed out.  The ptr
;           may still change.  Returns the new pointer (may change or not).
;
;       SizeCheck(compare := false)
;
;           If [compare = FALSE], then it simply returns the size as reported
;           by the heap.  Otherwise, it compares the heap allocation size to
;           the internally recorded size, and returns TRUE if the sizes match.
;
;   Properties:
;
;       ptr  (or p) = the pointer to the memory block
;       size (or s) = the size of the memory block
;       chk_err     = TRUE by default, checks for errors on __Delete()
;                     NOTE:  This will may throw on script exit if an
;                            active buffer is not first destroyed.
;
;   NOTES:
;
;   These class objects will self destruct when they go out of scope.
; ========================================================================
class Buffer {
    __New(sz, z := true, chk_err := true) {
        If !(this.hp := DllCall("GetProcessHeap","UPtr"))
            throw Exception("Failed to get default process heap handle.",-1)
        If !(ptr:=DlLCall("HeapAlloc","UPtr",this.hp,"UInt",(z?8:0),"UPtr",sz,"UPtr"))
            throw Exception("Failed to allocate memory.",-1)
        this["ptr"] := this["p"] := ptr, this["size"] := this["s"] := size, this["chk_err"] := chk_err
    }
    Free(er := true) { ; return of zero = fail
        return DllCall("HeapFree","UPtr",this.hp,"UInt",0,"UPtr",this.p,"UPtr")
    }
    Realloc(sz := "", z := true) {
        If !(ptr := DllCall("HeapReAlloc","UPtr",this.hp,"UInt",(z?8:0),"UPtr",this.p,"UPtr",ns:=((sz="")?this.s:sz)),"UPtr")
            throw Exception("Failed to reallocate memory.",-1)
        this["ptr"] := this["p"] := ptr, this["size"] := this["s"] := ns
        return ptr
    }
    SizeCheck(compare := false) { ; to check reported size against "this.size"
        sz := DllCall("Kernel32\HeapSize","UPtr",this.hp,"UInt",0,"UPtr",this.p,"UPtr")
        return (compare ? (sz = this.s) : sz)
    }
    __Delete() {
        If !DllCall("Kernel32\HeapFree","UPtr",this.hp,"UInt",0,"UPtr",this.ptr) && this["chk_err"]
            throw Exception("Freeing of memory block failed.",-1)
    }
}

Use the "functional" class if you intend to reuse buffers frequently, and want a few extra error checking tools.



Short example:

Code: Select all

#Include Buffer.ahk

t := new Buffer(4)

NumPut(-123,t.ptr,"Int")

msgbox % NumGet(t.ptr,"Int")



Notes for geeks

dbgba
Posts: 20
Joined: 02 Apr 2021, 22:11

Re: Buffer() for AHK v1.1

Post by dbgba » 17 Jan 2024, 09:16

That's a great idea!

feiyue
Posts: 351
Joined: 08 Aug 2014, 04:08

Re: Buffer() for AHK v1.1

Post by feiyue » 18 Jan 2024, 01:18

Code: Select all

Buffer(size, FillByte:="")
{
  local
  buf:={}, buf.SetCapacity("__buf__", size), p:=buf.GetAddress("__buf__")
  , (FillByte!="" && DllCall("RtlFillMemory","Ptr",p,"Ptr",size,"uchar",FillByte))
  , buf.Ptr:=p, buf.Size:=size
  return buf
}

Post Reply

Return to “Scripts and Functions (v1)”