Yksi VB:n merkkijonojen suurimpia heikkouksia on niiden toisiinsa liittämisen hitaus, etenkin käsiteltäessä suuria merkkijonoja. Pienet korkeintaan noin 64 kt lopputuloksen tuottavat yhdistelyt suoriutuvat vielä varsin vauhdikkaasti, tästä suuremmat hidastuvat kumulatiivisesti. Tämä johtuu VB:n sisäisen merkkijonokohtaisen bufferirajan ylittymisestä.
Tämä luokka sekä sen apumoduuli kiertävät ongelman tehokkaasti säilyttäen kuitenkin helppokäyttöisyyden. Luokka varaa automaattisesti 64 merkkiä tilaa merkkijononsa molemmin puolin, tai enemmän mikäli BufferAllocSize -arvoa muuttaa. Luokka ei kuitenkaan jättäydy ominaisuuksiissaan vain yhdelle alalle, vaan rohkeasti laajentaa useisiin muihinkin asioihin. Merkittävä osa toiminnoista on nopeampaa toteuttaa luokan kautta kuin käyttää VB:n omaa merkkijonomuuttujatyyppiä.
Suurena erona luokan käyttämissä funktioissa on se, että ne ovat nollapohjaisia. Ensimmäisen merkin indeksi on siis 0, ja merkkijonon loppu on vastaavasti yhtä kuin merkkien määrä.
Luokka sisältää seuraavat toiminnot:
- Append (lisää merkkijonon loppuun)
- Prepend (lisää merkkijonon alkuun)
- Asc, Chr
- Left, Right
- Middle (vastaa Mid$-funktiota)
- InStr
- LCase, UCase
- PCase (proper case, ensimmäinen merkki isolla)
- Replicate (monistaa sisällön)
- Reverse (kääntää merkkijonon)
- Trim (vastaa Trim$-funktiota)
- Value (palauttaa tai asettaa merkkijonon)
- ValueArray (palauttaa/asettaa sisällön Integer-muuttujajonoon)
- GetLeft, GetMiddle, GetRight, GetValue (kuin perusarvot, mutta palauttavat uuden kopion luokasta)
Suurimmat erot perusfunktioihin löytyvät Middle- sekä Trim-komennoista. Trim kykenee poistamaan minkä tahansa annetun merkin tai merkit, kummalta tahansa tai molemmilta puolen merkkijonoa. Tämä on siis merkittävä ero VB:n omaan Trim$-funktioon. Middle vastaavasti sekin kykenee johonkin, mihin VB:n oma Mid$ ei kykene.
Dim strTesti As New clsBSTR strTesti = "ABC kissa kävelee" strTesti.Middle(0, 3) = "Oikein nätisti" MsgBox strTesti
Sen sijaan että lopputulos olisi "Oik kissa kävelee", se onkin "Oikein nätisti kissa kävelee". Voit myös asettaa pituudeksi nolla merkkiä kolmen sijaan, jolloin merkkijonon alkuun lisättäisiin annetut merkit.
Luokka käyttää hyväkseen useita optimointitekniikoita. Käytössä ovat omat mm. safearrayt, muutamia aputaulukoita lisälaskennan välttämiseksi sekä API-kutsujen vaihtoehtoinen lisäämistapa. Luokka on tietyllä tapaa keskentekoinen, että optimointivaihtoehtojen testaus on vielä vaiheessa; ja luonnollisesti tarkoitus on ollut välttää ASMin käyttöä, jotta luokka olisi helppo ottaa käyttöön eikä ylimääräisiä tiedostoja tarvittaisi.
Tiedostoja on siis kaksi, clsBSTR.cls sekä modBSTR.bas. Huomionarvoista on, että kun lisäät clsBSTR.cls:n koodin, avaa Tools-valikosta Procedure Attributes, valitse Name-pudotuslistasta Value, sitten avaa Advanced >> ja aseta kohtaan Procedure ID arvo (Default). Tämän jälkeen ei tarvitse enää kirjoittaa .Value, jotta merkkijonoarvo tallentuisi suoraan samaan tapaan kuin natiiveja merkkijonoja käytettäessä.
Jos et halua sähläillä itse tiedostojen kanssa tai haluat nähdä myös hieman nopeusbenchmarkkia muita vaihtoehtoja vastaan, löytyy koodi myös Planet Source Codesta.
' clsBSTR: a fast string handling class for big strings ' ----------------------------------------------------------- ' By Vesa Piittinen < vesa.piittinen.name > 2007-04-28 ' This copyright notice must be left intact in the beginning of this file. ' License: http://creativecommons.org/licenses/by/1.0/fi/deed.en ' TODO ideas: ' - Array functions (Join and Split) ' - Boyer-Moore improvement for InStr (when keyword longer than 5 chars/binary or 3 chars/text) ' - Count (number of occurances of a keyword in the string) ' - File loading and saving (support for ANSI, UTF-8 and UTF-16 files, with and without BoM) ' - InStrRev ' - Pad ' - Replace Option Explicit Const ALLOCDEFAULT = 64 ' default extra allocation size for buffer Const ALLOCMAXIMUM = 134217728 ' limit maximum allocation size to 128 MB Private AllocExtra As Long ' contains the main data in BSTR format: 4 bytes length + data + 2 bytes NULLCHAR Private Char() As Integer Private CharLB As Long Private CharRealUB As Long Private CharUB As Long Private Chars As Long ' our very own fake BSTR string Private CharStr As String Private CharStrPtr() As Long Private CharStrHeader(5) As Long ' points to the first four bytes of Char to give BSTR compatibility Private CharLen() As Long Private CharLenHeader(5) As Long ' for fast access to string data Private CharTmp() As Integer Private CharTmp2() As Integer Private CharHeader(5) As Long Private CharHeader2(5) As Long Private Sub Allocate(ByVal Characters As Long, Optional ByVal PrependChars As Long = 0, Optional ByVal PreserveContent As Boolean = True) Dim lngOldLB As Long, lngOldChars As Long, lngPtr As Long, blnDataMove As Boolean If Characters < 0 Then Characters = 0 If PrependChars < 0 Then PrependChars = 0 ' store number of characters lngOldChars = Chars + Chars Chars = Characters + PrependChars ' store LBound(Char) lngOldLB = CharLB CharLB = CharLB - PrependChars blnDataMove = (CharLB < 2) Or (CharLB > AllocExtra + 2) If Not blnDataMove Then Else CharLB = 2 + AllocExtra ' store UBound(Char) CharUB = Chars + CharLB ' see if we preserve old content If PreserveContent Then ' see if we need to allocate more space If CharRealUB < CharUB Then CharRealUB = CharUB + AllocExtra ReDim Preserve Char(CharRealUB) End If ' move data if LBound changed If blnDataMove And lngOldChars > 0 Then RtlMoveLongVV VarPtr(Char(CharLB + PrependChars)), VarPtr(Char(lngOldLB)), lngOldChars End If ' make sure always ends in nullchar Char(CharUB) = 0 ' keep CharStr in correct memory pointer lngPtr = VarPtr(Char(CharLB)) CharStrPtr(0) = lngPtr ' keep CharLen(0) in correct memory pointer CharLenHeader(3) = lngPtr - 4& ' keep character count up to date to have BSTR compatibility If Chars > 0 Then CharLen(0) = Chars * 2 Else CharLen(0) = 0 End If Else ' see if we need to allocate more space If CharRealUB < CharUB Then CharRealUB = CharUB + AllocExtra ReDim Char(CharRealUB) End If ' keep CharStr in correct memory pointer lngPtr = VarPtr(Char(CharLB)) CharStrPtr(0) = lngPtr ' keep CharLen(0) in correct memory pointer CharLenHeader(3) = lngPtr - 4& ' keep character count up to date to have BSTR compatibility If Chars > 0 Then CharLen(0) = Chars * 2 End If End Sub ' add data after the end of the string Public Function Append(ByRef Text As String) As clsBSTR Dim lngLen As Long, lngChars As Long lngLen = Len(Text) If lngLen > 0 Then lngChars = Chars ' see if we can avoid the costly Allocate call If CharUB + lngLen <= CharRealUB Then CharUB = CharUB + lngLen Char(CharUB) = 0 Chars = Chars + lngLen CharLen(0) = Chars * 2 Else Allocate Chars + lngLen End If vbaCopyBytes lngLen * 2, CharStrPtr(0) + lngChars * 2, StrPtr(Text) 'Mid$(CharStr, lngChars + 1, lngLen) = Text End If ' return myself Set Append = Me End Function Public Property Get Asc(ByVal Pos As Long) As Integer If Pos >= 0 And Pos < Chars Then Asc = Char(Pos + CharLB) End Property Public Property Let Asc(ByVal Pos As Long, ByVal NewValue As Integer) If Pos >= 0 And Pos < Chars Then Char(Pos + CharLB) = NewValue End Property Public Property Get BufferAllocSize() As Long BufferAllocSize = AllocExtra End Property Public Property Let BufferAllocSize(ByVal NewValue As Long) If NewValue < 0 Then AllocExtra = 0 ElseIf BufferSize > ALLOCMAXIMUM Then AllocExtra = ALLOCMAXIMUM Else AllocExtra = NewValue End If End Property Friend Sub BufferCopy(ByVal FromPtr As Long, ByVal DataLen As Long) Allocate DataLen \ 2 'RtlMoveLongVV VarPtr(Char(CharLB)), FromPtr, DataLen vbaCopyBytes DataLen, VarPtr(Char(CharLB)), FromPtr End Sub Public Property Get BufferSize() As Long BufferSize = CharRealUB End Property Public Property Let BufferSize(ByVal NewValue As Long) Dim lngPtr As Long If NewValue >= 0 Then CharRealUB = NewValue + CharLB Else CharRealUB = CharLB End If If CharRealUB < CharUB Then Chars = (CharRealUB - CharLB) CharUB = CharRealUB End If ReDim Preserve Char(CharRealUB) Char(CharUB) = 0 ' keep CharStr in correct memory pointer lngPtr = VarPtr(Char(CharLB)) CharStrPtr(0) = lngPtr ' keep CharLen(0) in correct memory pointer CharLenHeader(3) = lngPtr - 4& CharLen(0) = Chars * 2 End Property Public Property Get Chr(ByVal Pos As Long) As String If Pos >= 0 And Pos < Chars Then Chr = ChrW$(Char(Pos + CharLB)) End Property Public Property Let Chr(ByVal Pos As Long, ByRef NewValue As String) If Pos >= 0 And Pos < Chars Then If LenB(NewValue) > 0 Then Char(Pos + CharLB) = AscW(NewValue) End If End Property ' returns a given number of characters from the left as new clsBSTR Public Function GetLeft(ByVal Length As Long) As clsBSTR Set GetLeft = New clsBSTR If Length > 0 Then If Length < Chars Then GetLeft.BufferCopy VarPtr(Char(CharLB)), Length * 2 Else GetLeft.BufferCopy VarPtr(Char(CharLB)), CharLen(0) End If End If End Function ' returns a clip of a string as new clsBSTR Public Function GetMiddle(ByVal Pos As Long, Optional ByVal Length As Long = -1&) As clsBSTR Dim lngA As Long Set GetMiddle = New clsBSTR Pos = Pos + CharLB If Pos >= CharLB And Pos <= CharUB Then If Length >= -1& Then If Pos + Length >= CharUB Or Length = -1& Then Length = CharUB - Pos If Length > 0 Then GetMiddle.BufferCopy VarPtr(Char(Pos)), Length * 2 End If End If End Function ' returns a given number of characters from the right as new clsBSTR Public Function GetRight(ByVal Length As Long) As clsBSTR Set GetRight = New clsBSTR If Length > 0 Then If Length < Chars Then GetRight.BufferCopy VarPtr(Char(CharUB - Length)), Length * 2 Else GetRight.BufferCopy VarPtr(Char(CharLB)), CharLen(0) End If End If End Function Public Function GetValue() As clsBSTR Set GetValue = New clsBSTR GetValue.BufferCopy VarPtr(Char(CharLB)), CharLen(0) End Function Public Function InStr(ByRef Keyword As String, Optional ByVal Start As Long = 0&, Optional ByVal Compare As VbCompareMethod = vbBinaryCompare) As Long Dim lngKeyLen As Long, lngKeyLenB As Long, lngKeyEnd As Long, lngA As Long, lngB As Long Dim strLCase As String, strUCase As String Dim intChar1 As Integer, intChar1b As Integer Dim intChar2 As Integer Dim intChar3 As Integer, intChar3b As Integer Dim intChar4 As Integer If CharUB < 3 Then InStr = -1&: Exit Function lngKeyLen = Len(Keyword) lngKeyLenB = LenB(Keyword) If lngKeyLen = 0 Then InStr = Start: Exit Function If CharLen(0) < lngKeyLenB Then InStr = -1&: Exit Function If Start < 0 Or Start > (CharLen(0) - lngKeyLenB + 1) \ 2 Then InStr = -1&: Exit Function If Compare = vbBinaryCompare Then Dim lngResult As Long lngResult = vbaInStr(0&, StrPtr(Keyword), CharStrPtr(0), Start + 1) InStr = lngResult - 1 Exit Function CharHeader(3) = StrPtr(Keyword) CharHeader(4) = lngKeyLen intChar1 = CharTmp(0) If lngKeyLen = 1 Then For lngA = CharLB To CharUB - 1 intChar2 = Char(lngA) If intChar1 = intChar2 Then Exit For Next lngA If lngA < CharUB Then InStr = lngA - CharLB Else InStr = -1& ElseIf lngKeyLen = 2 Then intChar3 = CharTmp(1) For lngA = CharLB To CharUB - 2 intChar2 = Char(lngA) If intChar1 = intChar2 Then intChar4 = Char(lngA + 1) If intChar3 = intChar4 Then Exit For End If Next lngA If lngA < CharUB Then InStr = lngA - CharLB Else InStr = -1& Else lngKeyEnd = lngKeyLen - 1 intChar3 = CharTmp(lngKeyEnd) For lngA = CharLB To CharUB - lngKeyLen intChar2 = Char(lngA) If intChar1 = intChar2 Then intChar4 = Char(lngA + lngKeyEnd) If intChar3 = intChar4 Then For lngB = 1 To lngKeyEnd - 1 If Char(lngB + lngA) <> CharTmp(lngB) Then Exit For Next lngB If lngB = lngKeyEnd Then Exit For End If End If Next lngA If lngA < CharUB Then InStr = lngA - CharLB Else InStr = -1& End If Else ' create local copies of the keywords strUCase = Keyword strLCase = Keyword CharHeader(3) = StrPtr(strUCase) CharHeader(4) = lngKeyLen CharHeader2(3) = StrPtr(strLCase) CharHeader2(4) = lngKeyLen ' convert to lower case For lngA = 0 To lngKeyLen - 1 CharTmp(lngA) = UTable(CharTmp(lngA) And &HFFFF&) CharTmp2(lngA) = LTable(CharTmp(lngA) And &HFFFF&) Next lngA intChar1 = CharTmp(0) intChar1b = CharTmp2(0) If lngKeyLen = 1 Then For lngA = CharLB To CharUB - 1 intChar2 = Char(lngA) If intChar1 = intChar2 Or intChar1b = intChar2 Then Exit For Next lngA If lngA < CharUB Then InStr = lngA - CharLB Else InStr = -1& ElseIf lngKeyLen = 2 Then intChar3 = CharTmp(1) intChar3b = CharTmp2(1) For lngA = CharLB To CharUB - 2 intChar2 = Char(lngA) If intChar1 = intChar2 Or intChar1b = intChar2 Then intChar4 = Char(lngA + 1) If intChar3 = intChar4 Or intChar3b = intChar4 Then Exit For End If Next lngA If lngA < CharUB Then InStr = lngA - CharLB Else InStr = -1& Else lngKeyEnd = lngKeyLen - 1 intChar3 = CharTmp(lngKeyEnd) intChar3b = CharTmp2(lngKeyEnd) For lngA = CharLB To CharUB - lngKeyLen intChar2 = Char(lngA) If intChar1 = intChar2 Or intChar1b = intChar2 Then intChar4 = Char(lngA + lngKeyEnd) If intChar3 = intChar4 Or intChar3b = intChar4 Then For lngB = 1 To lngKeyEnd - 1 intChar2 = Char(lngB + lngA) If intChar2 <> CharTmp(lngB) And intChar2 <> CharTmp2(lngB) Then Exit For Next lngB If lngB = lngKeyEnd Then Exit For End If End If Next lngA If lngA < CharUB Then InStr = lngA - CharLB Else InStr = -1& End If End If End Function ' lower case Public Function LCase() As clsBSTR Dim lngA As Long ' loop through all characters and switch to lower case For lngA = CharLB To CharUB - 1 Char(lngA) = LTable(Char(lngA) And &HFFFF&) Next lngA ' return myself Set LCase = Me End Function ' returns a given number of characters from the left Public Function Left(ByVal Length As Long) As String If Length > 0 Then If Length < Chars Then Left = Strings.Left$(CharStr, Length) Else Left = CharStr End If End If End Function ' returns string length in characters Public Function Length() As Long Length = Chars End Function ' returns string length in bytes Public Function LengthB() As Long LengthB = CharLen(0) End Function Public Property Get Middle(ByVal Pos As Long, Optional ByVal Length As Long = -1&) As String Dim lngPos As Long lngPos = Pos + CharLB If lngPos >= CharLB And lngPos <= CharUB Then If Length >= -1& Then If (lngPos + Length >= CharUB Or Length = -1&) Then Length = CharUB - lngPos If Length > 0 Then Middle = Mid$(CharStr, Pos + 1, Length) End If End If End Property Public Property Let Middle(ByVal Pos As Long, Optional ByVal Length As Long = -1&, ByRef NewValue As String) Dim lngA As Long, lngDiff As Long, lngEnd As Long, lngPos As Long, lngLen As Long lngPos = Pos + CharLB If lngPos >= CharLB And lngPos <= CharUB Then If Length >= -1& Then If lngPos + Length >= CharUB Or Length = -1& Then Length = CharUB - lngPos lngLen = Len(NewValue) lngDiff = Length - lngLen If lngDiff = 0 Then Mid$(CharStr, Pos + 1, Length) = NewValue 'vbaCopyBytes Length * 2, VarPtr(Char(lngPos)), StrPtr(NewValue) ElseIf lngDiff > 0 Then If lngLen > 0 Then Mid$(CharStr, Pos + 1, lngLen) = NewValue 'vbaCopyBytes Length * 2, VarPtr(Char(lngPos)), StrPtr(NewValue) lngPos = lngPos + lngLen End If lngA = CharUB - lngPos - lngDiff If lngA > 0 Then RtlMoveLongVV VarPtr(Char(lngPos)), VarPtr(Char(lngPos + lngDiff)), lngA * 2 Allocate Chars - lngDiff Else lngPos = lngPos + Length lngEnd = (CharUB - lngPos) * 2 Allocate Chars - lngDiff If lngEnd > 0 Then RtlMoveLongVV VarPtr(Char(lngPos - lngDiff)), VarPtr(Char(lngPos)), lngEnd If lngLen > 0 Then Mid$(CharStr, Pos + 1, lngLen) = NewValue 'vbaCopyBytes Length * 2, VarPtr(Char(lngPos)), StrPtr(NewValue) End If End If End If End Property Public Property Set Middle(ByVal Pos As Long, Optional ByVal Length As Long = -1&, ByRef NewValue As clsBSTR) Dim lngA As Long, lngDiff As Long, lngEnd As Long, lngPos As Long, lngLen As Long, strValue As String lngPos = Pos + CharLB If lngPos >= CharLB And lngPos <= CharUB Then If Length >= -1& Then If lngPos + Length >= CharUB Or Length = -1& Then Length = CharUB - lngPos If Not NewValue Is Nothing Then strValue = NewValue.Value lngLen = Len(strValue) lngDiff = Length - lngLen If lngDiff = 0 Then Mid$(CharStr, Pos + 1, Length) = strValue ElseIf lngDiff > 0 Then If lngLen > 0 Then Mid$(CharStr, Pos + 1, lngLen) = strValue lngPos = lngPos + lngLen End If lngA = CharUB - lngPos - lngDiff If lngA > 0 Then RtlMoveLongVV VarPtr(Char(lngPos)), VarPtr(Char(lngPos + lngDiff)), lngA * 2 Allocate Chars - lngDiff Else lngPos = lngPos + Length lngEnd = (CharUB - lngPos) * 2 Allocate Chars - lngDiff If lngEnd > 0 Then RtlMoveLongVV VarPtr(Char(lngPos - lngDiff)), VarPtr(Char(lngPos)), lngEnd If lngLen > 0 Then Mid$(CharStr, Pos + 1, lngLen) = strValue End If End If End If End Property ' proper case Public Function PCase() As clsBSTR Dim lngA As Long, blnLCase As Boolean, lngChar As Long ' loop through all characters and switch to proper case For lngA = CharLB To CharUB - 1 lngChar = Char(lngA) And &HFFFF& If Not PTable(lngChar) Then If blnLCase Then Char(lngA) = LTable(lngChar) Else Char(lngA) = UTable(lngChar) blnLCase = True End If Else blnLCase = False End If Next lngA ' return myself Set PCase = Me End Function ' add data before the beginning of the string Public Function Prepend(ByRef Text As String) As clsBSTR Dim lngLen As Long, lngLenB As Long lngLenB = LenB(Text) If lngLenB > 0 Then lngLen = lngLenB \ 2 ' avoid the costly Allocate call If CharLB - lngLen >= 2 Then CharLB = CharLB - lngLen Chars = Chars + lngLen CharStrPtr(0) = CharStrPtr(0) - lngLenB CharLenHeader(3) = CharLenHeader(3) - lngLenB CharLen(0) = Chars * 2 Else Allocate Chars, lngLen End If vbaCopyBytes lngLenB, CharStrPtr(0), StrPtr(Text) 'Mid$(CharStr, 1, lngLen) = Text End If ' return myself Set Prepend = Me End Function ' returns the StrPtr to data Public Function Ptr() As Long Ptr = VarPtr(Char(CharLB)) End Function ' replicates the current string to given number of copies Public Function Replicate(Optional ByVal Count As Long = 2) As clsBSTR Dim lngLenB As Long, lngPtrSrc As Long, lngPtrDest As Long, lngPtrOut As Long ' validate count If Count > 1 Then lngLenB = CharLen(0) Allocate Chars * Count lngPtrSrc = VarPtr(Char(CharLB)) lngPtrDest = lngPtrSrc + lngLenB lngPtrOut = VarPtr(Char(CharUB)) Do While lngPtrOut > lngPtrDest + lngLenB RtlMoveLongVV lngPtrDest, lngPtrSrc, lngLenB lngPtrDest = lngPtrDest + lngLenB lngLenB = lngLenB * 2 Loop If lngPtrDest < lngPtrOut Then RtlMoveLongVV lngPtrDest, lngPtrSrc, lngPtrOut - lngPtrDest ElseIf Count = 0 Then Allocate 0 End If ' return self Set Replicate = Me End Function ' reverses the string; returns object itself Public Function Reverse() As clsBSTR ' see if we have anything to reverse If Chars > 1 Then Mid$(CharStr, 1&, Chars) = vbaStrReverse(CharStrPtr(0)) ' return self Set Reverse = Me End Function ' returns a given number of characters from the right Public Function Right(ByVal Length As Long) As String If Length > 0 Then If Length < Chars Then Right = Strings.Right$(CharStr, Length) Else Right = CharStr End If End If End Function Public Function SetValue(ByRef Text As String) As clsBSTR Allocate Len(Text) If CharLen(0) > 0 Then Mid$(CharStr, 1, Chars) = Text Set SetValue = Me End Function ' trim by given string Public Function Trim(Optional ByRef Characters As String = " ", Optional TrimType As vbTrimType = vbTrimBoth) As clsBSTR Dim TrimChar As Integer, lngTrimType As Long Dim lngLeft As Long, lngRight As Long, lngChar As Long, lngA As Long Dim lngPtr As Long lngChar = LenB(Characters) ' see if we got trim characters If lngChar = 1 Or lngChar = 2 Then ' one character only TrimChar = AscW(Characters) ' see if we trim from the left If (TrimType And vbTrimLeft) = vbTrimLeft Then For lngLeft = CharLB To CharUB - 1 If Char(lngLeft) = TrimChar Then Else Exit For Next lngLeft Else lngLeft = CharLB End If If (TrimType And vbTrimRight) = vbTrimRight Then For lngRight = CharUB - 1 To lngLeft Step -1 If Char(lngRight) = TrimChar Then Else Exit For Next lngRight Else lngRight = CharUB - 1 End If If lngLeft <= lngRight Then Chars = (lngRight - lngLeft) + 1 CharUB = lngRight CharLB = lngLeft ' keep CharStr in correct memory pointer lngPtr = VarPtr(Char(CharLB)) CharStrPtr(0) = lngPtr CharLenHeader(3) = lngPtr - 4& CharLen(0) = Chars * 2 'RtlMoveLongVV VarPtr(Char(CharLB)), VarPtr(Char(lngLeft)), (lngRight - lngLeft + 1) * 2 'Allocate (lngRight - lngLeft + 1) Else Allocate 0 End If ElseIf lngChar <> 0 Then ' generate a table of characters to use for trimming CharHeader(3) = StrPtr(Characters) CharHeader(4) = Len(Characters) For lngA = 0 To CharHeader(4) - 1 TTable(CharTmp(lngA) And &HFFFF&) = True Next lngA ' see if we trim from the left If (TrimType And vbTrimLeft) = vbTrimLeft Then For lngLeft = CharLB To CharUB - 1 If TTable(Char(lngLeft) And &HFFFF&) Then Else Exit For Next lngLeft Else lngLeft = CharLB End If If (TrimType And vbTrimRight) = vbTrimRight Then For lngRight = CharUB - 1 To lngLeft Step -1 If TTable(Char(lngRight) And &HFFFF&) Then Else Exit For Next lngRight Else lngRight = CharUB - 1 End If If lngLeft <= lngRight Then Chars = (lngRight - lngLeft) + 1 CharUB = lngRight CharLB = lngLeft ' keep CharStr in correct memory pointer lngPtr = VarPtr(Char(CharLB)) CharStrPtr(0) = lngPtr CharLenHeader(3) = lngPtr - 4& CharLen(0) = Chars * 2 'RtlMoveLongVV VarPtr(Char(CharLB)), VarPtr(Char(lngLeft)), (lngRight - lngLeft + 1) * 2 'Allocate (lngRight - lngLeft + 1) Else Allocate 0 End If ' restore trimchars to null state For lngA = 0 To CharHeader(4) - 1 TTable(CharTmp(lngA) And &HFFFF&) = False Next lngA End If ' return myself Set Trim = Me End Function ' upper case Public Function UCase() As clsBSTR Dim lngA As Long ' loop through all characters and switch to upper case For lngA = CharLB To CharUB - 1 Char(lngA) = UTable(Char(lngA) And &HFFFF&) Next lngA ' return myself Set UCase = Me End Function ' get string Public Property Get Value() As String Value = CharStr End Property ' set string Public Property Let Value(ByRef Text As String) Dim lngLen As Long lngLen = Len(Text) If lngLen > 0 Then If lngLen <> Chars Then If CharLB + lngLen <= CharRealUB Then CharUB = CharLB + lngLen Char(CharUB) = 0 Chars = lngLen CharLen(0) = Chars * 2 Else Allocate lngLen End If End If vbaCopyBytes CharLen(0), CharStrPtr(0), StrPtr(Text) 'Mid$(CharStr, 1, Chars) = Text Else Allocate 0 End If End Property ' set string from other object Public Property Set Value(ByRef Text As clsBSTR) If Not Text Is Nothing Then BufferCopy Text.Ptr, Text.LengthB Else Allocate 0 End If End Property ' get string as an integer array Public Property Get ValueArray() As Integer() Dim intOut() As Integer If Chars > 0 Then ReDim intOut(CharUB - CharLB) RtlMoveLongVV VarPtr(intOut(0)), VarPtr(Char(CharLB)), CharLen(0) ValueArray = intOut Erase intOut End If End Property ' set string from integer array Public Property Let ValueArray(ByRef NewValue() As Integer) Dim lngPtr As Long, lngLow As Long, lngHigh As Long ' check if the array is initialized RtlMoveMemory lngPtr, ByVal VarPtrArray(NewValue), 4 If lngPtr = 0 Then Exit Property ' check boundaries lngLow = LBound(NewValue) lngHigh = UBound(NewValue) If lngHigh < lngLow Then Exit Property ' reserve space for the new data Allocate lngHigh - lngLow + 1 ' copy data RtlMoveLongVV VarPtr(Char(CharLB)), VarPtr(NewValue(lngLow)), (lngHigh - lngLow + 1) * 2 End Property Private Sub Class_Initialize() ' only set these once: see modBSTR If Not PTable(0) Then GenerateCaseTables ' create a one element long safearray for fast access: CharStrPtr(0) CharStrHeader(0) = 1& CharStrHeader(1) = 4& CharStrHeader(3) = VarPtr(CharStr) CharStrHeader(4) = 1& RtlMoveLongVR ByVal VarPtrArray(CharStrPtr), VarPtr(CharStrHeader(0)), 4& ' create a one element long safearray for fast access: CharLen(0) CharLenHeader(0) = 1& CharLenHeader(1) = 4& 'CharLenHeader(3) is automatically set in Allocate CharLenHeader(4) = 1& RtlMoveLongVR ByVal VarPtrArray(CharLen), VarPtr(CharLenHeader(0)), 4& ' this is our primary safearray for strings: CharTmp CharHeader(0) = 1& CharHeader(1) = 2& RtlMoveLongVR ByVal VarPtrArray(CharTmp), VarPtr(CharHeader(0)), 4& ' this is our secondary safearray for strings: CharTmp2 CharHeader2(0) = 1& CharHeader2(1) = 2& RtlMoveLongVR ByVal VarPtrArray(CharTmp2), VarPtr(CharHeader2(0)), 4& ' initial allocation: also resets all our helper variables AllocExtra = ALLOCDEFAULT Allocate 0, 0, False End Sub Private Sub Class_Terminate() ' unset faked BSTR CharStrPtr(0) = 0& ' unset custom safearrays RtlMoveLongVR ByVal VarPtrArray(CharStrPtr), 0&, 4& RtlMoveLongVR ByVal VarPtrArray(CharLen), 0&, 4& RtlMoveLongVR ByVal VarPtrArray(CharTmp), 0&, 4& RtlMoveLongVR ByVal VarPtrArray(CharTmp2), 0&, 4& ' free memory Erase Char End Sub
' clsBSTR: a fast string handling class for big strings ' ----------------------------------------------------------- ' By Vesa Piittinen < vesa.piittinen.name > 2007-04-28 ' This copyright notice must be left intact in the beginning of this file. ' License: http://creativecommons.org/licenses/by/1.0/fi/deed.en ' ' modBSTR.bas for use with clsBSTR.cls Option Explicit ' case tables Public LTable(65535) As Integer ' to lower case codes Public PTable(65535) As Boolean ' proper case separators Public UTable(65535) As Integer ' to upper case codes Public TTable(65535) As Boolean ' temp table that is always kept in zero state when not in use ' generic additions Public Const vbQuote As String = """" ' for Pad Public Enum vbPadType vbPadLeft = 1 vbPadRight = 2 vbPadBoth = 3 End Enum ' for Trim Public Enum vbTrimType vbTrimLeft = 1 vbTrimRight = 2 vbTrimBoth = 3 End Enum ' API declarations Public Declare Sub RtlMoveMemory Lib "ntdll.dll" (ByRef lpvDest As Any, ByRef lpvSrc As Any, ByVal cbLen As Long) 'Public Declare Sub RtlMoveLongVR Lib "ntdll.dll" Alias "RtlMoveMemory" (ByRef lpvDest As Any, ByRef lpvSrc As Any, ByVal cbLen As Long) 'Public Declare Sub RtlMoveLongVV Lib "ntdll.dll" Alias "RtlMoveMemory" (ByVal lpvDest As Long, ByVal lpvSrc As Long, ByVal cbLen As Long) 'Public Declare Function VarPtrArray Lib "msvbvm50.dll" Alias "VarPtr" (Var() As Any) As Long Public Declare Function VarPtrArray Lib "msvbvm60.dll" Alias "VarPtr" (Var() As Any) As Long Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef lpvDest As Any, ByRef lpvSrc As Any, ByVal cbLen As Long) Private Declare Function GetCurrentProcessId Lib "kernel32" () As Long Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long Private Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long Private Sub DeclareAPI(ByVal AddressOfDest As Long, ByRef API As String, ByRef Module As String) Dim lngModuleHandle As Long, AddressOfSrc As Long Dim larJMPASM(1) As Long Dim lngProcessHandle As Long, lngBytesWritten As Long ' get handle for module lngModuleHandle = GetModuleHandle(Module) If lngModuleHandle = 0 Then lngModuleHandle = LoadLibrary(Module) ' if failed, we can't do anything If lngModuleHandle = 0 Then Exit Sub ' get address of function AddressOfSrc = GetProcAddress(lngModuleHandle, API) ' if failed, we can't do anything If AddressOfSrc = 0 Then Exit Sub ' get a handle for current process lngProcessHandle = OpenProcess(&H1F0FFF, 0&, GetCurrentProcessId) ' if failed, we can't do anything If lngProcessHandle = 0 Then Exit Sub ' check if we are in the IDE If InIDE Then ' get the real location of the procedure CopyMemory AddressOfDest, ByVal AddressOfDest + &H16&, 4& End If ' set ASM JMP larJMPASM(0) = &HE9000000 ' set JMP parameter (how many bytes to jump) larJMPASM(1) = AddressOfSrc - AddressOfDest - 5 ' replace original procedure with the JMP WriteProcessMemory lngProcessHandle, ByVal AddressOfDest, ByVal VarPtr(larJMPASM(0)) + 3, 5, lngBytesWritten ' close handle for current process CloseHandle lngProcessHandle End Sub ' this generates case tables: optimized to separate module to save memory and extra speed Public Sub GenerateCaseTables() Dim strTest As String, lngA As Long ' generate upper and lower case link tables For lngA = 0 To 65535 strTest = ChrW$(lngA) UTable(lngA) = AscW(UCase$(strTest)) LTable(lngA) = AscW(LCase$(strTest)) Next lngA ' proper case separators: these are the same as in StrConv(vbProperCase) PTable(0) = True PTable(9) = True PTable(10) = True PTable(11) = True PTable(12) = True PTable(13) = True PTable(32) = True PTable(8192) = True PTable(8193) = True PTable(8194) = True PTable(8195) = True PTable(8196) = True PTable(8197) = True PTable(8198) = True PTable(12288) = True ' hack: enum names to prevent VB IDE from messing their case Dim vbPadLeft As vbPadType, vbPadRight As vbPadType, vbPadBoth As vbPadType Dim vbTrimLeft As vbTrimType, vbTrimRight As vbTrimType, vbTrimBoth As vbTrimType End Sub Public Function InIDE() As Boolean Debug.Assert Not InIDEtest(InIDE) End Function Private Function InIDEtest(ByRef IDE As Boolean) As Boolean IDE = True End Function Private Sub ReplaceSub(ByVal AddressOfDest As Long, ByVal AddressOfSrc As Long) Dim larJMPASM(1) As Long Dim lngProcessHandle As Long, lngBytesWritten As Long ' get a handle for current process lngProcessHandle = OpenProcess(&H1F0FFF, 0&, GetCurrentProcessId) ' if failed, we can't do anything If lngProcessHandle = 0 Then Exit Sub ' check if we are in the IDE If InIDE Then ' get the real locations of the procedures CopyMemory AddressOfDest, ByVal AddressOfDest + &H16&, 4& CopyMemory AddressOfSrc, ByVal AddressOfSrc + &H16&, 4& End If ' set ASM JMP larJMPASM(0) = &HE9000000 ' set JMP parameter (how many bytes to jump) larJMPASM(1) = AddressOfSrc - AddressOfDest - 5 ' replace original procedure with the JMP WriteProcessMemory lngProcessHandle, ByVal AddressOfDest, ByVal VarPtr(larJMPASM(0)) + 3, 5, lngBytesWritten ' close handle for current process CloseHandle lngProcessHandle End Sub Public Sub RtlMoveLongVR(ByVal lpvDest As Long, lpvSrc As Long, ByVal cbLen As Long) DeclareAPI AddressOf RtlMoveLongVR, "RtlMoveMemory", "ntdll.dll" RtlMoveLongVR lpvDest, lpvSrc, cbLen End Sub Public Sub RtlMoveLongVV(ByVal lpvDest As Long, ByVal lpvSrc As Long, ByVal cbLen As Long) DeclareAPI AddressOf RtlMoveLongVV, "RtlMoveMemory", "ntdll.dll" RtlMoveLongVV lpvDest, lpvSrc, cbLen End Sub Public Sub vbaCopyBytes(ByVal Length As Long, ByVal dest As Long, ByVal Src As Long) DeclareAPI AddressOf modBSTR.vbaCopyBytes, "__vbaCopyBytes", "msvbvm60.dll" vbaCopyBytes Length, dest, Src End Sub Public Function vbaInStr(ByVal Compare As Long, ByVal String2 As Long, ByVal String1 As Long, ByVal Start As Long) As Long DeclareAPI AddressOf modBSTR.vbaInStr, "__vbaInStr", "msvbvm60.dll" vbaInStr = vbaInStr(Compare, String2, String1, Start) End Function Public Function vbaStrReverse(ByVal Text As Long) As String DeclareAPI AddressOf modBSTR.vbaStrReverse, "rtcStrReverse", "msvbvm60.dll" vbaStrReverse = vbaStrReverse(Text) End Function
Aihe on jo aika vanha, joten et voi enää vastata siihen.