JavascriptProva

lunedì 30 dicembre 2013

Mescolamento e ordinamento di un ArrayList per ordinare con disposizione casuale dei valori pari

L'ordine dovrebbe essere:
  • mescola l'array di partenza
  • ordina l'array di partenza
così facendo, quelli che hanno pari preferenza vengono messi in ordine casuale.
Creo una funzione per stampare sulla finestra immediata gli array.
    Sub mostra(ByVal arr As ArrayList)
        For n = 0 To arr.Count - 1
            Debug.Print(arr(n).nome & " " & arr(n).turni)
        Next
        Debug.WriteLine("")
    End Sub
Ecco, quindi adesso provo con le mie due procedure di rimescolamento e ordinamento.
         'Rimescolamento casuale della matrice di partenza
        Dim r As New Random
        Dim x As Integer
        Dim temp As persona
        For n = 0 To matrice.Count - 1
            x = r.Next(matrice.Count - n) + n
            temp = matrice(n)
            matrice(n) = matrice(x)
            matrice(x) = temp
        Next


        'ordinamento della matrice
        matrice.Sort()

        mostra(matrice)

Avendo tutti e tre gli elementi la stessa preferenza, di 50, il risultato dovrebbe essere assolutamente casuale.
Mario 0
Giuseppe 0
Luigi 0

Luigi 0
Mario 0
Giuseppe 0

Giuseppe 0
Luigi 0
Mario 0

Giuseppe 0
Mario 0
Luigi 0

Mario 0
Giuseppe 0
Luigi 0

Mario 0
Luigi 0
Giuseppe 0

Giuseppe 0
Mario 0
Luigi 0

Mario 0
Giuseppe 0
Luigi 0

Mario 0
Giuseppe 0
Luigi 0

Luigi 0
Mario 0
Giuseppe 0

Mario 0
Luigi 0
Giuseppe 0

Giuseppe 0
Mario 0
Luigi 0

Giuseppe 0
Mario 0
Luigi 0

Mario 0
Giuseppe 0
Luigi 0

Luigi 0
Mario 0
Giuseppe 0

Luigi 0
Mario 0
Giuseppe 0

Giuseppe 0
Luigi 0
Mario 0

Luigi 0
Mario 0
Giuseppe 0

Mario 0
Giuseppe 0
Luigi 0

Giuseppe 0
Mario 0
Luigi 0

Giuseppe 0
Mario 0
Luigi 0

Mario 0
Giuseppe 0
Luigi 0

Mario 0
Giuseppe 0
Luigi 0

Mario 0
Giuseppe 0
Luigi 0

Giuseppe 0
Luigi 0
Mario 0

Mario 0
Giuseppe 0
Luigi 0

Mario 0
Luigi 0
Giuseppe 0

Luigi 0
Giuseppe 0
Mario 0
credo che non ci sia nessuna sequenza preferenziale...

Adesso impostiamo una differenza di preferenze, dando a Mario una preferenza di 20. Come risultato, la disposizione casuale dovrebbe applicarsi solo a Giuseppe e Luigi, mentre Mario dovrebbe venire sempre per primo...
Mario 0
Giuseppe 0
Luigi 0

Mario 0
Giuseppe 0
Luigi 0

Mario 0
Giuseppe 0
Luigi 0

Mario 0
Luigi 0
Giuseppe 0

Mario 0
Luigi 0
Giuseppe 0

Mario 0
Luigi 0
Giuseppe 0

Mario 0
Luigi 0
Giuseppe 0

Mario 0
Luigi 0
Giuseppe 0

Mario 0
Luigi 0
Giuseppe 0

Mario 0
Luigi 0
Giuseppe 0

Mario 0
Giuseppe 0
Luigi 0

Mario 0
Giuseppe 0
Luigi 0

Mario 0
Giuseppe 0
Luigi 0

Mario 0
Giuseppe 0
Luigi 0

Mario 0
Giuseppe 0
Luigi 0

Sembra funzionare...

Adesso diamo anche a Luigi una preferenza di 20, prevedendo che debba risultare un ordinamento casuale di Mario e Luigi, con Giuseppe sempre per ultimo...
Mario 0
Luigi 0
Giuseppe 0

Mario 0
Luigi 0
Giuseppe 0

Luigi 0
Mario 0
Giuseppe 0

Luigi 0
Mario 0
Giuseppe 0

Luigi 0
Mario 0
Giuseppe 0

Mario 0
Luigi 0
Giuseppe 0

Luigi 0
Mario 0
Giuseppe 0

Luigi 0
Mario 0
Giuseppe 0

Luigi 0
Mario 0
Giuseppe 0

Luigi 0
Mario 0
Giuseppe 0

Mario 0
Luigi 0
Giuseppe 0
Sì... fin qui sembra che ci siamo...

lunedì 23 dicembre 2013

Riepilogo in preparazione di un algoritmo di distribuzione casuale dei turni restanti.

Ho trovato un algoritmo per la distribuzione casuale di un ArrayList.
Ne ho capito e imparato il concetto.

L'idea è di usare un array su cui lavorare, in cui immettere gli elementi.

Ho un array chiamato matrice.
        mario.nome = "Mario"
        giuseppe.nome = "Giuseppe"
        luigi.nome = "Luigi"
        mario.preferenza = 80
        giuseppe.preferenza = 80
        luigi.preferenza = 20

        matrice = New ArrayList
        matrice.Add(mario)
        matrice.Add(giuseppe)
        matrice.Add(luigi)
Ora mi immetto gli elementi su un array che chiamo matrice2, e verifico:
matrice2 = New ArrayList
        For n = 0 To matrice.Count - 1
            matrice2.Add(matrice(n))
        Next

        For n = 0 To matrice2.Count - 1
            Debug.Print(matrice2(n).nome)
        Next
Mario
Giuseppe
Luigi





Bene.
Adesso faccio le operazioni su questa matrice:

Conto il totale delle singole preferenze, memorizzandolo nella variabile totale
        For n = 0 To matrice2.Count - 1
            totale += matrice2(n).preferenza
        Next
Eseguo il calcolo che attribuisce i turni a seconda della preferenza:
        For n = 0 To matrice2.Count - 1
            matrice2(n).turni = Math.Floor(numTurni / totale * matrice2(n).preferenza)
        Next
e verifico, nella matrice iniziale matrice l'attribuzione dei turni, contando il totale dei turni attribuiti;
Quindi stampo i turni attribuiti e il resto da attribuire:
        For n = 0 To matrice.Count - 1
            Debug.Print(matrice(n).nome & " " & matrice(n).turni)
            attribuiti += matrice(n).turni
        Next

        resto = numTurni - attribuiti

        Debug.Print("attribuiti " & attribuiti)
        Debug.Print("resto " & resto)
Ecco:
Mario 44
Giuseppe 44
Luigi 11
attribuiti 99
resto 1


Fin qua nessuna novità: ora, però, provo a cancellare matrice2. Ecco tutto il codice con il codice che cancella la matrice2 in rosso. Quella matrice mi serve solo per lavorarci.
mario.nome = "Mario"
        giuseppe.nome = "Giuseppe"
        luigi.nome = "Luigi"
        mario.preferenza = 80
        giuseppe.preferenza = 80
        luigi.preferenza = 20

        matrice = New ArrayList
        matrice.Add(mario)
        matrice.Add(giuseppe)
        matrice.Add(luigi)


        matrice2 = New ArrayList
        For n = 0 To matrice.Count - 1
            matrice2.Add(matrice(n))
        Next

        For n = 0 To matrice2.Count - 1
            totale += matrice2(n).preferenza
        Next

        For n = 0 To matrice2.Count - 1

            matrice2(n).turni = Math.Floor(numTurni / totale * matrice2(n).preferenza)
        Next
        matrice2.Clear()
        For n = 0 To matrice.Count - 1
            Debug.Print(matrice(n).nome & " " & matrice(n).turni)
            attribuiti += matrice(n).turni
        Next

        resto = numTurni - attribuiti

        Debug.Print("attribuiti " & attribuiti)
        Debug.Print("resto " & resto)

Ora posso scegliere se distribuire i turni restanti solo a quelli che hanno il massimo delle preferenze o se escludere quelli che vogliono il minimo.
Questa soluzione mi sembra la più giusta.
Dunque devo costruire una funzione che elimini quelli che vogliono il minimo, progressivamente.

domenica 22 dicembre 2013

Una distribuzione migliore

Con quel codice che ho fatto ultimamente, però, l'ultimo turno viene sempre ridistribuito a quello che ha espresso la più alta preferenza che si trova per ultimo.
Se ad esprimere la preferenza più alta sono più di uno, il progressivo restringersi dell'array dal basso elimina persone che hanno la stessa preferenza dell'ultimo.
Luigi 50 33
Giuseppe 50 33
Mario 50 33
resto 1
-----
Luigi 50 33
Giuseppe 50 33
Mario 50 33
resto 1
-----
Luigi 50 33
Giuseppe 50 33
Mario 50 34
resto 0
-----
Questo non va bene.

Dovrei assicurare la casualità, e quindi trovare un altro algoritmo...

Si potrebbe procedere in modo da:
  • Isolare quelli che esprimono il grado più alto di preferenza
  • Porli in un altro array
  • Rimescolarli in modo casuale
  • Procedere con l'attribuzione per riduzione progressiva dell'array come fatto prima.

Intanto, verifichiamo se le operazioni svolte su un altro array si ripercuotono sugli oggetti elencati nel primo array. Sono convinto che debba essere necessariamente così...
Però verifichiamo.
Dispongo gli oggetti su un array:
        mario.nome = "Mario"
        giuseppe.nome = "Giuseppe"
        luigi.nome = "Luigi"
        mario.preferenza = 50
        giuseppe.preferenza = 50
        luigi.preferenza = 50

        matrice = New ArrayList
        matrice.Add(mario)
        matrice.Add(giuseppe)
        matrice.Add(luigi)
Ora preparo un altro ArrayList e vi trasferisco alcuni elementi. Verifico prima il valore sulla finestra immediata.
        Debug.Print(matrice(0).preferenza)
        Dim matrice2 As New ArrayList
        matrice2.Add(matrice(0))
Ora modifico un valore dell'elemento e verifico se anche l'elemento del primo ArrayList si è modificato.
        matrice2(0).preferenza = 1234

        Debug.Print(matrice(0).preferenza)
50
1234



Ecco, come volevasi dimostrare. Era ovvio, del resto!

E dunque, determiniamo il massimo valore presente in matrice.
        mario.preferenza = 50
        giuseppe.preferenza = 50
        luigi.preferenza = 50

.......

        Dim base As Integer = 0
        For Each elemento In matrice
            If elemento.preferenza > base Then base = elemento.preferenza
        Next

        Debug.Print(base)
50



Vario un po' i valori...
        mario.preferenza = 20
        giuseppe.preferenza = 80
        luigi.preferenza = 50

........

        Dim base As Integer = 0
        For Each elemento In matrice
            If elemento.preferenza > base Then base = elemento.preferenza
        Next
80




Sì, sì, funziona...

Avevo già elaborato una funzioncina che svolgeva questo compitino, l'ho messa in un modulo:
Module funzioni
    Public Function Max(ByVal m As ArrayList) As Integer
        Dim base As Integer = 0
        For Each elemento In m
            If elemento > base Then base = elemento
        Next
        Return base
    End Function
End Module
Vediamo se funziona in questo contesto...
        mario.preferenza = 20
        giuseppe.preferenza = 80
        luigi.preferenza = 50

        matrice = New ArrayList
        matrice.Add(mario)
        matrice.Add(giuseppe)
        matrice.Add(luigi)

        Debug.Print(Max(matrice))
Eccezione first-chance di tipo 'System.InvalidCastException' in Microsoft.VisualBasic.dll



AHIAHIAHI!!! Invalid cast!!! Certo! Elemento è l'oggetto, non la proprietà che mi interessa!!!

Vediamo...

Module funzioni
    Public Function Max(ByVal m As ArrayList) As Integer
        Dim base As Integer = 0
        For Each elemento In m
            If elemento.preferenza > base Then base = elemento.preferenza
        Next
        Return base
    End Function

End Module
Non è il massimo, ma funziona...

