Consuming Win32 CALLBACK Functions in C#

How to call native callback functions from .Net



Nella prima parte di questo tutorial abbiamo visto come creare ed usare funzioni callback in C/C++ in Win32.

Ora vedremo come utilizzare le stesse in .Net; per far ciò proseguiremo il lavoro lasciato nella parte 1, aggiungendo alla soluzione Visual Studio un progetto (chiamiamolo CSharpCallbackDemo) Visual C# → Windows Forms.

Settiamo il progetto .Net come progetto StartUp.

Conclusi i passaggi preliminari, focalizziamo una questione fondamentale: dobbiamo far comunicare una funzione CALLBACK unmanaged con un client .Net (C#) managed.
Ovvero:

Nota: per un'introduzione alle funzioni callback vedere parte 1 di questo post.

A differenza del metodo CALLBACK unmanaged (DLL → EXE) tradizonale che usa puntatori a funzione, nel caso DLL unmanaged → .Net ciò non è possibile.
Nell'universo .Net però esiste qualcosa di simile, i delegates.
Un delegate infatti è un tipo definito dall'utente e rappresenta una dichiarazione di un metodo (ed i suoi parametri se esistono).

Esempio della dichiarazione di una funzione CALLBACK unmanaged:

/*
CALLBACK function declaration

This function will be implemented by a caller process
*/
typedef void(CALLBACK *SendNotification)(LPNOTIFY_ARGS arg);


Esempio della dichiarazione di un delegate .Net:


public delegate void SendNotification(LPNOTIFY_ARGS arg);


Come si può vedere le due dichiarazioni sono molto simili.
In entrambi i casi l'argomento della funzione è di tipo LPNOTIFY_ARGS che nel sorgente C/C++ però non è una semplice struttura ma un puntatore ad una struttura (vedi parte 1).

In C# non abbiamo puntatori a funzione come nel C/C++, tuttavia nel sorgente C# dovremo in qualche modo dichiarare LPNOTIFY_ARGS ovviamente, altrimenti la compilazione dell'assembly fallirà.

Nota: se avete in mente di dichiarare LPNOTIFY_ARGS una struct (lato .Net)... cambiate subito idea, andreste incontro a grossi problemi di allocazione della memoria a runtime. Ciò è dovuto al fatto che una struttura è un value type mentre noi nella dll unmanaged abbiamo un puntatore ad una struttura.
Se dichiarassimo LPNOTIFY_ARGS una struct, questa sarebbe allocata nello stack (.Net), un'area di memoria inaccessibile per la DLL unmanaged.
Abbiamo invece necessità di un tipo di variabile che rimanga consistente durante lo scambio di dati tra processi e che possa accedere ad un certo indirizzo di memoria, in altre parole ci serve un reference type.
Quest'ultimo tipo di variabile alloca un'area di memoria nello heap, l'indirizzo di una variabile allocata nello heap può essere passato alla DLL unmanaged.
Per far ciò sarà sufficiente dichiarare LPNOTIFY_ARGS class anzichè struct.
Ultima cosa: dato che l'argomento della funzione callback .Net ( LPNOTIFY_ARGS ) non è un puntatore, eliminiamo il prefisso LP (long pointer).


Dichiariamo la class NOTIFY_ARGS

 
//class NOTIFY_ARGS declaration

 [StructLayout(LayoutKind.Sequential)]
        public class NOTIFY_ARGS
        {
            [MarshalAs(UnmanagedType.LPWStr)]
            public string Message;
            public bool Flag;
            public UInt32 Cookie;
        };

Dichiariamo il metodo delegate


//The delegate method works as C/C++ function pointer

 public delegate void SendNotification(NOTIFY_ARGS arg);

Dichiariamo il metodo della funzione CALLBACK della DLL


//Import DLL unmanaged function
 
[DllImport("CallbackDemo.dll", CallingConvention = CallingConvention.Cdecl)]
 public static extern void CallMe(SendNotification n, NOTIFY_ARGS InArgs);


Implementiamo il metodo CALLBACK che verrà chiamato dalla DLL unmanaged


//This method will be called by unmanaged DLL

 public void CallCallMe(NOTIFY_ARGS InArgs)
        {

            string lpszMessage = InArgs.Message.ToString();
            bool bFlag         = InArgs.Flag;
            UInt32 dwCookie    = InArgs.Cookie;

            TextBoxText = lpszMessage;
            CheckBoxChecked = bFlag;
        }


Nella Form dell'applicazione .Net aggiungiamo i controlli :

Win32 DLL CALLBACK in C#



Implementiamo il buttonclick del button callback (vedi immagine qui sopra)



private void button1_Click(object sender, EventArgs e)
        {

            NOTIFY_ARGS args = new NOTIFY_ARGS();

            args.Message = "Send this message to DLL";
            args.Flag = Convert.ToBoolean(this.checkBox1.CheckState);
            args.Cookie = 0;

            SendNotification _send = new SendNotification(CallCallMe);
            
            //Here we call the unmanaged DLL function
            CallMe(_send, args);

            textBox1.Text = TextBoxText;
            checkBox1.Checked = CheckBoxChecked;


        }




        private void button2_Click(object sender, EventArgs e)
        {
            Close();
        }




Mandiamo in run l'applicazione .Net, clicchiamo sul button Callback una prima volta per visualizzare il 1° messaggio dalla DLL unmanaged.
Clicchiamo una seconda volta per visualizzare il 2° messaggio.

Nota: all'evento button1_Click viene definita una nuova istanza della classe NOTIFY_ARGS e del delegate SendNotification.
Quindi viene chiamata la funzione callback CallMe della DLL unmanaged.
La funzione callback della DLL riceve in input la variabile args, valuta valori assegnati ai membri di essa, li setta al loro opposto (es: se il membro Flag è false diviene true) e a sua volta chiama la funzione callback .Net CallCallMe, la quale assegna i valori ricevuti ai controlli della Form.

Win32 DLL CALLBACK in C# Win32 DLL CALLBACK in C# 1° Message Win32 DLL CALLBACK in C# 2° Message



Giuseppe Pischedda 2018




Processing request, please wait...


download C# CALLBACK Demo Source  (zip) 


download C# CALLBACK Demo bin  (zip) 






Se il post ti è utile puoi fare una donazione all'autore, l'importo è a tua libera scelta.

Grazie