Using Callbacks

team lib

Sometimes a DLL function will take a function pointer as one of its arguments; the DLL function can then use the pointer to call back to the client function, thus providing two-way communication. This is a common technique for implementing mechanisms such as event handling and filtering.

More Info 

Function pointers are used in C and C++ code. A function pointer is the address of a function, and the function can be executed through the pointer. Function pointers are typed, so only addresses of functions with the correct argument list and return type can be assigned to the pointer.

The short C++ program in Listing 13-3 shows a callback in action. You can find this sample in the Chapter13\CppCallback folder of the book's companion content.

Listing 13-3: CppCallback.cpp
start example
 //Excluderarely-usedstufffromWindowsheaders #defineWIN32_LEAN_AND_MEAN #include<windows.h> #include<iostream> usingnamespacestd; //Theuser-definedcallbackfunction BOOLCALLBACKEWSProc(LPTSTRname,LPARAMlp) { cout<< "EWSProc: " <<name<<endl; returnTRUE;//continueenumeration } intmain(intargc,char*argv[]) { //Enumeratewindowstations BOOLbOK=EnumWindowStations(EWSProc,(LPARAM)0); cout<< "Enum " <<((bOK)? "OK" : "failed")<<endl;    return0; } 
end example
 

The EnumWindowStations API is used to enumerate the window stations present on a Windows NT, Windows 2000, or Windows XP system. It is typically used to search the list of window stations, looking for one with a particular name.

More Info 

A window station is a securable object associated with a process, which contains a clipboard, an atom table, and a set of desktop objects. There are three common desktops: the default desktop, the login desktop, and the screen-saver desktop. The default desktop has access to the screen and is used for normal communication with the user. The login desktop is displayed if you press Ctrl+Alt+Del, and the screen-saver desktop is used to run screen savers. The window station for the interactive user is always named Winsta0.

The API takes a pointer to a callback function as its first argument and a user-defined parameter as its second; this second parameter can be anything, but it will often be a string representing the name of the window station that is being looked for. The callback function is called once for each window station; it is passed the name of a window station and the user-defined parameter that was passed to EnumWindowStations . If the enumeration is to continue, the callback should return TRUE; if no more enumeration is needed, the function should return FALSE . Running the program should give output similar to the following, although the number of window stations enumerated-and their names -will vary:

 EWSProc:WinSta0 EWSProc:Service-0x0-3e7$ EWSProc:Service-0x0-3e4$ EWSProc:Service-0x0-3e5$ EWSProc:SAWinSta EWSProc:__X78B95_89_IW EnumOK 

Although function pointers are peculiar to C and C++ and can't be used directly from other languages, DLL functions that use callbacks can be called from any .NET language using delegates . If you haven't met delegates before, you will find a brief description in the following section.

Introduction to Delegates

A delegate is the .NET equivalent of a C/C++ function pointer. Delegates have several advantages over function pointers, the main one being that they are language independent, whereas function pointers can be used only from C or C++ code.

In simple terms, a delegate is an object that can call a function for you. Delegates are perhaps most easily explained through an example, so here is a Visual Basic .NET program that shows how to create and use a delegate:

 ModuleModule1 'Defineadelegatetocallatrigfunction PublicDelegateFunctionTrigDelegate(_ ByValangleInRadiansAsDouble)AsDouble 'Atrigfunction PublicFunctionSin(ByValangleInRadiansAsDouble)AsDouble ReturnMath.Sin(angleInRadians) EndFunction SubMain() 'CreateadelegateboundtotheSinfunction DimdelAsNewTrigDelegate(AddressOfSin) DimdAsDouble=del(1.0) Console.WriteLine("Sinof1.0radiansis{0}",d) EndSub EndModule 

The delegate definition is introduced with the Delegate keyword; apart from that, it is a normal function definition. The arguments and return value of the delegate define which functions it will be able to call: in this case, the delegate can call any function that takes a double as an argument and returns a double .

The delegate is followed by the definition of a trigonometric function that returns the sine of an angle. Notice how the signature of this function matches that of the delegate. To use the delegate, create a new TrigDelegate object and pass in the address of the Sin function. This binds the function to the delegate and ensures that when the delegate is executed, the Sin function will get called. You can now use the delegate in exactly the same way as you would the Sin function; the delegate calls the Sin function for you, passing in the argument and returning the result.

