The majority of actions you perform in a DirectPlay session happen asynchronously. The method will return almost instantly, and then DirectPlay will go to work doing the action you requested. After this action has been completed, or when there is something DirectPlay needs to inform the application about, an event will be fired on a separate thread. These events are how we find out everything we need to know. In our preceding call to, the method will return right away, but behind the scenes, DirectPlay is busy looking for any available sessions. Whenever one is found the event is fired. Since we want to know about the hosts we find, we should hook this event. Add the following line directly after you've created your new peer object in the InitializeDirectPlay method: connection.FindHostResponse += new FindHostResponseEventHandler(OnFindHost); You'll also need to add the actual method that will be used to handle this event. You will find this in Listing 18.2. Listing 18.2 Event Handler for a Found Hostprivate void OnFindHost(object sender, FindHostResponseEventArgs e) { lock(this) { // Do nothing if we're connected already if (connected) return; connected = true; string foundSession = string.Format ("Found session ({0}), trying to connect.", e.Message.ApplicationDescription.SessionName); this.BeginInvoke(new AddTextCallback(AddText), new object[] { foundSession }); // Connect to the first one ((Peer)sender).Connect(e.Message.ApplicationDescription, e.Message.AddressSender, e.Message.AddressDevice, null, ConnectFlags.OkToQueryForAddressing); } } As you can see, our event handler includes a FindHostsReponseEventArgs object that will include the data about the host that was found. Examining the rest of this method tells us a lot about how multi-threaded programming should happen. First, it's entirely possible for two different hosts to be found by DirectPlay. It's also possible that your application could be notified about each on two separate threads at the exact same time. Having two separate threads manipulating the same data at the same time is a big no-no, so we want to avoid that. We do this by using the lock keyword from C# (which is really just Monitor.Enter and Monitor.Exit calls wrapped in a try/finally block). Once the lock block has been entered by one thread, no other thread can enter that block until the first one has left the block. We can only connect to one host at a time, so we will use a Boolean variable to check if we are already connected (you will need to declare this variable in your class; feel free to declare it publicly). If we are, there's nothing more to do in this method; we can just return. If we aren't, however, the first thing we will do is update this variable so that new enumerations won't interrupt us while we really do try to connect. Now we want to update our UI to let the user know that we have found a session, and we are going to try to connect to it now. However, this event has been fired on a thread that is not the main UI thread where the control was created. It is not legal to modify or update controls on any thread other than the thread on which it was created. Doing so can result in failures, or even worse, deadlocks. The BeginInvoke method we use will call any delegate with the set of parameters we specify on the thread where the control was created for us. BeginInvoke happens asynchronously, much like DirectPlay. There is also an Invoke method that takes the same parameters, but runs synchronously. The definition for our delegate would be private delegate void AddTextCallback(string text); Nothing special, and we'll only use it to call our already existing AddText method. It is never a good idea to let a DirectPlay thread be blocked (for example, by using the synchronous Invoke method, or by displaying a modal message box). Always try to get the data you need and let the event end as quickly as possible. Finally, we are ready to try and connect to this session. The connect call has four overloads, and once again, we used the simplest of them to get our connection started. You'll notice that we use the application description we received from the FindHostsEventArgs object to pass in to the Connect call. Actually, the majority of parameters we pass in to this method are retrieved from this object: the hosts address (the "sender" of the find hosts response), as well as the local address. The last two parameters we pass in are application defined. The first is any user-defined data you want to pass into the connect call. This could be data that the host uses to determine which "team" you are on, for example; it is completely up to the application. The last parameter allows DirectPlay to query for addressing information, which probably isn't necessary since we are receiving full addressing information from the callback. The Sync flag is also allowed here to change the default behavior of the call to behave synchronously. Other overloads of the Connect method contain one or more parameters we didn't need to use in this instance. One is an out parameter that is the asynchronous handle of the call. In DirectPlay, all calls that run asynchronously can return a handle to the call. You can use this handle to cancel the operation via the CancelAsyncOperation method if needed. We don't plan on canceling our operations, so this isn't necessary. The last two possible parameters are both user-defined context variables. Context variables can be used to store application-specific data.
|