But to allow for relatively seamless integration with PB's String datatype i'm using this:
Code: Select all
Structure BSTR
size.l
s.s ;POINTER to the string, not the actual string data itself
EndStructure
Simple rule of thumb - when READING you can treat it as a normal string, eg. Debug "The string is " + *bstr\s
But whenever WRITING you must use the bXxx() functions.
In the following simple SpeedTest.pb which repeatedly appends an 11 byte string 20000 times, PB's String function took 7716ms, BSTR took 26ms (both in Debug mode):
Code: Select all
XIncludeFile("BSTR.pbi")
Define *append.BSTR, append$ ;a BString and a normal String$
append$ = ""
Time1=ElapsedMilliseconds()
For i = 1 To 20000
append$ + "Append This"
Next i
Time2=ElapsedMilliseconds()
Debug "Str$ Time=" + Str(Time2-Time1)+"ms" ;+ " String=" + append$
*append.BSTR = BSTR(0, "")
Time1=ElapsedMilliseconds()
For i = 1 To 20000
bAppend(*append, "Append This")
Next i
Time2=ElapsedMilliseconds()
Debug "BSTR Time=" + Str(Time2-Time1)+"ms" ;+ " String=" + *append\s
;Str$ Time=7716ms
;BSTR Time=26ms
Code: Select all
;### BSTR Dynamic Strings ###
;by Keya. Public domain.
;Unicode + Ascii, 32 + 64, Win+Linux+Mac.
Structure BSTR
size.l ;"ABC" = 3 (Asc) or 6 (unicode)
s.s ;Never directly WRITE to this; always use bFunctions() to ensure \Size is updated.
EndStructure
CompilerIf #PB_Compiler_Unicode
#PB_StringFormat = #PB_Unicode
CompilerElse
#PB_StringFormat = #PB_Ascii
CompilerEndIf
CompilerIf #PB_Compiler_Unicode
Macro UNICALC(value)
(value)*2
EndMacro
CompilerElse
Macro UNICALC(value)
(value)
EndMacro
CompilerEndIf
Procedure BSTR(*bstr.BSTR, string$) ;Create/overwrite bStrings
strlen = StringByteLength(string$)
If Not *bstr
*bstr = AllocateMemory(SizeOf(BSTR))
*newaddr = AllocateMemory(strlen+2)
PokeI(*bstr+4, *newaddr)
Else
If *bstr\size <> strlen
*newaddr = ReAllocateMemory(@*bstr\s, strlen+2)
PokeI(*bstr+4, *newaddr)
EndIf
EndIf
PokeL(*bstr, strlen)
CopyMemory(@string$, @*bstr\s, strlen + SizeOf(Character))
ProcedureReturn *bstr
EndProcedure
Procedure bAppend(*bstr.BSTR, string$) ;Append String to bString
strlen = StringByteLength(string$)
oldlen = *bstr\size
newsize = oldlen + strlen
*newaddr = ReAllocateMemory(@*bstr\s, newsize+2)
PokeI(*bstr+4, *newaddr)
CopyMemory(@string$, @*bstr\s + oldlen, strlen + SizeOf(Character))
PokeL(*bstr, newsize)
EndProcedure
Procedure bFree(*bstr.BSTR) ;Free. Not really required (there's no Free for normal strings)
If *bstr
FreeMemory(@*bstr\s)
PokeL(*bstr,0): PokeI(*bstr+4,0)
FreeMemory(*bstr)
EndIf
EndProcedure
Procedure.i bLen(*bstr.BSTR)
CompilerIf #PB_Compiler_Unicode
ProcedureReturn *bstr\size >> 1
CompilerElse
ProcedureReturn *bstr\size
CompilerEndIf
EndProcedure
Procedure.i bStringByteLength(*bstr.BSTR)
ProcedureReturn *bstr\size
EndProcedure
Procedure.s bMid(*bstr.BSTR, startpos, length=0, format=#PB_StringFormat)
If length<=0: length=*bstr\size: EndIf
ProcedureReturn PeekS(@*bstr\s + UNICALC(startpos)-SizeOf(Character), length, format)
EndProcedure
Macro bRight(bstr, length)
PeekS(@bstr\s + bstr\size - UNICALC(length), length, #PB_StringFormat)
EndMacro
; Procedure.s bRight(*bstr.BSTR, length, format=#PB_StringFormat)
; ProcedureReturn PeekS(@*bstr\s + *bstr\size - UNICALC(length), length, format)
; EndProcedure
Macro bLeft(bstr, length)
PeekS(@bstr\s, length, #PB_StringFormat)
EndMacro
;Procedure.s bLeft(*bstr.BSTR, length, format=#PB_StringFormat)
; ProcedureReturn PeekS(@*bstr\s, length, format)
;EndProcedure
Macro bTrim(pbstr,character=" ")
Trim(pbstr\s,character)
EndMacro
Macro bLTrim(pbstr,character=" ")
LTrim(pbstr\s,character)
EndMacro
Macro bRTrim(pbstr,character=" ")
RTrim(pbstr\s,character)
EndMacro
Macro bLCase(pbstr)
LCase(pbstr\s)
EndMacro
Macro bUCase(pbstr)
UCase(pbstr\s)
EndMacro
Macro bFindString(pbstr, stringtofind, startposition=0,mode=#PB_String_CaseSensitive)
FindString(pbstr\s, stringtofind, startposition, mode)
EndMacro
Most functions are implemented as macros. I've only added about 1/3rd of the string functions, but wanted to keep it fairly short for this first post. I don't envisage any problems adding full support. This demo uses a normal string function alongside a bstr string function - this is just for comparison, you don't need a normal string in order to use a bstr.
Code: Select all
XIncludeFile("BSTR.pbi")
Define *bstr1.BSTR, normal$ ;a BString and a normal String
*bstr1 = BSTR(0,"First") ;create string "First"
normal$ = "First"
Debug "Full: " + normal$
Debug "Full: " + *bstr1\s
*bstr1 = BSTR(*bstr1,"Second") ;overwrite string to become "Second"
normal$ = "Second"
Debug "Full: " + normal$
Debug "Full: " + *bstr1\s
Debug "Left: " + Left(normal$,3)
Debug "Left: " + bLeft(*bstr1,3)
Debug "Right: " + Right(normal$, 3)
Debug "Right: " + bRight(*bstr1, 3)
Debug "Mid: " + Mid(normal$, 2, 3)
Debug "Mid: " + bMid(*bstr1, 2, 3)
normal$ = normal$ + " appended "
*bstr1 = BSTR(*bstr1,*bstr1\s + " appended ")
Debug "Append: " + normal$ + "[End]"
Debug "Append: " + *bstr1\s + "[End]"
Debug "Trim: " + Trim(normal$) + "[End]"
Debug "Trim: " + bTrim(*bstr1) + "[End]"
Debug "Len: " + Str(Len(normal$))
Debug "Len: " + Str(bLen(*bstr1))
Debug "StrByteLen: " + Str(StringByteLength(normal$))
Debug "StrByteLen: " + Str(bStringByteLength(*bstr1))
Debug "FindString: " + Str(FindString(normal$, "pen"))
Debug "FindString: " + Str(bFindString(*bstr1, "pen"))
Debug "UCase: " + UCase(normal$)
Debug "UCase: " + bUcase(*bstr1)
bFree(*bstr1) ;not really needed