80




Bene.
Ora devo isolare in un altro array gli elementi che portano questo valore massimo.
        matrice2 = New ArrayList
        For Each elemento In matrice
            If elemento.preferenza = Max(matrice) Then matrice2.Add(elemento)
        Next

        For Each elemento In matrice2
            Debug.Print(elemento.nome & " " & elemento.preferenza)
        Next
...con verifica, ovviamente. Ho impostato così i valori di preferenza delle persone:
        mario.preferenza = 120
        giuseppe.preferenza = 80
        luigi.preferenza = 120
Vediamo...
Mario 120
Luigi 120


Perfetto!

Cambiamo i valori e vediamo ancora...
        mario.preferenza = 80
        giuseppe.preferenza = 80
        luigi.preferenza = 20
Mario 80
Giuseppe 80
Benissimo!

Ho ottenuto di isolare in un altro ArrayList solo quelli che hanno una preferenza maggiore, onde attribuire i turni che avanzano come resto nella prima distribuzione.
Mi sembra giusto, infatti, attribuire più turni a chi preferisce farne di più. Ma in modo casuale qualora questa distribuzione debba essere necessariamente disuguale.

Codice per la ridistribuzione di turni in base a una preferenza

Codice per la ridistribuzione di turni in base a una preferenza da 0 a 100.
Class persona
    Implements IComparable
    Public nome As String
    Public preferenza As Integer
    Public turni As Integer

    Public Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
        Dim altro As persona = DirectCast(obj, persona)
        Return Me.preferenza.CompareTo(altro.preferenza)
    End Function
End Class
Public Class Form1
    Dim WithEvents miaGriglia As Griglia(Of jLabel)
    Dim risultato As Double
    Dim indice As Integer
    Dim mario As New persona
    Dim giuseppe As New persona
    Dim luigi As New persona
    Dim numTurni As Integer = 100
    Dim totale As Integer
    Dim attribuiti As Integer = 0
    Dim matrice As ArrayList
    Dim resto As Integer
    Dim aggiunta As Integer
    Private Sub Form1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Click
        mario.nome = "Mario"
        giuseppe.nome = "Giuseppe"
        luigi.nome = "Luigi"
        mario.preferenza = 50
        giuseppe.preferenza = 50
        luigi.preferenza = 50

        matrice = New ArrayList
        matrice.Add(mario)
        matrice.Add(giuseppe)
        matrice.Add(luigi)

        'conto il totale delle preferenze
        matrice.Sort()
        calcolo()
       
        
    End Sub
    Sub calcolo()

        If indice = matrice.Count Then Return
        For n = indice To matrice.Count - 1
            totale += matrice(n).preferenza
        Next

        'attribuisco i turni


        For n = indice To matrice.Count - 1
            aggiunta = Math.Floor(numTurni / totale * matrice(n).preferenza)
            matrice(n).turni += aggiunta
            attribuiti += aggiunta
        Next



        For n = 0 To matrice.Count - 1
            Debug.Print(matrice(n).nome & " " & matrice(n).preferenza & " " & matrice(n).turni)
        Next
        resto = numTurni - attribuiti
        Debug.Print("resto " & resto)
        totale = 0
        attribuiti = 0
        numTurni = resto
        Debug.Print("-----")
        indice += 1
        calcolo()

    End Sub
End Class

giovedì 12 dicembre 2013

Operazioni sulle stringhe Substring e Mid.

Dividere una stringa.
Il metodo Substring estrae un pezzo di stringa a partire da un indirizzo e per una certa lunghezza.
Esploriamo questo metodo:
Public Class Form1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim stringa As String = "Questa è una stringa"
        Debug.Print(stringa.Substring(0, 3))
    End Sub
End Class
Que


Quindi il metodo Substring estrae una stringa che parte dall'indirizzo specificato e ha la lunghezza specificata.

La funzione Mid, invece:
Public Class Form1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim stringa As String = "Questa è una stringa"
        Debug.Print(Mid(stringa, 0, 3))
    End Sub
End Class
Eccezione first-chance di tipo 'System.ArgumentException' in Microsoft.VisualBasic.dll
Con questa funzione, la lettura avviene dal carattere successivo a quello che ha per indice il numero specificato (non parte da 0 ma da 1).

Public Class Form1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim stringa As String = "Questa è una stringa"
        Debug.Print(Mid(stringa, 1, 3))
    End Sub
End Class
Que



mercoledì 11 dicembre 2013

Aggiunta di separator ai menu

Devo aggiungere un sottomenu bianco...

Fatto: basta aggiungere un sottomenu senza specificare testo, o anche specificando una stringa vuota...
Come si aggiunge una linea sul menu? Mi sembra bello separare i sottomenu "pieni" da quello vuoto...
Dim separatore As New ToolStripSeparator
            mioMenu.Items.Add(separatore)
            sottomenu = New ToolStripMenuItem()
            sottomenu.Text = ""
            AddHandler sottomenu.Click, AddressOf Trascrivi
            mioMenu.Items.Add(sottomenu)
Aggiunti separatore e menu vuoto, e aggiustato il codice in modo che in caso di click sul menu vuoto non si faccia alcun calcolo...
    Sub CALCOLO()
        If mioMenu.Items(SelectedMenu).Text <> "" Then nomiGriglia.CelleDellaColonna(0)(SelectedMenu).BackColor = Color.Red
    End Sub
in effetti, un calcolo si deve fare, ma andiamo per gradi...

Alla ricerca di un metodo di calcolo più rapido

Bene.
Ora devo modificare questo mammuth e velocizzarlo.

Ho in mano, salvato come una variabile, il sottomenu selezionato al momento del click per introdurre nella casella il nome.
Che ci faccio?

Il numero di indice del sottomenu corrisponde all'indice del nome nella matrice dei nomi, e corrisponderà all'indice della casella nella matrice delle caselle appartenenti a una determinata colonna.
Facendo valere questa corrispondenza, si può fare un calcolo limitato all'oggetto di interesse senza stare ogni volta a sfogliare tutto l'array di caselle perdendo moltissimo tempo.

Ho bisogno di lavorare un po' su un menu isolato per vedere se si può ricavare un indice di un sottomenu.
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim sottomenu As New ToolStripMenuItem
        sottomenu.Text = "STRONZO"
        MenuStrip1.Items.Add(sottomenu)
        MenuStrip1.Items(0).BackColor = Color.Cyan
    End Sub
Ecco, dunque devo salvare come variabile non il sottomenu come variabile oggetto ma il suo indice come variabile numerica (tipo Integer o altro).

Cambio il tipo di variabile...
    Dim SelectedMenu As Integer 'indice del menu selezionato


Adesso provvediamo a salvare l'indice del sottomenu, e non più il sottomenu, in questa variabile.
    Sub Trascrivi(ByVal sender As Object, ByVal e As EventArgs)

        SelectedItem.Text = sender.text
        SelectedMenu = mioMenu.Items.IndexOf(sender)
        MsgBox(SelectedMenu)
        CALCOLO()
Bene.
Abbiamo memorizzato l'indice del sottomenu.
Adesso usiamolo. Facciamo una prova...
        nomiGriglia.CelleDellaColonna(0)(SelectedMenu).BackColor = Color.Red
inserita nella routine "Calcolo()".
E funziona!!!

La casella si colora di rosso immediatamente. Prima, con quel metodo rudimentale del dover sfogliare tutte le caselle, passava più di mezzo secondo.

sabato 7 dicembre 2013

Alcune funzioni per navigare nella mia griglia di celle.

Ecco, nell'ambito della mia griglia di celle, le funzioni che ho creato:

Ecco alcune funzioni che , fornendo come parametro una cella, fanno vari calcoli.
Funzioni che, data la cella, identificano la colonna e la riga della cella:
    'identifica la colonna della casella
    Public Function colonna(ByVal sender As Object) As Integer
        Return IndexOf(sender) Mod casxFila
    End Function
    'identifica la riga della casella
    Function fila(ByVal sender As Object) As Integer
        Return IndexOf(sender) \ casxFila
    End Function
La prima è piuttosto facile: il resto della divisione dell'indice di una casella per il numero di caselle per fila dà la colonna.
La seconda è anch'essa facile: la divisione intera dell'indice per il numero di caselle per riga dà la riga.
Funzioni che, data la cella, identificano le celle precedente e successiva:
       'identifica la casella precedente nell'array
    Function precedente(ByVal sender As Object) As Label
        Return Me(IndexOf(sender) - 1)
    End Function
    ''identifica la casella successiva nell'array
    Function successiva(ByVal sender As Object) As Label
        Return Me(IndexOf(sender) + 1)
    End Function
Queste partono dall'indice della cella nell'array, diminuendolo o aumentandolo di uno.
Funzioni che, data la cella, identificano la cella corrispondente nella fila precedente e nella successiva:
    'data la casella, identifica la casella corrispondente del giorno prima
    Function PrevParall(ByVal sender As Object) As Label
        Return Me(IndexOf(sender) - casxFila)
    End Function
    'data la casella, identifica la casella corrispondente del giorno dopo
    Function NextParall(ByVal sender As Object) As Label
        Return Me(IndexOf(sender) + casxFila)
    End Function
Queste desumono l'indice della casella cliccata e lo diminuiscono o lo aumentano di un numero pari al numero delle celle per riga.

venerdì 6 dicembre 2013

martedì 3 dicembre 2013

Ripasso di ParamArray (array passata come parametro)

Esploriamo ParamArray...

Public Class Form1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        funzione("ciao", "buongiorno", "scemo", "cretino")
 
    End Sub

    Sub funzione(ByVal ParamArray parametri() As String)
        For n = 0 To parametri.Length - 1
            MsgBox(parametri(n))
        Next

    End Sub

End Class

giovedì 28 novembre 2013

Funzione che individua una casella "compagna"

Devo inventare una funzione che, se la mia label ha il tag "mattina" e se nell'altra il tag sia uguale e abbia un testo corrispondente a un menu, questo menu non venga caricato nel menu popup.

Se una casella con lo stesso numero di riga e con lo stesso tag ha un certo testo non caricare il menu con questo testo.
Una funzione dovrebbe individuare la casella "compagna" sulla base del criterio che è o precedente o seguente e ha lo stesso tag.

Proviamo a buttare giù qualcosa del genere...

    Function compagna(ByVal sender As Object) As Label
        If precedente(sender).Tag = sender.tag Then
            Return precedente(sender)
        ElseIf successiva(sender).Tag = sender.tag Then
            Return successiva(sender)
        Else
            Return Nothing
        End If
    End Function
Ecco.
Ovviamente, la funzione può restituire un Nothing, per cui può essere sollevata un'eccezione: per questo motivo va gestita l'eccezione con un codice Try... Catch.
    Sub ClassEventHandler(ByVal sender As Object, ByVal e As MouseEventArgs) Handles miaGriglia.Evento
        Try
            compagna(sender).BackColor = Color.Green
        Catch ex As Exception

        End Try

    End Sub
E, premesso questo, funziona.

Ricostruzione codice perso inavvertitamente: menu popup al calendario.

Menu popup al calendario

domenica 24 novembre 2013

Evento nei vari elementi di un ArrayList

Ecco il codice per gestire un evento in tutti gli elementi di un ArrayList:
Public Class Form1
    Dim matrice As ArrayList
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        matrice = New ArrayList
        matrice.Add(TextBox1)
        matrice.Add(TextBox2)
        matrice.Add(TextBox3)
        For Each elemento As TextBox In matrice
            AddHandler elemento.Click, AddressOf evento
        Next
    End Sub
    Sub evento(ByVal sender As Object, ByVal e As EventArgs)
        MsgBox("Ciao")
    End Sub
End Class

Calendario/griglia riposizionabile.



Ho anche aggiunto del codice per evidenziare i sabati e le domeniche:
        For n = 0 To mioCalendario.Count - 1
            If Weekday(mioCalendario(n).text) = 1 Then mioCalendario(n).forecolor = Color.Red
            If Weekday(mioCalendario(n).text) = 7 Then mioCalendario(n).forecolor = Color.Blue
        Next
Questo è possibile, ovviamente, in virtù del fatto che la classe Calendario eredita da ArrayList, discorso che avevo già fatto prima.

Classe Calendario erede di ArrayList (per interagire con le caselle)

Classe Calendario (un calendario flessibile)

Creazione di un calendario

Classe label personalizzata per mezzo dell'ereditarietà

Griglia come erede di ArrayList per interagire con i suoi elementi

Orientamento orizzontale o verticale di una griglia di labels

domenica 17 novembre 2013

Costruzione di un quadrato con due triangoli.

Bene.
Ora cerchiamo di disegnare un quadrato fatto di due triangoli.

Pagina di riferimento per il costruttore di VertexBuffer

Ecco il codice:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        NumeroVertici = 4
        NumeroIndici = 4

        InitializeGraphics()
        VBuffer = New VertexBuffer(GetType(CustomVertex.PositionColored), NumeroVertici, dispositivo, 0, CustomVertex.PositionColored.Format, Pool.Default)
        Dim vertici As CustomVertex.PositionColored() = New CustomVertex.PositionColored(NumeroVertici) {}
        vertici(0).Position = New Vector3(0, 0, 0)
        vertici(0).Color = Color.Blue.ToArgb
        vertici(1).Position = New Vector3(5, 0, 0)
        vertici(1).Color = Color.Red.ToArgb
        vertici(2).Position = New Vector3(5, 5, 0)
        vertici(2).Color = Color.Yellow.ToArgb
        vertici(3).Position = New Vector3(0, 5, 0)
        vertici(3).Color = Color.Green.ToArgb
       

        VBuffer.SetData(vertici, 0, LockFlags.None)




        ib = New IndexBuffer(GetType(Integer), NumeroIndici, dispositivo, 0, Pool.Default)
        Dim indici As Integer() = New Integer(NumeroIndici) {}
        indici(0) = 1
        indici(1) = 2
        indici(2) = 0
        indici(3) = 3
        'indici(4) = 2
        'indici(5) = 0
        ib.SetData(indici, 0, LockFlags.None)

...
Prima si costruisce il VertexBuffer:
        VBuffer = New VertexBuffer(GetType(CustomVertex.PositionColored), NumeroVertici, dispositivo, 0, CustomVertex.PositionColored.Format, Pool.Default)
        Dim vertici As CustomVertex.PositionColored() = New CustomVertex.PositionColored(NumeroVertici) {}
        vertici(0).Position = New Vector3(0, 0, 0)
        vertici(0).Color = Color.Blue.ToArgb
        vertici(1).Position = New Vector3(5, 0, 0)
        vertici(1).Color = Color.Red.ToArgb
        vertici(2).Position = New Vector3(5, 5, 0)
        vertici(2).Color = Color.Yellow.ToArgb
        vertici(3).Position = New Vector3(0, 5, 0)
        vertici(3).Color = Color.Green.ToArgb
       

        VBuffer.SetData(vertici, 0, LockFlags.None)
Quindi l'IndexBuffer:
        ib = New IndexBuffer(GetType(Integer), NumeroIndici, dispositivo, 0, Pool.Default)
        Dim indici As Integer() = New Integer(NumeroIndici) {}
        indici(0) = 1
        indici(1) = 2
        indici(2) = 0
        indici(3) = 3
        ib.SetData(indici, 0, LockFlags.None)
Ecco il codice che "stampa":
        dispositivo.RenderState.Lighting = False
        dispositivo.RenderState.CullMode = Cull.None
        dispositivo.RenderState.FillMode = FillMode.WireFrame
        dispositivo.Clear(ClearFlags.Target, Color.Black, 1, 0)
        dispositivo.BeginScene()
        dispositivo.VertexFormat = CustomVertex.PositionColored.Format
        dispositivo.SetStreamSource(0, VBuffer, 0)
        dispositivo.Indices = ib

        dispositivo.DrawIndexedPrimitives(PrimitiveType.TriangleStrip, 0, 0, 4, 0, 2)
        dispositivo.EndScene()
        dispositivo.Present()


Che cosa ottengo con tutto questo?



Ho problemi con l'ordine dei vertici.
Cerchiamo innanzitutto di stabilire il punto di vista mediante la View Matrix:
        dispositivo.Transform.View = Matrix.LookAtLH(New Vector3(0, 0, 15), New Vector3(0, 0, 0), New Vector3(0, 1, 0))
Ecco: creo due triangoli in una TriangleStrip.
Creo il VertexBuffer:
VBuffer = New VertexBuffer(GetType(CustomVertex.PositionColored), NumeroVertici, dispositivo, 0, CustomVertex.PositionColored.Format, Pool.Default)
        Dim vertici As CustomVertex.PositionColored() = New CustomVertex.PositionColored(NumeroVertici) {}
        vertici(0).Position = New Vector3(0, 0, 0)
        vertici(0).Color = Color.Blue.ToArgb
        vertici(1).Position = New Vector3(5, 0, 0)
        vertici(1).Color = Color.Yellow.ToArgb
        vertici(2).Position = New Vector3(5, 5, 0)
        vertici(2).Color = Color.Red.ToArgb
        vertici(3).Position = New Vector3(0, 5, 0)
        vertici(3).Color = Color.Green.ToArgb


Creo l'IndexBuffer:
        ib = New IndexBuffer(GetType(Integer), NumeroIndici, dispositivo, 0, Pool.Default)
        Dim indici As Integer() = New Integer(NumeroIndici) {}
        indici(0) = 0
        indici(1) = 1
        indici(2) = 2
        indici(3) = 3
        ib.SetData(indici, 0, LockFlags.None)
Ecco il risultato:



Qui il triangolo è stato tracciato nell'ordine dei vertici rosso, giallo e blu. L'ultimo ad essere stato tracciato è il cateto verticale, e il nuovo triangolo è stato tracciato avendo in comune con esso questo cateto.
Adesso tracciamo per ultimo il cateto orizzontale:
ib = New IndexBuffer(GetType(Integer), NumeroIndici, dispositivo, 0, Pool.Default)
        Dim indici As Integer() = New Integer(NumeroIndici) {}
        indici(0) = 2
        indici(1) = 0
        indici(2) = 1
        indici(3) = 3




E ora tracciamo per ultima l'ipotenusa:
        ib = New IndexBuffer(GetType(Integer), NumeroIndici, dispositivo, 0, Pool.Default)
        Dim indici As Integer() = New Integer(NumeroIndici) {}
        indici(0) = 1
        indici(1) = 2
        indici(2) = 0
        indici(3) = 3


sabato 16 novembre 2013

Annotazioni informali sulla creazione di VertexBuffer e IndexBuffer.

Bene. Dunque la creazione di un buffer sarebbe motivata dal dover archiviare una grande quantità di vertici.
C'è una differenza nel metodo che disegna le "primitive".
device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, vertices)
e
 device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 5, 0, 2);
Si tratta di due metodi differenti.
Vediamo i parametri assunti dal primo.
device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, vertices)
 device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 5, 0, 2);
Mentre il primo metodo, DrawUserPrimitives, prende come argomenti il tipo di primitiva, il numero di elementi da disegnare e i vertici,
il secondo, DrawIndexedPrimitives, prende come argomenti il tipo di primitiva, quattro numeri che devo ancora studiare e, da ultimo, il numero di elementi da disegnare.

Mancano i vertici.
Dove sono?

Ecco: ci sono due membri dell'oggetto Device:
 device.SetStreamSource(0, vb, 0);
 device.Indices = ib;
laddove vb sta per VertexBuffer, e ib sta per IndexBuffer

Quindi, prima, ci sono due oggetti, un VertexBuffer e un IndexBuffer, che vengono costruiti.

Mi sembra chiaro che DrawIndexedPrimitives prenda i suoi vertici dal membro device.Indices.


Da quello che ho capito il VertexBuffer contiene tutti i punti che vanno a fare da vertici, senza ripeterli quando essi siano uguali.
Costruiamo questo VertexBuffer...

        Dim VBuffer As New VertexBuffer(GetType(CustomVertex.PositionColored),3,dispositivo,0,CustomVertex.PositionColored.Format,pool.Default)
Non mi sono chiari tutti i parametri e il loro perchè...

Costruiamo i vertici...
Prima l'array;
Dim vertici As CustomVertex.PositionColored() = New CustomVertex.PositionColored(3) {}
Quindi tutti i vertici, in questo caso tre:
        vertici(0).Position = New Vector3(0, 0, 0)
        vertici(0).Color = Color.Blue.ToArgb
        vertici(1).Position = New Vector3(10, 0, 0)
        vertici(1).Color = Color.Red.ToArgb
        vertici(2).Position = New Vector3(0, 10, 0)
        vertici(2).Color = Color.Yellow.ToArgb
Dunque, creata la matrice di vertici, ossia CustomVertex.PositionColored, e creati i vertici, bisogna "associarli" in qualche modo a VertexBuffer.
Ed ecco che trovo questa istruzione:
VBuffer.SetData(vertici, 0, LockFlags.None)
In sintesi:
  • Creazione di VertexBuffer
  • Creazione di un array di vertici
  • "associazione" dell'array di vertici al VertexBuffer.

Adesso si deve creare un IndexBuffer

Ecco, alla fine ci sono riuscito.
 ib = New IndexBuffer(GetType(Integer), 3, dispositivo, 0, Pool.Default)
        Dim indici As Integer() = New Integer(3) {}
        indici(0) = 0
        indici(1) = 1
        indici(2) = 2
        ib.SetData(indici, 0, LockFlags.None)


Adesso il mio programma non fa assolutamente niente, ma almeno non dà messaggi di errore.
E' già qualcosa...

venerdì 15 novembre 2013

Appunti informali su VertexBuffer

Praticamente InitializeGraphics non fa altro che settare i parametri e creare il device:
    Private Sub InitializeGraphics()
        Dim parametri As New PresentParameters
        parametri.SwapEffect = SwapEffect.Discard
        parametri.Windowed = True
        dispositivo = New Device(0, DeviceType.Hardware, Me, CreateFlags.SoftwareVertexProcessing, parametri)
    End Sub
E la inseriamo in Form_Load:
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        InitializeGraphics()
    End Sub


E fin qua ci siamo.

Ora creiamo la subroutine Render, ossia il suo scheletro:
    Private Sub Render()
        dispositivo.BeginScene()
        dispositivo.EndScene()
        dispositivo.Present()
    End Sub
o meglio:
    Private Sub Render()
        dispositivo.Clear(ClearFlags.Target, Color.Black, 1, 0)
        dispositivo.BeginScene()
        dispositivo.EndScene()
        dispositivo.Present()
    End Sub
Il metodo Clear come funziona?
Lasciamolo perdere, altrimenti qua ci disperdiamo... Lo vedrò dopo.

Adesso si costruisce un buffer.
La costruzione del buffer è delegata a una subroutine CreateVertexBuffer:
    Private Sub CreateVertexBuffer()
        Dim BufferVertici As New VertexBuffer(GetType(CustomVertex.PositionColored), 3, dispositivo, 0, CustomVertex.PositionColored.Format, Pool.Default)

    End Sub
Ma ora, che ci faccio con questo BufferVertici? Nell'altro tutorial non era contemplato...

mercoledì 6 novembre 2013

Primo abbozzo sulla comprensione della World Matrix

Dunque...
Io ho un triangolo con uno dei suoi vertici situato nel punto 0,0,0. Le coordinate dei vertici sono rispettivamente:
0, 0, 0

0.5, 0, 0

0, 1, 0
Viene ruotato di un certo angolo θ sul suo vertice posto in 0,0,0. Le nuove coordinate saranno, per i due vertici che subiscono lo spostamento:
0,0,0

0.5*cosθ, 0.5*sinθ, 0

-1*sinθ, 1*cosθ,   0
C'è qualcosa che non mi è chiaro: quale sia l'ordine dei vertici, e perchè nella matrice del tutorial che sto consultando il vertice che sta a zero non presenta tutte le coordinate a zero ma ne presenta una a 1.
Il principio l'ho capito, ma devo ancora ragionarci un po' su...

lunedì 4 novembre 2013

Cambio dei punti di vista lungo l'asse delle Z nella visualizzazione di un triangolo.

E se provassi a fare un viaggio sequenziale lungo l'asse delle Z, avvicinandomi da dietro al triangolo e quindi trapassandone il piano frontale e avvicinandomi ancora?

Fatto!
Punto di vista 0,0,8:
        device.Transform.View = Matrix.LookAtLH(New Vector3(0, 0, 3), New Vector3(0, 0, 0), New Vector3(0, 1, 0))



Punto di vista 0,0,3:
        device.Transform.View = Matrix.LookAtLH(New Vector3(0, 0, 8), New Vector3(0, 0, 0), New Vector3(0, 1, 0))



Punto di vista 0,0,-3
        device.Transform.View = Matrix.LookAtLH(New Vector3(0, 0, -3), New Vector3(0, 0, 0), New Vector3(0, 1, 0))


Ecco: ho "trapassato" il piano su cui si trova il triangolo e gli sono passato davanti, così adesso lo vedo "dall'altro lato".
Punto di vista 0,0,-8
        device.Transform.View = Matrix.LookAtLH(New Vector3(0, 0, -8), New Vector3(0, 0, 0), New Vector3(0, 1, 0))


Cominciamo a venire a capo di qualcosa di concreto su Direct3D

Forse sto venendo a capo.
Ragioniamo.
Ho un triangolo con i vertici di colore diverso:Blu, Verde e Rosso. Le coordinate dei vertici sono rispettivamente:
  • Blu: x=0,y=0,z=0
  • Verde: x=0.5, y=0, z=0
  • Rosso: x=0, y=0.5, z=0
Lasciamo la coordinata z invariata, ossia si trova perfettamente sul piano frontale.

Ora imposto il punto di vista, il punto puntato e l' "alto".
 device.Transform.View = Matrix.LookAtLH(New Vector3(0, 0, 2), New Vector3(0, 0, 0), New Vector3(0, 1, 0)) 
Il punto di vista dovrebbe essere di 2 unità "dietro" il triangolo.
Per questo il triangolo si vede così:

Questo perchè lo 0 dell'asse Z è "dietro".
Inverto la coordinata Z del punto di vista:
evice.Transform.View = Matrix.LookAtLH(New Vector3(0, 0, -2), New Vector3(0, 0, 0), New Vector3(0, 1, 0))
Ed ecco l'immagine:


E finalmente cominciamo a venirne a capo.

domenica 3 novembre 2013

Appunti informali su DirectX

Customvertex. Che è 'sta roba?

Contenitore di strutture.

Forse sarebbe il caso di creare delle strutture e vedere come una classe possa essere un contenitore di strutture.
Public Class Form1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim mia As Raccolta.Tipo
        mia = New Raccolta.Tipo
        mia.proprieta = "uno"
        mia.proprieta2 = "due"

        Dim mietta As Raccolta.Tipetto
        mietta = New Raccolta.Tipetto
        mietta.proprieta = "unetto"
        mietta.proprieta2 = "duetto"
    End Sub


End Class


Class Raccolta
    Public Structure Tipo
        Public proprieta As String
        Public proprieta2 As String
    End Structure

    Public Structure Tipetto
        Public proprieta As String
        Public proprieta2 As String
    End Structure
End Class
Ecco, qualcosa del genere.
Che macello!
Cerchiamo di analizzare la cosa per venirne a capo... Sto in crisi!!!

        Dim present As PresentParameters = New PresentParameters
        present.Windowed = True 'we?ll draw on a window
        present.SwapEffect = SwapEffect.Discard 'discuss later
        device = New Direct3D.Device(0, DeviceType.Hardware, Me, CreateFlags.SoftwareVertexProcessing, present)
Che cosa è PresentParameters?
Facciamo una ricerca.

Serve per inizializzare il device.
Come?
Ha un membro Windowed e uno SwapEffect: che significano?
Windowed è facile da capire: è booleano, e se è True significa che l'applicazione si esegue in una finestra.
SwapEffect è più complicato... Qui è settato a Discard.
Che accidenti significa?
Ah, ecco, può essere settato a questi valori:
  1. Discard
  2. Copy
  3. Flip
Ora il casino è vedere che significano questi valori.

Ha a che fare con il metodo Device.Present()
Che metodo è? E' presente qui?
Sì, è presente. Quindi PresentParameters ha a che fare con questo metodo Device.Present
Che fa il metodo Present di Device?
Eccolo copiato direttamente dalla guida di MSDN:
"Presents the display with the contents of the next buffer in the sequence of back buffers owned by the device."

Presenta il display con il contenuto del successivo buffer nella sequenza dei back buffers posseduti dal device.
Bene. Dovrei cercare di capire che significa swap chain
A swap chain is a collection of buffers that are used for displaying frames to the user. Each time an application presents a new frame for display, the first buffer in the swap chain takes the place of the displayed buffer. This process is called swapping or flipping. Quindi una swap chain è una collezione di buffers che sono usati per mostrare i frames all'utente.
Sarebbe la coda di buffers che "fanno la fila" dietro lo schermo per essere presentati.
Questo processo si chiama swapping o flipping.
Collegamento a MSDN.

Bene.
La frequenza di refresh dello schermo va da 60 a 100 Hz.
Se lo swapping ha una frequenza superiore alla frequenza di refresh dello schermo, dato che il disegno dello schermo va dall'alto a sinistra fino al basso a destra, se mentre l'immagine è in fase di disegno si ha lo swapping al buffer successivo, la parte superiore dello schermo contiene l'immagine vecchia mentre quella inferiore contiene l'immagine nuova. Questo effetto si chiama tearing.

domenica 27 ottobre 2013

Registrazione della finestra

La procedura principale è la WinMain.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    //Step 1: Registering the Window Class
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // Step 2: Creating the Window
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // Step 3: The Message Loop
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}
...durante la quale, per prima cosa si definisce la variabile wc di tipo WNDCLASSEX, che è una struttura.
Quindi si "registra" la wc.

.....

 WNDCLASSEX wc;

.....

    //Step 1: Registering the Window Class
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }


E questo è il primo passo.

Una finestra Win32

Bene.
Basta a pazzià!!!
Cerchiamo di andare su linguaggi più ponderosi.

Ecco una finestra con la sintassi del C++:
#include 

const char g_szClassName[] = "myWindowClass";

// Step 4: the Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    //Step 1: Registering the Window Class
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // Step 2: Creating the Window
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // Step 3: The Message Loop
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}
Bene, adesso vediamo come si rimaneggia tutto questo...

giovedì 10 ottobre 2013

Metodi di lettura di uno stream.

Non capisco.
Dunque, riepiloghiamo: uno stream può essere letto con metodi diversi:
  • Read, che ha bisogno di un buffer;
  • ReadByte, che legge un byte alla volta;
  • Per mezzo di un Reader, che è un oggetto a parte.
Sparo di aver capito... La cosa mi è un po' ostica!

Lettura in un buffer di un file wav mediante il metodo Read

Ecco, con questo codice leggo una parte di un file WAV. E ottengo la conferma che si tratta di un file WAV in quanto ne riconosco l'header:
Imports System.IO
Module Module1
    Dim corrente As New FileStream("c:\users\antonello\desktop\voce.wav", FileMode.Open)

    Dim destinazione(&H100) As Byte
    Sub Main()
        
        corrente.Read(destinazione, 0, destinazione.Length)

        For n = 0 To destinazione.Length - 1
            Console.Write(Chr(destinazione(n)))
        Next

        Console.ReadKey()
    End Sub

End Module
RIFFD☻☺ WAVEfmt ►   ☺ ☺ ◄+  ◄+  ☺ data ☻☺ ????⌂???????????????????⌂??????????⌂⌂⌂
????⌂???????????????????⌂??⌂???????⌂???⌂?⌂?????⌂??????⌂????????⌂⌂???????????⌂???
????????????⌂????⌂⌂???⌂?⌂???⌂??????????????????????????????????⌂???????????⌂???⌂
⌂????⌂??????⌂⌂?


Lettura di un file wav in un buffer mediante il metodo .Read

Ecco, con questo codice leggo una parte di un file WAV. E ottengo la conferma che si tratta di un file WAV in quanto ne riconosco l'header:
Imports System.IO
Module Module1
    Dim corrente As New FileStream("c:\users\antonello\desktop\voce.wav", FileMode.Open)

    Dim destinazione(&H100) As Byte
    Sub Main()
        
        corrente.Read(destinazione, 0, destinazione.Length)

        For n = 0 To destinazione.Length - 1
            Console.Write(Chr(destinazione(n)))
        Next

        Console.ReadKey()
    End Sub

End Module
RIFFD☻☺ WAVEfmt ►   ☺ ☺ ◄+  ◄+  ☺ data ☻☺ ????⌂???????????????????⌂??????????⌂⌂⌂
????⌂???????????????????⌂??⌂???????⌂???⌂?⌂?????⌂??????⌂????????⌂⌂???????????⌂???
????????????⌂????⌂⌂???⌂?⌂???⌂??????????????????????????????????⌂???????????⌂???⌂
⌂????⌂??????⌂⌂?


Ancora lettura e scrittura da MemoryStream

Imports System.IO
Module Module1
    Dim corrente As New MemoryStream
    Dim sorgente() As Byte = {1, 4, 65, 3, 7}
    Dim destinazione(5) As Byte
    Sub Main()
        corrente.Write(sorgente, 0, sorgente.Length)
        corrente.Position = 0
        corrente.Read(destinazione, 0, sorgente.Length)

        For n = 0 To sorgente.Length - 1
            Console.WriteLine(destinazione(n))
        Next
        Console.ReadKey()
    End Sub

End Module
Ecco come scrivo con Write e leggo con Read.

WriteByte e ReadByte, invece, servono per leggere singoli bytes.

mercoledì 9 ottobre 2013

Lettura e scrittura da MemoryStream

Sempre per leggere e scrivere dagli streams.
Uso un MemoryStream.

Imports System.IO
Module Module1
    Dim corrente As New MemoryStream
    Dim sorgente() As Byte = {1, 2, 3, 4, 5}
    Sub Main()
        corrente.Write(sorgente, 0, 3)
        Dim result As Integer
        corrente.Position = 1
        result = corrente.ReadByte()
        Console.WriteLine(result)
        Console.ReadKey()
    End Sub

End Module
Ho scritto sullo stream prelevando da una matrice di bytes.
In blocco. Quindi ho usato Write.
Write accetta come parametri la matrice da cui leggere, la posizione iniziale e la quantità di bytes. Legge solo da una matrice di bytes.

Per la lettura ho usato ReadByte che legge un byte alla volta.
Se invece avessi voluto leggere in blocco dentro una matrice di bytes, avrei dovuto usare Read.

Seguendo un esempio visto in rete, ho usato il tipo Integer per la variabile result in cui leggere il byte.
Ma potrei usare anche un tipo Byte?

Imports System.IO
Module Module1
    Dim corrente As New MemoryStream
    Dim sorgente() As Byte = {1, 2, 3, 4, 5}
    Sub Main()
        corrente.Write(sorgente, 0, 3)
        Dim result As Byte
        corrente.Position = 1
        result = corrente.ReadByte()
        Console.WriteLine(result)
        Console.ReadKey()
    End Sub

End Module
Sì, ottengo sempre risultati pertinenti.

Ho ottenuto un errore, prima, usando il tipo Byte in questo modo:
    Sub Main()
        corrente.Write(sorgente, 0, 3)
        Dim result As Byte
        result = corrente.ReadByte()
        Console.WriteLine(result)
        Console.ReadKey()
    End Sub
Il tipo di errore era questo:
Eccezione first-chance di tipo 'System.OverflowException' in Streams.exe
che, usando un tipo Integar, non veniva più segnalato:
    Sub Main()
        corrente.Write(sorgente, 0, 3)
        Dim result As Integer
        result = corrente.ReadByte()
        Console.WriteLine(result)
        Console.ReadKey()
    End Sub
ottenendo però questo risultato:
-1


Il problema era che non avevo reimpostato la posizione di lettura dell'oggetto MemoryStream, per cui la posizione, una volta effettuata la scrittura, restava impostata sull'ultimo byte, e il valore successivo era -1, che non può essere rappresentato in un tipo Byte, che è senza segno.
Invece il tipo Integer può rappresentare i numeri negativi:
1111 1111 1111 1111 1111 1111 1111 1111
dovrebbe essere la rappresentazione di -1 in un tipo Integer.
Usando un tipo che non può essere con segno, invece, ottengo un errore.
La riprova di questo la ottengo usando un tipo Integer senza segno:
    Sub Main()
        corrente.Write(sorgente, 0, 3)
        Dim result As UInt32
        result = corrente.ReadByte()
        Console.WriteLine(result)
        Console.ReadKey()
    End Sub
Ed ottengo sempre:
Eccezione first-chance di tipo 'System.OverflowException' in Streams.exe
come volevasi dimostrare.

Il fatto che ottengo -1 è dovuto al fatto che non ho resettato la posizione.
Rimedio subito e posso usare anche Byte (o anche Uint32):
    Sub Main()
        corrente.Write(sorgente, 0, 3)
        Dim result As Byte
        corrente.Position = 1
        result = corrente.ReadByte()
        Console.WriteLine(result)
        Console.ReadKey()
    End Sub
Ed ecco:
2


Okay!

martedì 8 ottobre 2013

Lettura e scrittura di bytes su uno stream

Ho creato un oggetto MemoryStream.
Imports System.IO
Public Class Form1
    Dim memoria As Stream
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        memoria = New MemoryStream


    End Sub
End Class
Ora devo vedere come si scrive e si legge da questo stream...

        memoria = New MemoryStream
        memoria.WriteByte(12)
Come si legge dallo stream?

        memoria = New MemoryStream
        memoria.WriteByte(12)

        Dim risultato As Integer
        memoria.Position = 0
        risultato = memoria.ReadByte()
        MsgBox(risultato)
E ottengo 12.

Scrivo due bytes.
        memoria = New MemoryStream
        memoria.WriteByte(12)
        memoria.WriteByte(4)
Leggo il byte in posizione 1.
                memoria = New MemoryStream
        memoria.WriteByte(12)
        memoria.WriteByte(4)

        Dim risultato As Integer
        memoria.Position = 1
        risultato = memoria.ReadByte()
        MsgBox(risultato)
E ottengo 4.

E finora ho capito come "funziona"!

martedì 1 ottobre 2013

BitsPerSample, BlockAlign, SampleRate, BytesPerSecond

1760:0100  52 49 46 46 05 2A 00 00-57 41 56 45 66 6D 74 20   RIFF.*..WAVEfmt
1760:0110  10 00 00 00 01 00 01 00-11 2B 00 00 11 2B 00 00   .........+...+..
1760:0120  01 00 08 00 64 61 74 61-E1 29 00 00 80 80 7F 80   ....data.)......
1760:0130  80 80 80 7F 80 80 80 80-80 80 80 80 80 80 80 80   ................
1760:0140  80 80 7F 80 80 80 80 80-80 80 80 80 81 80 7F 80   ................
1760:0150  80 80 80 80 80 80 80 80-80 80 80 80 80 80 80 80   ................
1760:0160  80 80 7F 80 80 80 80 80-80 80 80 80 80 80 80 80   ................
1760:0170  81 80 80 80 80 80 80 80-81 80 80 80 81 81 80 80   ................
Queste sono le voci dell'header del file WAV che esprimono rispettivamente:
11 2B 00 00 sample rate
11 2B 00 00 bytes per secondo
01 00 block align (bytes per campione) 
08 00 averageBitsPerSample (bits per campione per canale)
Le ultime due voci sono strettamente correlate: i bits per campione per canale, ridotti a bytes e moltiplicati per il numero dei canali, sono la quantità di informazione che deve essere creata per ogni campionamento. Moltiplicata per la frequenza di campionamento (Sample rate, in arancio, esprime i bytes per secondo (la voce in rosso).
Tutto qui.

In questo caso, essendoci 2B11H (11025) campioni al secondo e 2B11H (11025) bytes al secondo, è ovvio che per ogni campione ci sarà un byte, e quindi il numero di canali è 1 e il Block ALign è 1, essendo BitsPerSample 8, come risulta dall'header.

lunedì 30 settembre 2013

Altri appunti sul file WAV

-d
1760:0100  52 49 46 46 05 2A 00 00-57 41 56 45 66 6D 74 20   RIFF.*..WAVEfmt
1760:0110  10 00 00 00 01 00 01 00-11 2B 00 00 11 2B 00 00   .........+...+..
1760:0120  01 00 08 00 64 61 74 61-E1 29 00 00 80 80 7F 80   ....data.)......
1760:0130  80 80 80 7F 80 80 80 80-80 80 80 80 80 80 80 80   ................
1760:0140  80 80 7F 80 80 80 80 80-80 80 80 80 81 80 7F 80   ................
1760:0150  80 80 80 80 80 80 80 80-80 80 80 80 80 80 80 80   ................
1760:0160  80 80 7F 80 80 80 80 80-80 80 80 80 80 80 80 80   ................
1760:0170  81 80 80 80 80 80 80 80-81 80 80 80 81 81 80 80   ................
-
57 41 56 45 = WAVE

66 6D 74 20 = fmt 

10 00 00 00 dovrebbe essere la grandezza di ogni chunk... dovrebbe essere sempre 16 tranne in casi che al momento sfuggono alla mia comprensione.

01 00 = compression code. In questo caso sarebbe 1 quindi "uncompressed".

01 00 = number of channels. Avrebbe quindi un solo canale.

11 2B 00 00 = sample rate: in questo caso è 11025, se non sbaglio.

11 2B 00 00 = average bytes per second, bytes medi al secondo. E' uguale al sample rate.

Basta così.
Adesso dobbiamo vedere bene cosa si intende per sample rate, channels e bytes per second.

Iniziamo a studiare i files WAV

Iniziamo a smontare pezzo-pezzo un file wav...

C:\Users\Antonello\Downloads>debug ciccio.wav
-d
1760:0100  52 49 46 46 05 2A 00 00-57 41 56 45 66 6D 74 20   RIFF.*..WAVEfmt
1760:0110  10 00 00 00 01 00 01 00-11 2B 00 00 11 2B 00 00   .........+...+..
1760:0120  01 00 08 00 64 61 74 61-E1 29 00 00 80 80 7F 80   ....data.)......
1760:0130  80 80 80 7F 80 80 80 80-80 80 80 80 80 80 80 80   ................
1760:0140  80 80 7F 80 80 80 80 80-80 80 80 80 81 80 7F 80   ................
1760:0150  80 80 80 80 80 80 80 80-80 80 80 80 80 80 80 80   ................
1760:0160  80 80 7F 80 80 80 80 80-80 80 80 80 80 80 80 80   ................
1760:0170  81 80 80 80 80 80 80 80-81 80 80 80 81 81 80 80   ................
-
In relazione a questa guida abbiamo queste parti:
52 49 46 46 = i caratteri ASCII per RIFF

05 2A 00 00 = la grandezza del file meno gli otto bytes di questa voce e della precedente.
00002A05 a quanto corrisponde? Mano alla calcolatrice!
10757.

Verifichiamo le dimensioni del file:
Clicco col destro sul file, e ottengo le proprietà:
10,5 KB (10.765 byte)
che corrisponde alla perfezione con la grandezza del file. 10757 + 8 bytes = 10765.
Grande!!!

Inizio a usare DirectSound.

Procediamo, come al solito con linquaggio da ignorante, a cercare di capire qualcosa su come si usa DirectSound.

Dunque, io creo un Device. Non ho ancora capito bene che accidenti sia, ma lo creo ugualmente, toh!

Imports Microsoft.DirectX
Imports Microsoft.DirectX.DirectSound
Public Class Form1
    Dim _dev As Device
    Dim _buffer As SecondaryBuffer
Eccolo dichiarato.
Dichiaro anche un buffer.

Vediamo come si istanzia questo Device.

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        _dev = New Device
        _dev.SetCooperativeLevel(Me.Handle, CooperativeLevel.Priority)
Semplicemente con New Device. Il costruttore del Device non ha parametri.

Bene, ora che abbiamo dato vita a un Device, impostiamo il Cooperative Level.
Che accidenti è, per la precisione, questo Cooperative Level?

Credo che abbia a che fare con le altre applicazioni, ossia con la competizione con altre applicazioni per qualcosa, come le periferiche... ma non ne sono molto sicuro.

In ogni caso, sintetizziamo e andiamo avanti: Si dichiara e si istanzia un Device, che ha un semplice costruttore senza parametri, e quindi si imposta il Cooperative Level del Device stesso.
Quindi, dichiarato il buffer, si istanzia. Questo ha nel costruttore due parametri, uno che specifica il file, e l'altro che specifica il Device.
Forse si tratta di un discorso del tipo: "Creo un buffer nel quale immagazzino i dati del file e lo associo al tale Device.

Bene... con un linguaggio da caprone inizio a capire la cosa. Poi con calma cercherò di affinare sia la comprensione sia il linguaggio.
Se aspetto di comprendere tutto, non vado più avanti.

sabato 21 settembre 2013

Passaggio di parametri tramite stack con indicazione dei parametri nella direttiva PROC.

Ripassati CMP, alcuni salti condizionati, diverse flags, e riscoperta la tecnica di Giobe per ottenere la stampa a video dei codici ASCII dei numeri, mediante la quale egli realizza la procedura Byt2Asc, ho pensato di creare una Byt2Asc nella quale il parametro sia passato mediante lo stack, aggiungendo alla direttiva PROC il parametro.
Ecco la procedura:
public Byt2Asc
text segment byte public
Byt2Asc proc near C,num:WORD
 mov ax,num
 push ax
 shr al,1
 shr al,1
 shr al,1
 shr al,1
 
 cmp al,0AH
 jb X1
 add al,7
X1: add al,30h

 mov ah,0eh
 int 10h
 
 pop ax
 and al,0Fh
 
 cmp al,0AH
 jb X2
 add al,7
X2: add al,30h

 mov ah,0eh
 int 10h

 ret 2
Byt2Asc endp
text ends
end
Per specificare i parametri nella direttiva PROC, bisogna dichiarare anche la "convenzione di chiamata", che può essere C o di altro tipo (la ripasserò in seguito) in quanto la procedura deve conoscere l'ordine nel quale ritirare i parametri dallo stack.
Ho dichiarato C, ma mi sono cominciato a scontrare con una persistente segnalazione di errore, che non riuscivo a capire, di "unresolved external". Poi, ricordando l'associazione di idee di convenzione di chiamata C con l'underscore iniziale, ho capito che il problema era che nel programma chiamante avrei dovuto far precedere il nome Byt2Asc da un underscore, così ho fatto e ho risolto il problema:
extrn Cancella:near
extrn _Byt2Asc:near
extrn KeyWait:near

text segment public

.....
Ho assemblato e linkato il tutto, e funziona.
Dal momento che l'aggiunta di parametri automatizza parte del codice, vediamo qual è il vero codice in DEBUG:
AX=00F4  BX=0000  CX=0450  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1786  CS=1781  IP=001E   NV UP EI PL NZ NA PO NC
1781:001E 55            PUSH    BP
-p

AX=00F4  BX=0000  CX=0450  DX=0000  SP=03FA  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1786  CS=1781  IP=001F   NV UP EI PL NZ NA PO NC
1781:001F 8BEC          MOV     BP,SP
-p

AX=00F4  BX=0000  CX=0450  DX=0000  SP=03FA  BP=03FA  SI=0000  DI=0000
DS=1771  ES=1771  SS=1786  CS=1781  IP=0021   NV UP EI PL NZ NA PO NC
1781:0021 8B4604        MOV     AX,[BP+04]                         SS:03FE=00F4
-p

........


AX=0E34  BX=0000  CX=0450  DX=0000  SP=03FA  BP=03FA  SI=0000  DI=0000
DS=1771  ES=1771  SS=1786  CS=1781  IP=0048   NV UP EI PL NZ NA PO NC
1781:0048 5D            POP     BP
-p

AX=0E34  BX=0000  CX=0450  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1786  CS=1781  IP=0049   NV UP EI PL NZ NA PO NC
1781:0049 C20200        RET     0002
-p
Ecco: Le istruzioni "automatiche" sono:
PUSH BP
MOV BP,SP

.....

