- Esporre funzioni "alla C" da VB (6 e .Net) - |
|||
COSA SERVE PER QUESTO TUTORIAL | |||
Download | Chiedi sul FORUM | Glossario | Una qualunque versione di Visual Studio | ||
Un metodo alternativo di interoperazione VB6-.Net | |||
INTRODUZIONE Obiettivo dell'articolo: export "alla C" da VB In questo articolo vedremo come è possibile far interoperare
VB6 con .Net senza
coinvolgere COM. Per ottenere questo risultato creeremo una libreria di classi
in VB.Net (una DLL) della quale alcune funzioni saranno esportate "alla C"
(ovvero come si usa fare in C attraverso exports.def). La prima parte del
tutorial introdurrà la tecnica utilizzata attraverso un semplice esempio, mentre
la seconda spiegherà come impostare un hook globale utilizzando solamente VB (6
e .Net): questo sarebbe normalmente impossibile senza una DLL esterna scritta in
C/C++, ma facendo un export C-like attraverso .Net lo faremo. Un semplice esempio di interoperazione tra VB6 e .Net: GetHashCode() Ogni buon programmatore .Net conosce il metodo
GetHashCode della
classe String: "restituisce il codice hash per questa stringa".
Supponiamo ora che, per qualche motivo, si necessiti di utilizzare
l'algoritmo di String.GetHashCode in VB6. Vediamo un modo intelligente
per farlo. Private Declare Function JustHash Lib "HashExporter.Net.dll" (ByVal str As String) As Long Private Sub btnHash_Click() txtHash.Text = JustHash(txtInput.Text) End Sub Chi ha famigliarità con VB6 avrà già notato l'istruzione Declare che si riferisce ad una semplice funzione in una DLL esterna realizzata in VB.Net (progetto HashExporter.Net): Public Function JustHash(ByVal str As String) As Integer Return str.GetHashCode() End Function Ovviamente questa sintassi non è sufficiente per realizzare un export
"alla C" (ovvero accessibile da VB6 attraverso l'istruzione Declare),
infatti normalmente non è possibile fare questo genere di export in
VB.Net, né in C# o in qualunque altro linguaggio .Net, eccetto ILAsm (IL
assembly language). Modificare il codice ILAsm del programma per creare un export. Dopo aver compilato il progetto HashExporter.Net, aprite il Visual Studio Command Prompt (Start menu, Programmi, Microsoft .NET Framework SDK, SDK Command Prompt), raggiungete la cartella che contiene HashExporter.Net.dll e digitate: ildasm /out:HashExporter.Net.il HashExporter.Net.dll Questo comando estrae il codice ILAsm dalla DLL compilata: con un "dir" potrete vedere che sono stati generati i file HashExporter.Net.il e HashExporter.Net.res. Aprite ora con un qualunque editor di testo (il Blocco Note andrà benissimo) il file HashExporter.Net.il. La prima cosa da fare è individuare la linea che comincia con ".corflags" e quindi rimpiazzarla con ".corflags 0x00000002" (in genere il valore iniziale è 0x00000001): questo significa che il codice funzionerà solamente sotto win32. È inoltre necessario aggiungere dopo questa linea quanto segue: .vtfixup [1] int32 fromunmanaged at VT_01 .data VT_01 = int32(0) Questo funzionerebbe nel nostro caso perché necessitiamo di esportare una funziona sola, ma naturalmente è possibile eseguire quanti export si desidera: .vtfixup [1] int32 fromunmanaged at VT_01 .vtfixup [1] int32 fromunmanaged at VT_02 .vtfixup [1] int32 fromunmanaged at VT_03 .data VT_01 = int32(0) .data VT_02 = int32(0) .data VT_03 = int32(0) A questo punto individuate la dichiarazione della classe che contiene il metodo da esportare (es. ".class private auto ansi sealed HashExporter.Net.mdlHashExporter"), all'interno del blocco della classe cercate ".method public static int32 JustHash(string str) cil managed", e appena dopo l'aperta parentesi graffa aggiungete il seguente codice: .vtentry 1:1 .export [1] as JustHash o più in generale: .vtentry #:1 .export [#] as exported_function_name Dove # indica il numero dell'export (il primo 1, il secondo 2 e così
via) e exported_function_name rappresenta il nome con cui la funzione
sarà esposta. ilasm "HashExporter.Net.il" /DLL /OUT:"HashExporter.Net.dll" /RESOURCE:"HashExporter.Net.res" Se avrete compiuto tutti i passaggi correttamente, aprendo con
Dependency Walker (un tool di Visual Studio) la nuova DLL (HashExporter.Net.dll)
vedrete sulla destra il nome della funzione esportata! Impostare hook globali o a thread di processi esterni da VB6 grazie a .Net Gli hook di Windows sono una sorta di metodi di
subclassing
che non si associa ad una singola finestra ma ad un intero thread o
addirittura all'intero sistema (inteso come desktop). Si tratta in
sostanza di un filtro dei messaggi di Windows che permette di modificare
o aggiungere funzionalità alla ricezione di un particolare messaggio.
Ecco la definizione che
MSDN riporta di hook: Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" _ (ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long Chi ha provato ad usare SetWindowsHookEx in VB6, molto probabilmente saprà quale limitazione questo linguaggio impone. Infatti MSDN dice: If the dwThreadId parameter is zero or specifies the identifier of a thread created by a different process, the lpfn parameter must point to a hook procedure in a dynamic-link library (DLL). Creando un hook bisogna passare a SetWindowsHookEx un puntatore ad
una funzione (parametro lpfn) che riceverà i messaggi filtrandoli. Come
ognuno saprà per supplire alla mancanza di puntatori VB6 fornisce
l'operatore AddressOf, ma esso risulterebbe utile solamente nel caso in
cui si stia tentando di impostare un hook su un thread del processo
corrente. Infatti come è scritto su MSDN se il thread specificato non
appartiene al processo chiamante oppure è 0 (indica quindi l'intero
sistema), il parametro lpfn deve riferirsi ad una funzione in una DLL
esterna, ma è noto a tutti che VB6 non può esporre funzioni "alla C".
Per questo motivo risulta impossibile creare un hook globale o associato
al thread di un altro processo. La DLL esterna Il progetto HookCExport.Net contiene una funzione chiamata HookWndProc che è definita esattamente come CallWndProc. Ripetiamo dunque su di essa il processo spiegato nella prima parte del tutorial:
Importante: c'è un altro fondamentale passaggio per evitare il crash del sistema. Se si imposta un hook globale la funzione che riceve i messaggi verrà richiamata moltissime volte al secondo e per questo HookWndProc deve essere molto veloce, altrimenti tutto si rallenterà fino al crash completo. Come noto le applicazioni .Net sono compilate alla prima esecuzione dal compilatore JIT, dunque anche la nostra DLL. Questo è molto pericoloso poiché, mentre la libreria di classi viene compilata, vengono eseguite decine o centinaia di chiamate alla stessa funziona al suo interno, portando alla paralisi del sistema. La soluzione è forzare la compilazione manualmente dal prompt dei comandi: ngen install "HashExporter.Net.dll"TORNIAMO A VB6 L'interfaccia VB6 con la DLL esterna Questo è il cuore del progetto VB6HookListener, che si trova nel modulo mdlHooking: 'Crea l'hook Public Sub Hook(hThread As Long) Dim hProc As Long 'Carica la DLL esterna hModule = Chk(LoadLibrary("HookCExport.Net.DLL")) 'Prende l'indirizzo della funzione di callback della DLL esterna hProc = Chk(GetProcAddress(hModule, "HookWndProc")) 'Imposta l'hook usando l'handle della DLL e il puntatore della funzione appena 'ottenuto hHook = Chk(SetWindowsHookEx(WH_CALLWNDPROC, hProc, hModule, hThread)) [...] End Sub Come abbiamo già detto, il puntatore a funzione passato a SetWindowsHookEx
deve essere in una DLL esterna, per questo abbiamo usato LoadLibrary e
GetProcAddress
che rispettivamente caricano la libreria in memoria e prendono un
puntatore alla funzione specificata (nel nostro caso HookWndProc). A
questo punto è lecito chiedersi perché non abbiamo utilizzato una
semplice istruzione Declare, la risposta è semplice: in primo luogo non
è possibile utilizzare AddressOf su una funzione dichiarata con
Declare, inoltre non sarebbe possibile ottenere l'handle (l'indirizzo)
del modulo caricato, che è richiesto da SetWindowsHookEx. Cosa abbiamo ottenuto In questo articolo abbiamo visto come fare uso delle potenzialità di
.Net da VB6 per sfruttare tecniche altrimenti inutilizzabili, senza
pesanti wrapper COM, ma solamente esponendo alcuni metodi in
maniera C-like attraverso VB .Net e ILAsm. Questa tecnica è davvero
utile ogni volta che si necessita di usare le possibilità a basso
livello di .Net (proprio come impostare hook globali).
Non sarà mai più necessario chiedere aiuto a papà-C! |
|||
<< INDIETRO | by VeNoM00 |