Explaining Delegates and the AddressOf operator is a bit tough... So, let’s start at the beginning.
What are they used for?
Delegates are used primarily with API functions as a way to "customize" an API to do something that the author of the API couldn't possibly do in advance. Let me explain using a simple analogy... Let's say that I wrote a program (an API-style DLL) that waited for an alarm to go off and then did something. Since I'm writing this program for others to use, I can't possibility know what you might want to do when the alarm goes off. So, I write my program to use a delegate. That way others who use my program can just "plug in" anything they want into my program. For example, if you wanted to ring a bell when the alarm goes off, you could write a delegate called "RingBell" and plug it into my program. This technique makes it easy for an API programmer, since he doesn't have to predict in advance the actual use of the API by others.
Note: There are other uses for delegates that have nothing to do with APIs, but the concepts are the same as this example.
OK, but why would I ever use 'em?
You probably would never deliberately write a program that required the use of a delegate... in fact, I'd say that the only time you'd ever use a delegate is when you're using somebody else's code (an API-style DLL).
How do I make it work?
Well, there are four steps to the process.
- The 1st step is kinda simple... it's just a single line of code using the "Delegate" keyword. This declaration looks a lot like a normal function, but without a "body" and no "End Function". This tells the compiler to create a “signature” that will be used like a "type" in the declaration of an API function. The signature consists of the number and types of the parameters, plus their order. Typically, you get the requirements for the signature from the API documentation (you don't just make 'em up).
- The 2nd step is where you use the delegate as a "type" inside the declaration of an API function. One of the parameters to the API function will use the delegate you created in Step 1. In the API documentation, the parameter we're after will be called a "function pointer", perhaps with an API type of WNDENUMPROC (or something similar).
- The 3rd step is where you write the actual function that will be called (when the alarm goes off). It must match the "signature" of the delegate you created in Step 1. This is where you write the code that does the work (rings a bell, in my silly analogy).
- The 4th step is the "AddressOf" operator that used when your application calls the API. This is where you plug your function into the API to customize it. You have to pass "the address of" the function you created in Step 3 to the API function.
How about a real life example
Here is an example that returns the "Windows Text" (typically the title bar) of every running application. It also includes the text of all of the buttons and controls on each of those windows.
Step 1: The Delegate keyword is used to create a "signature" that will later be used like as a "type" in the declaration of an API function. Take a look at the following:
Private Delegate Function EnumCallBackDelegate( _ ByVal hwnd As IntPtr, _ ByVal lParam As Integer _ ) As Boolean
...this line of code doesn't do anything... it's just creates a "signature" (that's made up of the number and type of parameters, plus their order).
Step 2: OK, now it's time to use that signature... it's time to declare the EnumWindows API function.
Private Declare Function EnumWindows Lib "user32" ( _ ByVal lpEnumFunc As EnumCallBackDelegate, _ ByVal lParam As Integer _ ) As Boolean
...notice the 1st parameter of the API is "ByVal lpEnumFunc As EnumCallBackDelegate"? See how the delegate is used as a "type" (like "As Integer")
OK, we're done with the Delegate for rest of the program! Yep, after you've used it inside the API declaration, you'll never need it again.
Step 3: So, now it's time to create a function that you intend to use with the EnumWindowsAPI function.
Recall that this function *must* match the signature that you got from the API documentation (and used in the Delegate line). Obviously, the name of the function doesn't matter... you can call it anything you want. But in this example, the first parameter *must* be an IntPtr, and the second parameter *must* be an Integer. So, consider the following:
Private Function EnumCallBack(ByVal hwnd As IntPtr, ByVal lParam As Integer) As Boolean Dim TextSize, ret As Integer Dim WinText As String ' get the size of the text in the window TextSize = GetWindowTextLength(hwnd) If TextSize > 0 Then ' create a string big enough WinText = Space(TextSize + 1) ' get the windows text ret = GetWindowText(hwnd, WinText, TextSize + 1) If ret > 0 Then ' store this Window Text sb.AppendLine(WinText.Substring(0, ret)) End If End If ' The API documentation says to return True to continue with the ' enumeration of windows Return True End Function
... this is the part where you "fill in the blanks".... to customize the API to do whatever-it-is you want it to do. In this example, it records the Windows Text
Step 4: The last step is where you plug your function into the API using the AddressOf keyword. In this example, the GetWinText function uses the EnumWIndows API to collect all of the windows text and it then returns the text as a single string. Take a look at the following:
Public Function GetWinText() As String sb = New StringBuilder EnumWindows(AddressOf EnumCallBack, 0) Return sb.ToString End Function
...notice the 1st parameter to the EnumWindows API is "AddressOf EnumCallback". EnumCallback is the name of the function we created in Step 3 (not the name of the Delegate). The compiler will check to see that the signature of the EnumCallback function exactly matches the signature of the delegate used when we declared the API function.
Download/Links
Download the VB.Net 2005 Source code example used in this article:
DelegateDemo.zip
Download a related VB.Net 2005 Source code example:
FileSystemWatcherDemo.zip