POP BP
Ossia il salvataggio nello stack del Base Pointer, l'attribuzione al Base Pointer del valore di SP, e poi, alla fine, presupponendo che SP sia tornato al valore corrispondente a BP mediante il corretto equilibrio dei pushaggi e dei poppaggi, l'istruzione che riprende il valore precedente di BP.

Registri flag di segno e di parità.

Ecco, approfondiamo un po' queste belle flags.
In particolare, mi incuriosisce la flag di segno.
Vuoi vedere che questa flag diventa NG semplicemente se il risultato di un'operazione ha il primo bit pari a 1?
Per verificarlo, faccio una somma che mi dia il primo bit pari a 1:
text segment public
start:
 mov al,01111111B
 mov bl,10000000B
 add al,bl
 mov ah,4ch
 int 21h
 
text ends
end start
-r
AX=0000  BX=0000  CX=000A  DX=0000  SP=0000  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1781  CS=1781  IP=0000   NV UP EI PL NZ NA PO NC
1781:0000 B07F          MOV     AL,7F
-t

AX=007F  BX=0000  CX=000A  DX=0000  SP=0000  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1781  CS=1781  IP=0002   NV UP EI PL NZ NA PO NC
1781:0002 B380          MOV     BL,80
-t

AX=007F  BX=0080  CX=000A  DX=0000  SP=0000  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1781  CS=1781  IP=0004   NV UP EI PL NZ NA PO NC
1781:0004 02C3          ADD     AL,BL
-t

AX=00FF  BX=0080  CX=000A  DX=0000  SP=0000  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1781  CS=1781  IP=0006   NV UP EI NG NZ NA PE NC
1781:0006 B44C          MOV     AH,4C
-
Sì! Verificato! Ovviamente, se stiamo facendo un'operazione senza segno, ce ne freghiamo.

C'è un'altra flag che cambia, la seconda, che da PO diventa PE. Che significa? Giobe, aiutami tu!

Ecco: sarebbe il Parity Flag. Ricordando che in inglese Even e Odd significano Pari e Dispari, PO significa che nel risultato c'è un numero dispari di 1 mentre PE significa numero pari di 1.

Ripasso dell'istruzione CMP e di alcune flags.

Ripassiamo un po' l'istruzione CMP e le flags

Ecco un programmuscolo:
text segment public
start:
 mov al,3
 cmp al,8
 
 mov ah,4ch
 int 21h
 
text ends
end start
In debug, col comando traccia:
c:\Assembly>debug cmp.exe
-t

AX=0003  BX=0000  CX=0008  DX=0000  SP=0000  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1781  CS=1781  IP=0002   NV UP EI PL NZ NA PO NC
1781:0002 3C08          CMP     AL,08
-t

AX=0003  BX=0000  CX=0008  DX=0000  SP=0000  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1781  CS=1781  IP=0004   NV UP EI NG NZ AC PO CY
1781:0004 B44C          MOV     AH,4C
-
Rivediamo un po' queste flags...

La flag di riporto esprime un riporto o un prestito, e nella sottrazione di numero maggiore da numero minore si genera un prestito, per cui in questo caso la prima flag viene modificat da NC a CY.
La terza flag è quella di riporto ausiliario che esprime il riporto nell'ambito del nibble. In questo caso il riporto c'è, e quindi da NA diventa AC.
La quinta flag è quella del segno, che prende, se non vado errato, il valore del bit più significativo, che in questo caso è 1, e quindi da PL diventa NG.

Vediamo di ricostruire cosa avviene nello specifico in questa sottrazione di numeri binari.
3 - 8 si traduce in binario con 00000011 - 00001000.
Mettiamolo in colonna:

00000011 -
00001000 =
____________
11111011

che, nella lettura con segno equivale a -5, giusto risultato di 8 - 3.

Il mio "trucco" per leggere velocemente i numeri binari negativi è questo. Quando dal bit più significativo c'è una fila ininterrotta di 1, io vado a vedere l'ultimo 1 della fila, e vedo a quale numero "corrisponde", ossia quale valore avrebbe il numero binario senza segno se questo 1 fosse l'unico: in questo caso, se l'ultimo numero della fila fosse l'unico, ossia se fosse 00001000, il byte avrebbe valore di 8.
Quindi vado avanti e considero i restanti 1 come se fossero soli nel byte: in questo caso sono la penultima e l'ultima cifra del byte a essere 1, e se fossero soli il byte avrebbe valore di 3.
Quindi sottraggo al numero calcolato precedentemente, ossia a 8, il secondo numero calcolato, ossia il 3, e cambio di segno il risultato, ottenendo -5.

Veriabili locali e stack

Variabili locali.
extrn Cancella:near

extrn KeyWait:near
;definiamo il segmento
text segment public
;inizio del programma
start:
 
 call Cancella

 call prc

 call KeyWait
 
 mov ah,4ch
 int 21H
 
prc proc near
 push bp
 mov bp,sp
 
 ;crea la variabile locale
 sub sp,2
 
 mov [bp-2],41h
 mov ah,0eh
 mov al,[bp-2]
 int 10h
 
 ;cancella la variabile locale
 mov sp,bp
 pop bp
 ret 
prc endp
text ends

stack segment para stack
 db 1024 dup(00H)

stack ends

end start
 
Ecco. Molto semplice.
Una volta create le variabili locali è fondamentale resettare lo SP quando si chiude la procedura riportandolo al valore contenuto in BP.
Quindi la locazione puntata da BP è quella che separa, nello stack, i parametri dalle variabili locali.
Cerchiamo di costruire un programma che usi sia parametri sia variabili locali, in modo da studiarne lo stack.
extrn Cancella:near

extrn KeyWait:near
;definiamo il segmento
text segment public
;inizio del programma
start:
 
 call Cancella
 
 mov al,'A'

 push ax
 call prc
 
 call KeyWait
 
 mov ah,4ch
 int 21H
 
prc proc near
 push bp
 mov bp,sp
 
 ;crea la variabile locale.
 sub sp,2
 mov [bp-2],2
 
 mov ax,[bp+4]
 add ax,[bp-2]
 
 mov ah,0eh
 int 10h
 
 ;cancella la variabile locale
 mov sp,bp
 pop bp
 ret 2
prc endp
text ends

stack segment para stack
 db 1024 dup(00H)

stack ends

end start
 
Ecco: il parametro passato è la lettera A. La variabile locale è 2.
La procedura aggiunge la variabile locale al parametro ottenendo 42H, ossia la lettera C, che stampa a video.
Funziona:
C
c:\Assembly>

Vediamo un po' come è congegnato lo stack con parametri, variabili locali, indirizzo di ritorno e BP originario salvato:

AX=0041  BX=0000  CX=0440  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0010   NV UP EI PL NZ NA PO NC
1781:0010 55            PUSH    BP
-p

AX=0041  BX=0000  CX=0440  DX=0000  SP=03FA  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0011   NV UP EI PL NZ NA PO NC
1781:0011 8BEC          MOV     BP,SP
-p

AX=0041  BX=0000  CX=0440  DX=0000  SP=03FA  BP=03FA  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0013   NV UP EI PL NZ NA PO NC
1781:0013 83EC02        SUB     SP,+02
-p

AX=0041  BX=0000  CX=0440  DX=0000  SP=03F8  BP=03FA  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0016   NV UP EI PL NZ NA PO NC
1781:0016 C746FE0200    MOV     WORD PTR [BP-02],0002              SS:03F8=3302
-p

AX=0041  BX=0000  CX=0440  DX=0000  SP=03F8  BP=03FA  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=001B   NV UP EI PL NZ NA PO NC
1781:001B 8B4604        MOV     AX,[BP+04]                         SS:03FE=0041
-
-d ss:03f0
1785:03F0  FA 03 1E 00 81 17 C4 11-02 00 00 00 09 00 41 00   ..............A.
1785:0400  C2 02 00 00 55 8B EC 57-56 8B 7E 04 39 3E 7C 01   ....U..WV.~.9>|.
1785:0410  75 06 C7 06 7E 01 FF FF-39 3E 80 01 75 05 6A 00   u...~...9>..u.j.
1785:0420  E8 0F 12 6B DF 0E 8B B7-2E 4B C1 E6 02 03 36 B4   ...k.....K....6.
1785:0430  52 EB 18 8B C6 2B 06 B4-52 C1 F8 02 50 E8 66 A1   R....+..R...P.f.
1785:0440  8B 04 C1 E0 02 03 06 B4-52 8B F0 83 3C FF 75 E3   ........R...<.u.
1785:0450  6B DF 0E C7 87 2E 4B FF-FF 33 C0 5E 5F C9 C2 02   k.....K..3.^_...
1785:0460  00 00 55 8B EC 56 8B 5E-04 6B DB 0E 8D 87 2E 4B   ..U..V.^.k.....K
-
Ecco: SP punta la variabile locale, BP punta il suo valore originario salvato nello stack.

Riportare al valore iniziale il puntatore dello stack dopo il passaggio di parametri tramite stack

Nel mio programmino rimane un conto in sospeso con lo stack.
Le due WORD pushate prima di chiamare la procedura, contenenti i parametri, non vengono poi ripoppate o smaltite in qualche modo, cosicchè SP non torna ai valori iniziali fino alla fine del programma.
Seguo in DEBUG i valori prima e dopo la chiamata della procedura Stampa:
AX=0003  BX=0000  CX=0440  DX=0000  SP=0400  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0003   NV UP EI PL NZ NA PO NC
1781:0003 B041          MOV     AL,41
-p

AX=0041  BX=0000  CX=0440  DX=0000  SP=0400  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0005   NV UP EI PL NZ NA PO NC
1781:0005 B30C          MOV     BL,0C
-p

AX=0041  BX=000C  CX=0440  DX=0000  SP=0400  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0007   NV UP EI PL NZ NA PO NC
1781:0007 50            PUSH    AX
-p

AX=0041  BX=000C  CX=0440  DX=0000  SP=03FE  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0008   NV UP EI PL NZ NA PO NC
1781:0008 53            PUSH    BX
-p

AX=0041  BX=000C  CX=0440  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0009   NV UP EI PL NZ NA PO NC
1781:0009 E80700        CALL    0013
-
Il valore di base di SP è 0400.
Con il pushaggio dei parametri diventa poi 03FE e 03FC.

-t

AX=0041  BX=000C  CX=0440  DX=0000  SP=03FA  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0013   NV UP EI PL NZ NA PO NC
1781:0013 55            PUSH    BP
-
Con la CALL diventa 03FA perchè viene pushato l'indirizzo di ritorno.

-t

AX=0041  BX=000C  CX=0440  DX=0000  SP=03F8  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0014   NV UP EI PL NZ NA PO NC
1781:0014 8BEC          MOV     BP,SP
-t>
Con il PUSH BP all'interno della procedura diventa 03F8.

AX=0041  BX=000C  CX=0440  DX=0000  SP=03F8  BP=03F8  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0016   NV UP EI PL NZ NA PO NC
1781:0016 8B5E04        MOV     BX,[BP+04]                         SS:03FC=000C
-t

AX=0041  BX=000C  CX=0440  DX=0000  SP=03F8  BP=03F8  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0019   NV UP EI PL NZ NA PO NC
1781:0019 8B4606        MOV     AX,[BP+06]                         SS:03FE=0041
-t

AX=0041  BX=000C  CX=0440  DX=0000  SP=03F8  BP=03F8  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0019   NV UP EI PL NZ NA PO NC
1781:0019 8B4606        MOV     AX,[BP+06]                         SS:03FE=0041
-t

AX=0041  BX=000C  CX=0440  DX=0000  SP=03F8  BP=03F8  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=001C   NV UP EI PL NZ NA PO NC
1781:001C B409          MOV     AH,09
-t

AX=0941  BX=000C  CX=0440  DX=0000  SP=03F8  BP=03F8  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=001E   NV UP EI PL NZ NA PO NC
1781:001E B700          MOV     BH,00
-t

AX=0941  BX=000C  CX=0440  DX=0000  SP=03F8  BP=03F8  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0020   NV UP EI PL NZ NA PO NC
1781:0020 B90100        MOV     CX,0001
-t

