Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: VB6: Tehokas merkkijonoluokka

Merri [30.04.2007 18:32:09]

#

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

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta