Microsoft DirectX 9.0 SDK Update (Summer 2003) |
This tutorial shows how to use the IDirectPlay8ThreadPool interface and avoid multithreading by taking advantage of the IDirectPlay8ThreadPool::DoWork method.
Some Microsoft® DirectPlay® games choose to use DirectPlay's internal worker threads to send and receive messages. These games must implement synchronization mechanisms to avoid data corruption and deadlocking.
Because multithreading is a complex issue, DirectPlay has implemented the IDirectPlay8ThreadPool interface to help users managed their threads. One advantage to using the IDirectPlay8ThreadPool interface is that you can use the IDirectPlay8ThreadPool::SetThreadCount method to disable all of DirectPlay's internal threads. All you need to do is call the IDirectPlay8ThreadPool::DoWork method regularly in your game loop and DirectPlay will perform networking tasks only during the time specified and only on the calling thread.
This tutorial uses a peer-to-peer, lobbied application, so you should review the following tutorials before beginning this one.
The complete sample code for this tutorial is included with the Microsoft DirectX® software development kit (SDK) and can be found at (SDK root)\Samples\C++\DirectPlay\Tutorials\Tut10_ThreadPool.
This article contains the following sections.
When you run this tutorial sample, a window opens and you have the choice to either Host or Connect.
If you choose Host:
If you choose Connect:
You can run this sample twice—once to host a session and once to connect. When connecting, enter your computer's IP address. When connected, you can draw pictures on the canvas shared between the host and the client application. To test host migration, close the host application. The client application's session status will change to 'Hosting Session "YourSessionName".'.
The first step in using the DirectPlay thread pool is to create and initialize an IDirectPlay8ThreadPool object. The IDirectPlay8ThreadPool object must be initialized before any other DirectPlay object or DirectPlay will create its own IDirectPlay8ThreadPool object when IDirectPlay8Peer is initialized. There can be only one IDirectPlay8ThreadPool object per process so if DirectPlay has created its own IDirectPlay8ThreadPool object then a subsequent call to IDirectPlay8ThreadPool::Initialize will return DPNERR_ALREADYINITIALIZED.
To create an IDirectPlay8ThreadPool object, call CoCreateInstance passing the class identifier (CLSID_DirectPlay8ThreadPool), the identifier of the interface (IID_IDirectPlay8ThreadPool), and the address of a pointer to an IDirectPlay8ThreadPool object. The following snippet from the tutorial sample shows how to create and initialize an IDirectPlay8ThreadPool object.
IDirectPlay8ThreadPool* g_pThreadPool; . . . // Create the IDirectPlay8ThreadPool interface hr = CoCreateInstance( CLSID_DirectPlay8ThreadPool, NULL, CLSCTX_INPROC_SERVER, IID_IDirectPlay8ThreadPool, (LPVOID*) &g_pThreadPool ); // Initialize ThreadPool hr = g_pThreadPool->Initialize(NULL, DirectPlayMessageHandler, 0 );
After initializing your DirectPlay objects, you should you call IDirectPlay8ThreadPool::SetThreadCount and set the thread count to zero. It is recommended that you do this before making any networking calls.
The following snippet from the tutorial sample shows how to set the thread count to zero.
// Turn off worker DirectPlay worker threads because you'll be using the // DoWork method to synchronously handle network messages. hr = g_pThreadPool->SetThreadCount( (DWORD) -1, 0, 0 ); // dwProcessorNum, dwNumThreads, dwFlags
The dwProcessorNum parameter is set to -1 to change the thread count for all processors.
After you set the thread count to zero, you must call IDirectPlay8ThreadPool::DoWork to perform any DirectPlay tasks. When you call IDirectPlay8ThreadPool::DoWork, you specify the amount of time that the application should spend on DirectPlay tasks. DirectPlay will handle starting and closing threads to perform various tasks such as receiving messages. If DirectPlay finishes all the networking tasks before the specified time, the method will return early. If there are any tasks left when the time runs out, IDirectPlay8ThreadPool::DoWork will return DPNSUCCESS_PENDING.
The correct way to use IDirectPlay8ThreadPool::DoWork is to call it within the game loop. IDirectPlay8ThreadPool::DoWork will block the application until it returns, so you should limit the time your program spends on DirectPlay tasks.
The following snippet from the tutorial sample shows how to use IDirectPlay8ThreadPool::DoWork.
// Handle incoming network data // Here you're setting the allowed timeslice at 100 milliseconds. // The program will block while DoWork handles network communication // so you don't need to worry about thread synchronization issues as // you have on earlier tutorials. g_pThreadPool->DoWork( 100, 0 ); // dwAllowedTimeSlice, dwFlags
If an IDirectPlay8ThreadPool object was successfully initialized, you should call IDirectPlay8ThreadPool::Close before terminating the application. You should call IDirectPlay8ThreadPool::Close after terminating other DirectPlay objects. Before IDirectPlay8ThreadPool::Close returns, all existing DirectPlay threads will terminate and you will receive a DPN_MSGID_DESTROY_THREAD message for each thread.
The following snippet from the tutorial sample shows how to use IDirectPlay8ThreadPool::Close.
if( g_pThreadPool ) g_pThreadPool->Close(0);