AX=0941  BX=000C  CX=0001  DX=0000  SP=03F8  BP=03F8  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0023   NV UP EI PL NZ NA PO NC
1781:0023 CD10          INT     10
-p

A
AX=0941  BX=000C  CX=0001  DX=0000  SP=03F8  BP=03F8  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0025   NV UP EI PL NZ NA PO NC
1781:0025 5D            POP     BP
-p
E resta tale fino ad ora. Quindi il POP BP lo riporta a 03FA.
AX=0941  BX=000C  CX=0001  DX=0000  SP=03FA  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0026   NV UP EI PL NZ NA PO NC
1781:0026 C3            RET
-


E con il RET, col recupero dell'indirizzo di ritorno, va a 03FC.
-t

AX=0941  BX=000C  CX=0001  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=000C   NV UP EI PL NZ NA PO NC
1781:000C E81800        CALL    0027
-


...per poi restare uguale fino alla fine del programma.
-p

AX=1C0D  BX=000C  CX=0001  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=000F   NV UP EI PL NZ NA PO NC
1781:000F B44C          MOV     AH,4C
-p

AX=4C0D  BX=000C  CX=0001  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0011   NV UP EI PL NZ NA PO NC
1781:0011 CD21          INT     21
-p

Program terminated normally
-
C'è quindi un conto in sospeso con lo stack, che rimane 4 bytes, ossia 2 words, più in basso rispetto a quello che dovrebbe essere.

Come "aggiustarlo" alla fine della procedura?
Come "riassorbire", cioè, i due parametri che erano stati pushati prima della chiamata della procedura?
Mi pare di ricordare che posporre un numero a RET determina un riassestamento di SP.
Controlliamo...

extrn Cancella:near

extrn KeyWait:near
;definiamo il segmento
text segment public
;inizio del programma
start:
 
 call Cancella
 
 mov al,'A'
 mov bl,0Ch
 push ax
 push bx
 call Stampa
 
 
 call KeyWait
 
 mov ah,4ch
 int 21H
 
stampa proc near
 push bp
 mov bp,sp
 mov bx,[bp+4]
 mov ax,[bp+6]
 mov ah,09h
 mov bh,00h
 mov cx,1
 int 10h
 pop bp
 ret 4
stampa endp
text ends

stack segment para stack
 db 1024 dup(00H)

stack ends

end start
 

AX=0941  BX=000C  CX=0440  DX=0000  SP=03F8  BP=03F8  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0020   NV UP EI PL NZ NA PO NC
1781:0020 B90100        MOV     CX,0001
-p

AX=0941  BX=000C  CX=0001  DX=0000  SP=03F8  BP=03F8  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0023   NV UP EI PL NZ NA PO NC
1781:0023 CD10          INT     10
-p
A
AX=0941  BX=000C  CX=0001  DX=0000  SP=03F8  BP=03F8  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0025   NV UP EI PL NZ NA PO NC
1781:0025 5D            POP     BP
-t

AX=0941  BX=000C  CX=0001  DX=0000  SP=03FA  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0026   NV UP EI PL NZ NA PO NC
1781:0026 C20400        RET     0004
-p

AX=0941  BX=000C  CX=0001  DX=0000  SP=0400  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=000C   NV UP EI PL NZ NA PO NC
1781:000C E81A00        CALL    0029
-
Ecco. Aggiungendo a RET il numero di bytes che sono stati occupati dai parametri passati, oltre a "rimangiarsi" l'indirizzo di ritorno, si resetta SP 4 bytes più in su, al punto di partenza!

Mi pare che ci fosse anche la possibilità di riportare lo stack al valore di base mediante un'istruzione specifica, ma ciò andrebbe fatto dopo il "rientro" nel programma di base.
extrn Cancella:near

extrn KeyWait:near
;definiamo il segmento
text segment public
;inizio del programma
start:
 
 call Cancella
 
 mov al,'A'
 mov bl,0Ch
 push ax
 push bx
 call Stampa
 
 add sp,4
 call KeyWait
 
 mov ah,4ch
 int 21H
 
stampa proc near
 push bp
 mov bp,sp
 mov bx,[bp+4]
 mov ax,[bp+6]
 mov ah,09h
 mov bh,00h
 mov cx,1
 int 10h
 pop bp
 ret 
stampa endp
text ends

stack segment para stack
 db 1024 dup(00H)

stack ends

end start
 
AX=0941  BX=000C  CX=0001  DX=0000  SP=03F8  BP=03F8  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0028   NV UP EI PL NZ AC PE NC
1781:0028 5D            POP     BP
-t

AX=0941  BX=000C  CX=0001  DX=0000  SP=03FA  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0029   NV UP EI PL NZ AC PE NC
1781:0029 C3            RET
-t

AX=0941  BX=000C  CX=0001  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=000C   NV UP EI PL NZ AC PE NC
1781:000C 83C404        ADD     SP,+04
-t

AX=0941  BX=000C  CX=0001  DX=0000  SP=0400  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=000F   NV UP EI PL NZ AC PE NC
1781:000F E81800        CALL    002A
-
Ottimo!

Un BAT per assemblare e linkare in un attimo

Mi costruisco un "pipistrello" per l'assemblaggio e il linkaggio, roba di due minuti, perfezionando esegui.bat.
@echo off
ml /c /Zm %1.asm
link %1,,nul,%2,,
Perfetto! Così mi basta inserire il nome del sorgente e quello della libreria per ottenere tutto immediatamente.

c:\Assembly>dir uno*
 Il volume nell'unità C non ha etichetta.
 Numero di serie del volume: ECE6-4560

 Directory di c:\Assembly

21/09/2013  12.34               500 uno.asm
               1 File            500 byte
               0 Directory  12.490.698.752 byte disponibili

c:\Assembly>esegui uno,libreria
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: uno.asm

Microsoft (R) Segmented Executable Linker  Version 5.60.339 Dec  5 1994
Copyright (C) Microsoft Corp 1984-1993.  All rights reserved.


c:\Assembly>dir uno*
 Il volume nell'unità C non ha etichetta.
 Numero di serie del volume: ECE6-4560

 Directory di c:\Assembly

21/09/2013  12.34               500 uno.asm
21/09/2013  12.44             1.600 uno.exe
21/09/2013  12.44               153 uno.obj
               3 File          2.253 byte
               0 Directory  12.490.694.656 byte disponibili

c:\Assembly>
Perfetto!

venerdì 20 settembre 2013

Uso di MOV per recuperare i parametri dallo stack

Bene.
Anzichè con il POP, posso prendere i parametri pushati nello stack mediante l'istruzione MOV.
Ho provato (memoria corta!) a porre questi valori in un qualunque registro con l'istruzione MOV, prendendo come riferimento la locazione puntata dal registro SP, secondo il ragionamento che sommando 2 a questo valore si salta l'indirizzo di ritorno e si prende l'ultimo parametro pushato, poi sommando 4 si va a prendere il primo parametro
extrn Cancella:near

extrn KeyWait:near
;definiamo il segmento
text segment public
;inizio del programma
start:
 call Cancella
 
 mov al,'A'
 mov bl,0Ch
 push ax
 push bx
 call Stampa
 
 
 call KeyWait
 
 mov ah,4ch
 int 21H
 
stampa proc near
 mov bx,[sp+2]
 mov ax,[sp+4]
 mov ah,09h
 mov bh,00h
 mov cx,1
 int 10h
 ret
stampa endp
text ends

stack segment para stack
 db 1024 dup(00H)

stack ends

end start
 
E ottengo un errore dall'assemblatore:
c:\Assembly>esegui uno
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: uno.asm

c:\Assembly>esegui uno
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: uno.asm
uno.asm(24) : error A2031: must be index or base register
uno.asm(25) : error A2031: must be index or base register

c:\Assembly>

Andiamo a rivedere i registri.
Ecco: registri puntatore e registri indice

Questo significa che per questo scopo, ossia per trasferire dati prelevati dallo stack, si possono usare solo BP, DI e SI.
Allora, invece di usare BP proviamo a usare DI o SI.
extrn Cancella:near

extrn KeyWait:near
;definiamo il segmento
text segment public
;inizio del programma
start:
 call Cancella
 
 mov al,'A'
 mov bl,0Ch
 push ax
 push bx
 call Stampa
 
 
 call KeyWait
 
 mov ah,4ch
 int 21H
 
stampa proc near
 mov si,sp
 mov bx,[si+2]
 mov ax,[si+4]
 mov ah,09h
 mov bh,00h
 mov cx,1
 int 10h
 ret
stampa endp
text ends

stack segment para stack
 db 1024 dup(00H)

stack ends

end start
 
c:\Assembly>esegui uno
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: uno.asm

c:\Assembly>
Ancora, con DI:
extrn Cancella:near

extrn KeyWait:near
;definiamo il segmento
text segment public
;inizio del programma
start:
 call Cancella
 
 mov al,'A'
 mov bl,0Ch
 push ax
 push bx
 call Stampa
 
 
 call KeyWait
 
 mov ah,4ch
 int 21H
 
stampa proc near
 mov di,sp
 mov bx,[di+2]
 mov ax,[di+4]
 mov ah,09h
 mov bh,00h
 mov cx,1
 int 10h
 ret
stampa endp
text ends

stack segment para stack
 db 1024 dup(00H)

stack ends

end start
 
c:\Assembly>esegui uno
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: uno.asm

c:\Assembly>
Sì, con questi non mi dà errore.

Comunque, siccome classicamente il registro deputato a questa funzione è il BP, seguiamo la via "ortodossa":
extrn Cancella:near

extrn KeyWait:near
;definiamo il segmento
text segment public
;inizio del programma
start:
 call Cancella
 
 mov al,'A'
 mov bl,0Ch
 push ax
 push bx
 call Stampa
 
 
 call KeyWait
 
 mov ah,4ch
 int 21H
 
stampa proc near
 mov bp,sp
 mov bx,[bp+2]
 mov ax,[bp+4]
 mov ah,09h
 mov bh,00h
 mov cx,1
 int 10h
 ret
stampa endp
text ends

stack segment para stack
 db 1024 dup(00H)

stack ends

end start
 
c:\Assembly>esegui uno
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: uno.asm

c:\Assembly>
Bene. Adesso assembliamo, linkiamo e vediamo come funziona.
c:\Assembly>esegui uno
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: uno.asm

c:\Assembly>link uno

Microsoft (R) Segmented Executable Linker  Version 5.60.339 Dec  5 1994
Copyright (C) Microsoft Corp 1984-1993.  All rights reserved.

Run File [uno.exe]:
List File [nul.map]:
Libraries [.lib]: libreria
Definitions File [nul.def]:

c:\Assembly>
A
c:\Assembly>
Ottimo.

Non utilizzare SP è fondamentale in modo da poter continuare a usare lo stack anche nel contesto della procedura, ovvio!
Un ulteriore passaggio: BP potrebbe essere "occupato" con un valore importante, quindi per non perderlo lo pushiamo nello stack prima di usarlo come puntatore dei parametri.
Ovviamente, nel puntare i parametri bisogna aggiungere 2 in più a BP perchè oltre all'indirizzo di ritorno va saltato anche il valore precedente di BP pushato per ultimo nello stack.
extrn Cancella:near

extrn KeyWait:near
;definiamo il segmento
text segment public
;inizio del programma
start:
 call Cancella
 
 mov al,'A'
 mov bl,0Ch
 push ax
 push bx
 call Stampa
 
 
 call KeyWait
 
 mov ah,4ch
 int 21H
 
stampa proc near
 push bp
 mov bp,sp
 mov bx,[bp+4]
 mov ax,[bp+6]
 mov ah,09h
 mov bh,00h
 mov cx,1
 int 10h
 ret
stampa endp
text ends

stack segment para stack
 db 1024 dup(00H)

stack ends

end start
 


Adesso "sistemiamo" anche il recupero dello stack...
Intanto assemblo e linko per poi seguirmi tutte le istruzioni con -t e -p in debug. Non c'è bisogno di fare la "dimostrazione pratica" dell'errore, perchè incontro una finestra di messaggio secondo cui NTVDM ha incontrato un errore.

