domenica 23 gennaio 2011

Avaya Devlink, Visual Basic 6.0 e VB.Net 2010

Premessa: non sono un programmatore :) ma ogni tanto mi capita di metter mano a codice sorgente...per necessità!
Necessità che oggi si è presentata: il nostro programmatore deve creare una componente software (servizio di Windows) che si occupa di ricevere dati da una centrale telefonica Avaya IPOffice, per poi elaborarli.
I dati in questione, ottenuti tramite una DLL chiamata DevLink (devlink.dll) proprietaria Avaya, sono relativi alle attività eseguite sulla centrale stessa (in breve, è uno stream di dati in tempo reale, che segnala l'arrivo di una chiamata, la risposta, il riaggancio ecc).

La dll, scritta in c++, e il relativo sdk, forniscono pochi ma "sufficienti" esempi. Esempi di utilizzo in c++ e in Delphi.
*Purtroppo*, noi dobbiamo utilizzarla con VB.Net :)
La DLL in questione si basa sul principio delle funzioni di callback: una volta inizializzata, è necessario chiamare una funzione DLOpen passandole vari parametri (ip del centralino, password), tra cui l'indirizzo di memoria di una funzione di callback da noi creata.
La dll richiamerà questa funzione di callback ogni volta che un dato sarà disponibile.
La callback dovrà esser pronta a ricevere vari dati, tra cui una semplice stringa di testo contenente le informazioni in questione.

Dopo due giorni di smanettamenti, il nostro programmatore è ancora in alto mare...allora decido di dargli una mano.

Non conosco VB.Net, ma ho programmato (per un breve periodo) in VB6...decido quindi di provare a integrare la dll in un ambiente conosciuto, per poi eventualmente convertire il codice.

La cosa non è affatto semplice: dopo diverse ricerche su google (e almeno una 50ina di tab aperti), scopro che per lavorare con una "unmanaged dll che utilizza funzioni callback" (che, perdonatemi, non so bene cosa voglia dire :P, ma ho capito che era il mio caso) con VB6, è estremamente arduo e spesso non porta a risultati.

Non mi perdo d'animo, e riesco a scrivere il seguente codice:

in un form (form1) con due pulsanti di test e una listbox di debug


------------------------------------------------------------------------------------------------------------------------------------


Private Sub Command1_Click() 
On Error GoTo h

Dim pbxaddress As String
Dim pbxpassword As String
Dim reserved1 As String
Dim reserver2 As String
Dim pbxhandle1 As Long
Dim valore As Long
Dim cb As Long
DoEvents
pbxaddress = "192.168.15.245"
pbxpassword = "password"
pbxhandle1 = 1
cb = GetProcAddress(AddressOf HandleCommsEvent)

DoEvents
valore = DLOpen(ByVal pbxhandle1, ByVal pbxaddress, ByVal pbxpassword, Nothing, Nothing, cb)
DoEvents

Do While True
DoEvents
Loop


Exit Sub

h:

MsgBox Err.Description


End Sub


Private Sub Command2_Click()
MsgBox DLClose(ByVal 1)
End Sub

Public Function GetProcAddress(ByVal lngAddressOf As Long) As Long
GetProcAddress = lngAddressOf
End Function

Private Sub Command3_Click()
On Error GoTo h

Dim pbxaddress As String
Dim pbxpassword As String
Dim reserved1 As String
Dim reserver2 As String
Dim pbxhandle1 As Long
Dim valore As Long
Dim cb As Long
DoEvents
cb2 = GetProcAddress(AddressOf HandleCallLogEvent)
pbxhandle1 = 1
DoEvents
valore = DLRegisterType2CallDeltas(ByVal pbxhandle1, cb2)
DoEvents
Do While True
DoEvents
Loop


Exit Sub

h:

MsgBox Err.Description
End Sub



------------------------------------------------------------------------------------------------------------------------------------


command1 apre la connessione
Command2 la chiude
command3 dice alla dll "inizia a mandarmi messaggi"



In un modulo .bas (non va bene nel codice di un form)
------------------------------------------------------------------------------------------------------------------------------------

Public Declare Function DLOpen Lib "C:\Program Files\Avaya\IP Office\DEV Link\devlink.dll" (ByVal pbxh As Long, pbx_address As Any, pbx_password As Any, reserved1 As Any, reserved2 As Any, ByVal cb As Long) As Long
Public Declare Function DLClose Lib "C:\Program Files\Avaya\IP Office\DEV Link\devlink.dll" (ByVal pbxh As Long) As Long
Public Declare Function DLRegisterType2CallDeltas Lib "C:\Program Files\Avaya\IP Office\DEV Link\devlink.dll" (ByVal pbxh As Long, ByVal cb As Long) As Long



Public Sub HandleCommsEvent(ByVal pbxh As Long, ByVal comms_evt As Long, ByVal parm1 As Long)
Form1.List1.AddItem comms_evt
End Sub

Public Sub HandleCallLogEvent(ByVal pbxh As Long, ByVal info As String)
Form1.List2.AddItem "- " + info
End Sub
------------------------------------------------------------------------------------------------------------------------------------


In particolare, ho dichiarato le 3 funzioni principali della devlink.dll e creato due callback function (una che ci comunica lo stato della connessione a ogni sua variazione, una che riceve gli eventi di chiamata)
In entrambi i casi, aggiungo il valore a una lista sul form1 (solo a scopo debug)


E' importante considerare che:

- La funzione di callback, va messa assolutamente in un modulo (standard, non di classe)
- Nella dichiarazione della DLL, i valori stringa (per c++), vanno dichiarati a Any, in quanto VB e C++ rappresentano le stringhe in modo diverso
- tutti i passaggi di parametri vanno fatti byval
- il passaggio dell'indirizzo di memoria delle funzioni di callback va fatto semplicemente passando "byval" il valore numerico dell'indirizzo di memoria della callback, ottenuto tramite la funzione "addressof"
- di conseguenza, nella dichiarazione della dll, il parametro della funzione di callback, andrà dichiarato di tipo long (ByVal cb As Long)


Il codice così descritto funziona perfettamente in VB6.
Altre difficoltà sono state però incontrate nella trasposizione in VB.NET 2010. Scriverò un po' di dettagli nei prossimi giorni, ma in particolare:

- Non si può usare AddressOf, ma è necessario usare i delegati
- i Long di VB6, corrispondono agli Integer di VB.Net 2010 (che sono a 32 bit, mentre i long di VB2010 sono a 64 bit)

Se qualcuno riscontrasse difficoltà nell'implementazione della dll in codice VB6 o VB.Net, faccia un fischio in mail ;)

Ciao!!
  

1 commento: