Ščitenje resursov v VBA II

V prejšnjem blogu sem omenil, da nam pri ščitenju resursov lahko pomagajo objekti in zdaj je čas, da si ogledamo kako.

Napišimo torej skrajno preprost objekt, ki ima samo konstruktor, destruktor ter inicializacijsko funkcijo, ki mu postavi ime, da mu lažje sledimo (poimenujmo ga objSciti):

Option Explicit

Private ime As String

Private Sub Class_Initialize()
  Debug.Print "NOV "
End Sub

Private Sub Class_Terminate()
  Debug.Print "BRIŠEM "; ime
End Sub

Public Sub init(aime As String)
  ime = aime
  Debug.Print "  POSTAVIM "; ime
End Sub

Uporabimo ga v preprosti proceduri:
Sub Proc()
  Dim a As New objSciti
  a.init "prvi"
End Sub

Če proceduro izvedemo, dobimo sledeč rezultat:
NOV
  POSTAVIM prvi
BRIŠEM prvi

Če funkcijo pogledamo, nam je jasno, da izpis niza ‘NOV’ povzroči prva vrstica kode, izpis niza ‘POSTAVIM prvi’ povzroči druga vrstica kode… Kaj pa izpis tretjega niza? Tretji niz pa izpiše destruktor, ki ga VBA pokliče sam ob zaključku procedure.

Malce nižje bomo sicer spoznali tudi, da pravzaprav prvi dve vrstici izpiše funkcija ‘init’, a to zdajle niti ni pomembno.

Kaj če funkcijo preoblikujemo tako, da sprejme ime in jo potem kličemo iz neke druge funkcije? Omenjena ideja je predstavljena spodaj:

Sub Proc2(ime As String)
  Dim a As New objSciti
  a.init ime
End Sub

Sub TestProc2()
  Dim i
  For i = 1 To 20
    Proc2 "spr"; i
  Next
End Sub

Če izvedemo testno proceduro ‘TestProc2‘, dobimo sledeč rezultat
NOV
  POSTAVIM spr1
BRIŠEM spr1
NOV
  POSTAVIM spr2
BRIŠEM spr2
… in tako dalje …
NOV
  POSTAVIM spr20
BRIŠEM spr20

To je torej lep dokaz, da se objekt ob zaključku funkcije avtomatično uniči in nam za to ni potrebno skrbeti. Iz rezultata je namreč vidno, da se funkcija izvede 20x in vsakič kliče proceduro, ki objekt ustvari in ga tudi uniči. In to preprosto dejstvo nam lahko reši neprespane noči ob iskanju hrošča 🙂

Konkreten primer uporabe vam bom prikazal naslednjič, za danes pa podajam samo še dve zelo pomembni opozorili.

Opozorilo1

V kolikor ste navajeni klasičnih programskih jezikov, kot sta recimo C in C++ lahko pridete na idejo, da vso pomembno programsko kodo postavite v konstruktor in ustrezno čiščenje v destruktor…

TODA, če v VBA-ju ne uporabite vsaj ene javne funkcije/ metede/ lastnosti objekta, ga VBA izvajalno okolje ne bo naredilo. Torej VBA takšnega objekta sploh nikoli ne bo ustvaril.

Če prejšnjo funkcijo preoblikujete takole:

Sub Proc2(ime As String)
  Dim a As New objSciti
' objekta nikoli ne uporabimo
' zato ga VBA sploh ne bo ustvaril!
End Sub

Program ne bo izpisal ničesar!

Opozorilo2

V kolikor ste navajeni klasičnih programskih jezikov, kot sta recimo C in C++ lahko pričakujete, da bo VBA uničil objekt ob koncu programskega bloka. A to v primeeru VBA-ja ne drži. VBA uničuje objekte samo na koncu funkcij /procedur.

Primer je lepo viden, če v testni proceduri ne kličemo procedure, temveč objekt direktno ustvarimo:

Sub TestProc3()
  Dim i
  For i = 1 To 20
    ' VBA bo objekt ustvaril samo 1x !
    Dim a As New objSciti

    ' lastnosti bo spreminjal ISTEMU objektu
    a.init "spr"; i
  Next  ' objekt a ne bo uničen tukaj

  ' temveč bo uničen šele tukaj
End Sub

Sedaj pa je izpis povsem drugačen:
NOV
  POSTAVIM spr1
  POSTAVIM spr2
… in tako dalje …
  POSTAVIM spr20
BRIŠEM spr20

Na primeru je lepo vidno, da VBA ustvari en sam objekt. Bodite pozorni na to!

Ščitenje resursov v VBA

Kot klasični C/C++ programer sem obseden od ščitenja resursov!

Da najprej razčistimo, kaj resursi sploh so. V računalništvu so resursi pravzaprav vse kar računalnik uporablja, kot programerja pa nas najbolj zanimajo pomnilnik, datoteke in zunanje naprave.

Pri ščitenju resursov je ideja v tem, da vedno zagotavljate dvoje:

  1. Če resurs zasežete ga tudi sprostite.
  2. Vse resurse sproščajte čim prej. Če jih ne potrebujete več jih sprostite!

Primer je recimo odpiranje datoteke. Program odpre datoteko, piše vanjo in jo na koncu zapre… Toda, kaj če jo pozabi zapreti? Kaj če se v programski kodi zgodi napaka in se datoteka ne zapre? To je težava in nanjo je potrebno biti pozoren.

V Excelu pa se večkrat srečamo s povsem enakovrednim problemom preračunavanja. Da bi makro v Excelu tekel hitreje v večini primerov na začetku postavimo preračunavanje na ročno – izvedemo makro in postavimo preračunavanje nazaj na avtomatično

A kaj če pozabimo oz. se zgodi napaka 🙁 ? Uporabnik ostane z delovnim zvezkom, ki se ročno preračunava in seveda je zmeden…!

Sub Delujoce()
  Dim oldCalculation
  oldCalculation = Application.Calculation
  Application.Calculation = xlCalculationManual

  ' tu vmes je veliko vrstic kode…
  Dim i As Long
  For i = 1 To 10000
    Debug.Print i
  Next

  Application.Calculation = oldCalculation
End Sub

Toda čez nekaj časa kodo popravljamo in zapišemo nekaj takšnega…?
Sub NEDelujoce()
  Dim oldCalculation
  oldCalculation = Application.Calculation
  Application.Calculation = xlCalculationManual

  ' tu vmes je veliko vrstic kode…
  Dim i As Long
  For i = 1 To 10000
    Debug.Print i

    ' PROBLEM!!!
    If (i Mod 123 = 12) Then Exit Sub
  Next

  Application.Calculation = oldCalculation
End Sub

Sedaj se bo v nekem primeru funkcija zaključila prej in Excel ostane v ročnem preračunu! Napaka!

Rešitev

Rešitev ponuja objektno programiranje oz ideja konstruktorjev ~ destruktorjev. Konstruktor je funkcija, ki se izvede ob ustvarjanju objekta, destruktor pa se zgodi ob uničenju objekta. Lepota obeh funkcij je v tem, da sta privzeti in ju ni potrebno eksplicitno klicati.

Dobro; konstruktor pokličemo vedno ko objekt ustvarimo – to kot programerji zagotovo naredimo. A objekt se ob koncu ob koncu funkcije uniči avtomatično. No natančneje rečeno,  se objekt uniči ob koncu njegove življenske dobe, ki je odvisna od tega,kje ga ustvarimo… A to zdajle ni tako pomembno.

In to je ključ do uspeha. V konstruktorju zasežemo resurse, ob destruktorju pa se slednji avtomatično sprostijo in vse je lepo in prav.

Kako to izvesti v VBA-ju pa pokažem naslednjič.