Visual Basic 6:ssa ei varsinaisesti ole olemassa ns. pointtereita, joiden avulla voi käsitellä muistia suoraan. Kiertotie on kuitenkin olemassa ja VB:stä löytyy tuki muutamalle olennaiselle komennolle, mm. VarPtr ja StrPtr (variable pointer ja string pointer). Näiden avulla saa tietää, missä kohtaa tietokoneen muistia muuttuja oikeasti sijaitsee. Näin voi esimerkiksi käyttää CopyMemorya (RtlMoveMemory) kopioimaan tietoa jostakin muuttujasta johonkin toiseen muuttujaan.
Ongelmaksi kuitenkin muotoutuu se, että CopyMemoryn käyttö on hitaahkoa: API-kutsut ovat paljon raskaampia kuin esimerkiksi taulukkomuuttujan (array) käsitteleminen, koska API-kutsun tekeminen suorittaa muutamia "ylimääräisiä" kutsuja.
Ratkaisuna ongelmaan löytyykin kehittyneempi ja ovelampi keino. Myös taulukkomuuttujilla on olemassa muistissa oma rakenteensa. Mitäpä jos huijaamme taulukkomuuttujan osoittamaan itsetehtyyn muuttujarakenteeseen? Tämä mahdollistaa sen, että voimme tehdä taulukkomuuttujan joka osoittaa mihin tahansa kohtaan muistissa! Toisin sanoen voisimme muuttaa toisten muuttujien, vaikkapa jonkin tekstimuuttujan, sisältöä käyttämällä omaa taulukkomuuttujaamme.
Taulukkomuuttujan rakenne tunnetaan nimellä SAFEARRAY. Tämän rakenne muuttuu hieman sen mukaan, montako ulottuvuutta muuttujalla on. Helppouden, selkeyden ja nopeuden vuoksi kannattaa yleensä käyttää yksiulotteista muuttujaa.
Alle laittamani esimerkkikoodi on hyvin yksinkertainen, eikä käytä tämän menetelmän todellista potentiaalia hyväksi. Se kuitenkin esittää runsain kommentein, miten homma saadaan tehtyä. Huomattavaa on, että tämä menetelmä on erittäin nopea, joten konekielelle käännetty koodi toimii paljon nopeammin kuin esim. Mid$-komennoin tai Replace$:lla vastaavan tempun tekevä koodi. Mitä enemmän käsiteltävää, sitä suurempi on nopeusetu!
Tätä menetelmää voi käyttää myös kuvadatan käsittelyyn muistissa suoraan ilman että tarvitsee tehdä uutta kopiota kuvasta (esimerkiksi käyttäen GetBitmapBits-APIa). Mahdollisuuksia on monia, milloin vain on tarvetta nopeudelle :)
Form1.frm
Option Explicit Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef lpvDest As Any, ByRef lpvSrc As Any, ByVal cbLen As Long) ' normaali VarPtr ei ota vastaan taulukkomuuttujia, joten lisätään tuki sille tällä tavalla Private Declare Function VarPtrArray Lib "msvbvm60.dll" Alias "VarPtr" (Var() As Any) As Long ' yksiulotteisen taulukkomuuttujan rakenteen tiedot Private Type SafeArray1D Dimensions As Integer ' ulottuvuudet (aseta arvoksi aina 1) FeatureFlags As Integer ' ominaisuudet (nolla kelpaa peruskäytössä) ElementSize As Long ' yhden elementin koko tavuina (1 käy Bytelle, 2 Integerille ja Booleanille...) LockCount As Long ' lukitukset (en tiedä tämän tarkkaa tarkoitusta) DataPtr As Long ' pointteri siihen kohtaan muistissa, jossa tieto sijaitsee Elements1D As Long ' ensimmäisen ulottuvuuden elementtien määrä LBound1D As Long ' ensimmäisen ulottuvuuden alaindeksi (nolla on hyvä) End Type ' määritä SA-muuttuja, joka sisältää meidän oman taulukkomuuttujamme tiedot Dim SA As SafeArray1D ' määritä tyhjä Taulukko-muuttuja, jonka huijaamme toimimaan omien tietojemme mukaisesti Dim Taulukko() As Integer
Form1.frm
Private Sub Form_Load() ' alustetaan oman taulukkomuuttujan tiedot With SA ' yksiulotteinen .Dimensions = 1 ' yhden elementin koko: kaksi tavua .ElementSize = 2 ' elementtien määrä: todella monta (suurin positiivinen Long-arvo) .Elements1D = &H7FFFFFFF ' asetamme elementtien määrän näin, jottei sitä tarvitse muutella jatkuvasti End With ' asetetaan Taulukko-muuttujalle nyt tämä meidän oma SafeArray-rakenteemme CopyMemory ByVal VarPtrArray(Taulukko), VarPtr(SA), 4& End Sub Private Sub Form_Unload(Cancel As Integer) ' palautetaan Taulukko-muuttujan alkuperäinen tila, muuten VB kaatuu ' tärkeää! nollan täytyy olla Long, joten &-merkki on pakollinen! CopyMemory ByVal VarPtrArray(Taulukko), 0&, 4& ' emme alustaneet Taulukkoa mitenkään (esim. käyttämälle ReDimiä), ' joten se ei ennestään osoittanut minnekään muistissa: sen pointterin arvo oli 0 End Sub
Form1.frm: lisää komentonappula Command1 ja sille tämä koodi
Private Sub Command1_Click() Dim strTesti As String Dim lngA As Long ' asetetaan testimuuttujaamme jotakin tekstiä strTesti = "BBB! Terve!" ' kerrotaan alkutilanne MsgBox strTesti, , "Ennen" ' huomioi: yksi merkki on aina kaksi tavua, siksi SA.ElementSize on kaksi! ' nyt jallitetaan VB:tä: muutetaan Taulukko osoittamaan tähän testimuuttujaan! SA.DataPtr = StrPtr(strTesti) ' muutetaan kaikki B-kirjaimet A-kirjaimiksi For lngA = 0 To Len(strTesti) - 1 ' B:n merkistökoodi on 66, A puolestaan on 65 If Taulukko(lngA) = 66 Then Taulukko(lngA) = 65 Next lngA ' lopuksi katsotaan miltä strTesti nyt näyttää! MsgBox strTesti, , "Jälkeen" End Sub
Vaikuttaa jännältä! Tällä tavalla siis voi tehdä raskaampaa nopeammin? Kokeilen myöhemmin...
Jep, voi. Käytännön tarkoitus on pudottaa pois turhia suuria muistinsiirtoja kuin myös mahdollistaa tietyn datan (kuten stringien) käsittely nopeammin.
Muista tallentaa usein, koska koodin pysäyttäminen IDE:ssä saattaa kaataa ohjelman ja VB:n. Itselläni kävin näin kerran tätä esimerkkiä tehdessä. Jostain kumman syystä ohjelman kaatuessa myös leikepöytä tyhjenee, joten tallentaminen on lähes ainoa keino estää tietoja katoilemasta.
Loistava pätkä! Ihmetyttää vain, miksei vb:hin ole integroitu mitään tämmöistä. Perinteistä, että pitää jallitaa kääntäjää. :)
Aika pitkälle historiallisista syistä: Visual Basic siirtyi vasta viitosversion myötä siihen, että ohjelma käännettiin konekielelle. Mahdollisesti myös pointterit olisivat päätyneet kieleen, jos Microsoft ei yksinkertaisesti olisi lopetuttanut VB:n kehitystä moneksi vuodeksi. Valitettavasti he kuitenkin keskittivät kaikki voimansa .NET:in kehittämiseen :/
Vaihtoehtobasiceistahan FreeBASICissa on ne kaikki mitä vaan tahtoo. Tosin siitä puuttuu vielä luokat, sitten kun ne löytyy niin aika kova kieli on kasassa.
Aihe on jo aika vanha, joten et voi enää vastata siihen.