One characteristic of delegates that distinguishes them from function pointers is that a delegate object can be bound to more than one method at once. On the next page is an example in Visual C# that shows this.

 usingSystem; namespaceCsDelegate { classClass1 { //Delegatedefinition publicdelegatedoubleTrigDelegate(doubleangleInRadians); //Twotrigonometryfunctions publicstaticdoubleSin(doubleangleInRadians){ doubled=Math.Sin(angleInRadians); Console.WriteLine("Sinof1.0radiansis{0}",d); returnd; } publicstaticdoubleTan(doubleangleInRadians){ doubled=Math.Tan(angleInRadians); Console.WriteLine("Tanof1.0radiansis{0}",d); returnd; } [STAThread] staticvoidMain(string[]args) { //CreatedelegatestocallSinandTanfunctions TrigDelegatedelTrig1=newTrigDelegate(Class1.Sin); TrigDelegatedelTrig2=newTrigDelegate(Class1.Tan); //Combinethetwodelegates delTrig1+=delTrig2; //Executethedelegate doubled=delTrig1(1.0); } } } 

When the delTrig1 delegate is executed, the Sin and Tan methods will both be called, and they will be called in the order in which the delegates were added together.

Using Delegates for Callbacks

Now that you've seen what delegates are and how they work, let's look at how you'd use them to implement callbacks. When calling a function that uses callbacks from unmanaged C++, you supply the name of the function as an argument; in managed code, you simply supply the name of a delegate instance.

The Visual C# program in Listing 13-4 shows how you can provide a callback function using delegates. The application performs the same task as the C++ EnumWindowStations example in the previous section, so you can compare how the two approaches work. You can find this sample in the Chapter13\Delegates folder of the book's companion content.

Listing 13-4: The Class1.cs file from the Delegates sample
start example
 usingSystem; usingSystem.Runtime.InteropServices; namespaceDelegates { classClass1 { //Delegateusedtorepresentcallbackfunction publicdelegateInt32EnumWinStaDelegate([MarshalAs(UnmanagedType.LPTStr)]stringname, IntPtrlparam); //PlatformInvokeprototypeforEnumWindowStations [DllImport("user32.dll",CharSet=CharSet.Auto)] publicstaticexternInt32EnumWindowStations(EnumWinStaDelegatecallbackfunc,IntPtrlparam); //Delegateinstance staticEnumWinStaDelegatedel; //Thecallbackfunctionthatisboundtothedelegate staticInt32Callback(stringname,IntPtrlparam){ //GetthestringbackfromtheIntPtr stringsp=Marshal.PtrToStringAuto(lparam); if(name.Equals(sp)) Console.WriteLine("FoundrequiredWinStation:{0}",name); else Console.WriteLine("WinStation:{0}",name); return1;//non-zeroequatestotrue } [STAThread] staticvoidMain(string[]args) //Createthedelegate,andbindittothecallback //function del=newEnumWinStaDelegate(Callback); //Callthefunction,passinginthenameofthe //WinStationwewanttolookfor strings= "WinSta0"; IntPtrip=Marshal.StringToHGlobalAuto(s); Int32result=EnumWindowStations(del,ip); if(result!=0) Console.WriteLine("EnumOK"); else Console.WriteLine("Enumfailed"); //Freethememoryusedtomarshalthestring Marshal.FreeHGlobal(ip); } } } 
end example
 

The delegate EnumWinStaDelegate is defined to represent the callback function. Looking at the C prototype for the function, you can see that the arguments are an LPTSTR and an LPARAM . A string can be used for the first argument, with an appropriate MarshalAs attribute to control how it is marshaled. An LPARAM is simply a pointer, so it can be represented by an IntPtr . The Platform Invoke prototype for the EnumWindowStations function is simple; you will need to specify CharSet.Auto so that the correct character mappings are used for the call and callback.

A delegate object is created at the start of the main program and bound to the callback function; this is equivalent to assigning an address to a function pointer in C++. It is then simply a matter of calling the EnumWindowStations function, passing in the name of the delegate, and passing in the name of the user-defined parameter cast to an IntPtr . To pass a string as the second parameter, the string needs to be marshaled to a type acceptable to unmanaged code. Marshal.StringToHGlobalAuto allocates memory from the Windows heap using GlobalAlloc , and then copies the content of the string into unmanaged memory, returning an IntPtr that represents the allocated memory. The callback function takes the IntPtr and uses Marshal.PtrToStringAuto to marshal the unmanaged data back into a string again. This string can then be compared with the one passed in as the first parameter to see whether the named window station can be found.

 
team lib


COM Programming with Microsoft .NET
COM Programming with Microsoft .NET
ISBN: 0735618755
EAN: 2147483647
Year: 2006
Pages: 140

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net