Ovviamente, lo stack non perdona! Finchè l'ultimo valore pushato è l'indirizzo di ritorno, con l'istruzione RET esso viene poppato e usato per far "rientrare" il flusso nel programma principale, ma adesso se non ripoppo il valore in BP questo viene ripoppato dall'istruzione RET e scambiato per l'indirizzo di ritorno, dirottando il programma vattelappesca dove!
Rimedio:
extrn Cancella:near

extrn KeyWait:near
;definiamo il segmento
text segment public
;inizio del programma
start:
 call Cancella
 
 mov al,'A'
 mov bl,0Ch
 push ax
 push bx
 call Stampa
 
 
 call KeyWait
 
 mov ah,4ch
 int 21H
 
stampa proc near
 push bp
 mov bp,sp
 mov bx,[bp+4]
 mov ax,[bp+6]
 mov ah,09h
 mov bh,00h
 mov cx,1
 int 10h
 pop bp
 ret
stampa endp
text ends

stack segment para stack
 db 1024 dup(00H)

stack ends

end start
assemblando e linkando...
A
c:\Assembly>
Perfetto... per il momento.
Ma andiamo a seguire in debug: c'è qualcosa che non mi torna: sono stati pushati due valori che non vengono poppati...
AX=0041  BX=0000  CX=0440  DX=0000  SP=0400  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0005   NV UP EI PL NZ NA PO NC
1781:0005 B30C          MOV     BL,0C
-p

AX=0041  BX=000C  CX=0440  DX=0000  SP=0400  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0007   NV UP EI PL NZ NA PO NC
1781:0007 50            PUSH    AX
-p

AX=0041  BX=000C  CX=0440  DX=0000  SP=03FE  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0008   NV UP EI PL NZ NA PO NC
1781:0008 53            PUSH    BX
-p

AX=0041  BX=000C  CX=0440  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0009   NV UP EI PL NZ NA PO NC
1781:0009 E80700        CALL    0013
-t
Ecco, con il "pushaggio" di AX e BX lo Stack Pointer è giustamente arretrato di 4 bytes.

AX=0041  BX=000C  CX=0440  DX=0000  SP=03FA  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0013   NV UP EI PL NZ NA PO NC
1781:0013 55            PUSH    BP
-p
e adesso è andato ancora più indietro, a 03FAH, per il "pushaggio" dell'indirizzo di ritorno.

AX=0041  BX=000C  CX=0440  DX=0000  SP=03F8  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0014   NV UP EI PL NZ NA PO NC
1781:0014 8BEC          MOV     BP,SP
-p
E ancora più indietro, in quanto nel contesto della procedura viene pushato anche BP.

Seguiamo lo svolgersi della procedura...
.....

-p
A
AX=0941  BX=000C  CX=0001  DX=0000  SP=03F8  BP=03F8  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0025   NV UP EI PL NZ NA PO NC
1781:0025 5D            POP     BP
-p

AX=0941  BX=000C  CX=0001  DX=0000  SP=03FA  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0026   NV UP EI PL NZ NA PO NC
1781:0026 C3            RET
-p
Ecco, con il "poppaggio" di BP per restituire a questo registro il suo valore originario, SP è "risalito" a 03FAH.

AX=0941  BX=000C  CX=0001  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=000C   NV UP EI PL NZ NA PO NC
1781:000C E81800        CALL    0027
-p
...e con il "poppaggio" dell'indirizzo di ritorno ad opera dell'istruzione RET SP viene aumentato ancora di 2, andando a 03FCH.

Continuiamo a seguire il programma...
AX=1970  BX=000C  CX=0001  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=000F   NV UP EI PL NZ NA PO NC
1781:000F B44C          MOV     AH,4C
-
-p

AX=4C70  BX=000C  CX=0001  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1785  CS=1781  IP=0011   NV UP EI PL NZ NA PO NC
1781:0011 CD21          INT     21
-p

Program terminated normally
-
...laddove c'è un conto in sospeso con lo stack!!!

Come si fa a pareggiare i conti con lo stack?

Parametri tramite stack: due erroracci che si compensano!

Ecco trattata la procedura Stampa mediante passaggio di parametri per mezzo dello stack.
extrn Cancella:near

extrn KeyWait:near
;definiamo il segmento
text segment public
;inizio del programma
start:
 call Cancella
 
 mov al,'A'
 mov bl,0Ch
 push bx
 push ax
 call Stampa
 
 
 call KeyWait
 
 mov ah,4ch
 int 21H
 
stampa proc near
 pop ax
 mov bx,ax
 pop ax
 mov ah,09h
 mov bh,00h
 mov cx,1
 int 10h
 ret
stampa endp
text ends

stack segment para stack
 db 1024 dup(00H)

stack ends

end start
 
Funziona.
Vediamo nel dettaglio cosa accade:
AX=0003  BX=0000  CX=0430  DX=0000  SP=0400  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=0003   NV UP EI PL NZ NA PO NC
1781:0003 B041          MOV     AL,41
-p

AX=0041  BX=0000  CX=0430  DX=0000  SP=0400  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=0005   NV UP EI PL NZ NA PO NC
1781:0005 B30C          MOV     BL,0C
-
qui in AL e BL vengono posti rispettivamente il carattere e il colore.
AX=0041  BX=0000  CX=0430  DX=0000  SP=0400  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=0005   NV UP EI PL NZ NA PO NC
1781:0005 B30C          MOV     BL,0C
-p

AX=0041  BX=000C  CX=0430  DX=0000  SP=0400  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=0007   NV UP EI PL NZ NA PO NC
1781:0007 53            PUSH    BX
-d 1784:03f0
1784:03F0  00 00 00 00 03 00 41 00-00 00 07 00 81 17 C4 11   ......A.........
1784:0400  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0410  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0420  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0430  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0440  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0450  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0460  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
-
Prima che i parametri vengano passati tramite stack, ottengo un dump della "cima" dello stack.
Adesso vediamo come cambia dopo il primo push:
-p

AX=0041  BX=000C  CX=0430  DX=0000  SP=03FE  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=0008   NV UP EI PL NZ NA PO NC
1781:0008 50            PUSH    AX
-d 1784:03f0
1784:03F0  00 00 00 00 41 00 00 00-08 00 81 17 C4 11 0C 00   ....A...........
1784:0400  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0410  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0420  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0430  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0440  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0450  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0460  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
-
Ecco: in cima allo stack è stato depositato il colore presente in BH. SP è stato decrementato di 2 (è stata inserita una WORD)
Secondo push:
AX=0041  BX=000C  CX=0430  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=0009   NV UP EI PL NZ NA PO NC
1781:0009 E80700        CALL    0013
-d 1784:03f0
1784:03F0  00 00 41 00 00 00 09 00-81 17 C4 11 41 00 0C 00   ..A.........A...
1784:0400  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0410  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0420  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0430  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0440  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0450  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0460  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
-
E adesso in cima allo stack è stato depositato anche il carattere (41H, codice ASCII della A), e SP è stato decrementato ancora di 2.

Successivamente, con il CALL (NEAR), viene depositato nello stack l'indirizzo "di ritorno" calcolato:
-p

AX=0041  BX=000C  CX=0430  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=0009   NV UP EI PL NZ NA PO NC
1781:0009 E80700        CALL    0013
-t

AX=0041  BX=000C  CX=0430  DX=0000  SP=03FA  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=0013   NV UP EI PL NZ NA PO NC
1781:0013 58            POP     AX
-d1784:03f0
1784:03F0  41 00 00 00 13 00 81 17-C4 11 0C 00 41 00 0C 00   A...........A...
1784:0400  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0410  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0420  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0430  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0440  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0450  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0460  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
-
E guardacaso, una fortunata (o sfortunata) coincidenza fa sì che casualmente il programma "funzioni"! adesso faccio POP AX:
-p

AX=000C  BX=000C  CX=0430  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=0014   NV UP EI PL NZ NA PO NC
1781:0014 8BD8          MOV     BX,AX
-d 1784:03f0
1784:03F0  41 00 0C 00 00 00 14 00-81 17 C4 11 41 00 0C 00   A...........A...
1784:0400  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0410  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0420  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0430  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0440  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0450  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0460  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
-
Infatti l'indirizzo di ritorno è 000C, che è uguale alla word pushata che contiene il codice di colore rosso (0CH), e che viene posta in AX per poi essere passata in BX, passando in BL il codice di colore per la procedura Stampa.

Infatti non avevo tenuto conto, nel manipolare lo stack dalla procedura, che bisognava considerare l'indirizzo di ritorno messo in cima allo stack!
Mi meraviglio del fatto che il programma funzioni normalmente, ma forse ho capito perchè. Vado avanti:
-p

AX=000C  BX=000C  CX=0430  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=0014   NV UP EI PL NZ NA PO NC
1781:0014 8BD8          MOV     BX,AX
-d 1784:03f0
1784:03F0  41 00 0C 00 00 00 14 00-81 17 C4 11 41 00 0C 00   A...........A...
1784:0400  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0410  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0420  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0430  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0440  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0450  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0460  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
-p

AX=000C  BX=000C  CX=0430  DX=0000  SP=03FC  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=0016   NV UP EI PL NZ NA PO NC
1781:0016 58            POP     AX
-d 1784:03f0
1784:03F0  41 00 0C 00 00 00 16 00-81 17 C4 11 41 00 0C 00   A...........A...
1784:0400  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0410  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0420  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0430  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0440  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0450  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0460  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
-p

AX=0041  BX=000C  CX=0430  DX=0000  SP=03FE  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=0017   NV UP EI PL NZ NA PO NC
1781:0017 B409          MOV     AH,09
-d 1784:03f0
1784:03F0  41 00 0C 00 41 00 00 00-17 00 81 17 C4 11 0C 00   A...A...........
1784:0400  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0410  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0420  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0430  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0440  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0450  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0460  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
-
Il secondo POP AX mette in AX il valore della lettera 41H.
-p

AX=0941  BX=000C  CX=0430  DX=0000  SP=03FE  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=0019   NV UP EI PL NZ NA PO NC
1781:0019 B700          MOV     BH,00
-p

AX=0941  BX=000C  CX=0430  DX=0000  SP=03FE  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=001B   NV UP EI PL NZ NA PO NC
1781:001B B90100        MOV     CX,0001
-p

AX=0941  BX=000C  CX=0001  DX=0000  SP=03FE  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=001E   NV UP EI PL NZ NA PO NC
1781:001E CD10          INT     10
-p
A
AX=0941  BX=000C  CX=0001  DX=0000  SP=03FE  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=0020   NV UP EI PL NZ NA PO NC
1781:0020 C3            RET
-p
E già: ho erroneamente invertito l'ordine dei parametri immessi nello stack! Secondo errore, che ha compensato il primo! Infatti ho pushato prima il colore e dopo la lettera, mentre nella procedura ho poppato prima il colore e dopo la lettera, cosa che se non avessi avuto casualmente l'indirizzo di ritorno contenente esattamente il codice del colore nella parte bassa della WORD avrebbe generato una "stranezza".

Adesso RET riprende quello che nelle mie erronee intenzioni doveva essere il colore, e che coincide con l'indirizzo di ritorno per puro caso, e reindirizza IP nel modo giusto.
-p
A
AX=0941  BX=000C  CX=0001  DX=0000  SP=03FE  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=0020   NV UP EI PL NZ NA PO NC
1781:0020 C3            RET
-t

AX=0941  BX=000C  CX=0001  DX=0000  SP=0400  BP=0000  SI=0000  DI=0000
DS=1771  ES=1771  SS=1784  CS=1781  IP=000C   NV UP EI PL NZ NA PO NC
1781:000C E81200        CALL    0021
-d 1784:03f0
1784:03F0  41 00 0C 00 41 09 41 09-00 00 0C 00 81 17 C4 11   A...A.A.........
1784:0400  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0410  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0420  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0430  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0440  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0450  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
1784:0460  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
-
...pronto a chiamare la procedura KeyWait.

Simpatico, questo errore compensato da un altro errore!!!

Così, anziche poppare i parametri dallo stack, ho capito che devo usare istruzioni diverse per poterli richiamare dallo stack nel corpo della procedura, perchè per poppare i parametri non posso fare a meno di poppare anche l'indirizzo di ritorno, creando casini!