Author : Sobeit Void
Page : << Previous 2 Next >>
to connect to. Anyway, we store those in a global below:
BOOL gameServer; // flag for client/server
char player_name[10]; // local player name, limit it to 10 chars
char session_name[10]; // name of session, also limit to 10 chars
char tcp_address[15]; // tcp ip address to connect to
I'm sorry if you are firmly against globals. Feel free encapsulate them, but I think globals simplify the learning process here. Also I do not do much error checking here; theoretically you should test the result of every function call.
Now before we move on, we should implement a list of players in the current session. Although it will not be necessary here, you will need it in larger applications.
You create a list with the following item element:
Class DP_PLAYER_LIST_ELEM{
DPID dpid; // the directplay id of player
char name[10]; // name of player
DWORD flags; // directplay player flags
// any other info you might need
};
You need to implement a list class that adds a player and deletes a player with a specific dpid. Do not reference the players by their names because players can have the same name. Use the id to differentiate players. Due to space constraints, I will not include any code here. Alternatively you can use arrays to hold the global player information, but this is not scalable and more troublesome.
Then we define a global pointer to the class:
DP_PLAYER_LIST *dp_player_list; // list of players in current session
That is about all the globals you need. You should also create a local player struct for additional information for local players only.
Setting up Connection
Initialization
To set up the connection, we will write a function that takes a TCP/IP string and create a TCP/IP connection. This is also where the lobby interface comes in.
Side note: Although DirectPlay has a method for enumerating the connections available, the method will enumerate all connections even if they are not available. So if you do not have an IPX connection, the enumeration will return an IPX option to connect and the connection would fail only when the user tries to make the connection. This seems redundant to me so I recommend you skip this part and give the options to the user straight, failing only when the user tries to make the connection.
In case you don't know about enumeration, we will talk more about it later. Enumerating things in DirectX is for the user to provide a function that is called (repeatedly) by a process in DirectX. This is known as a callback function.
Here goes the function. Remember you should do error checking for every function call.
int Create_TCP_Connection(char *IP_address)
{
LPDIRECTPLAYLOBBYA old_lpdplobbyA = NULL; // old lobby pointer
DPCOMPOUNDADDRESSELEMENT Address[2]; // to create compound addr
DWORD AddressSize = 0; // size of compound address
LPVOID lpConnection= NULL; // pointer to make connection
CoInitialize(NULL); // registering COM
// creating directplay object
if ( CoCreateInstance(CLSID_DirectPlay, NULL, CLSCTX_INPROC_SERVER,
IID_IDirectPlay3A,(LPVOID*)&lpdp ) != S_OK)
{
// return a messagebox error
CoUnintialize(); // unregister the comp
return(0);
}
// creating lobby object
DirectPlayLobbyCreate(NULL, &old_lpdplobbyA, NULL, NULL, 0);
// get new interface of lobby
old_lpdplobbyA->QueryInterface(IID_LPDIRECTPLAYLOBBY2A, (LPVOID *)&lpdplobby));
old_lpdplobbyA->Release(); // release old interface since we have new one
// fill in data for address
Address[0].guidDataType = DPAID_ServiceProvider;
Address[0].dwDataSize = sizeof(GUID);
Address[0].lpData = (LPVOID)&DPSPGUID_TCPIP; // TCP ID
Address[1].guidDataType = DPAID_INet;
Address[1].dwDataSize = lstrlen(IP_address)+1;
Address[1].lpData = IP_address;
// get size to create address
// this method will return DPERR_BUFFERTOOSMALL – not an error
lpdplobby->CreateCompoundAddress(Address, 2, NULL, &Address_Size);
lpConnection = GlobalAllocPtr(GHND, AddressSize); // allocating mem
// now creating the address
lpdplobby->CreateCompoundAddress(Address, 2, lpConnection, &Address_Size);
// initialize the tcp connection
lpdp->InitializeConnection(lpConnection, 0);
GlobalFreePtr(lpConnection); // free allocated memory
return(1); // success
} // end int Create_TCP_Connection(..)
First we initialize COM to increment its count by 1 and we create the DirectPlay object using CoCreateInstance. This is another method of creating DirectX objects, which is actually the method the wrapper function uses. We have to pass in the class identifier and such but the main thing to note is the IID_DirectPlay3A parameter. This is the identifier of the IDirectPlay3A interface. So if you want to get an IDirectPlay2A interface, set the parameter to IID_DirectPlay2A. Similarly if you want an IDirectPlay4A interface.
Then we create the DirectPlay lobby object and we query for the 2A version. Since there is a macro DirectPlayLobbyCreate, we do not need to initalize COM like above. Underneath, this function does the same (COM and CoCreateInstance) except it gets the lowest interface, ie IDirectPlayLobbyA. So we need to get the 2A version, which is done by querying it with the interface identifier (note you query all DirectX objects in the same manner). Then we close the old lobby since we have a new one.
Next we create an address that holds the information about the TCP connection. Since we did not use EnumConnections, we need to build that information ourselves. You can use the information from the enumeration and jump straight to initialize but I prefer to reduce callback functions to a minimum. We set the fields of the address structure and we set the type to the TCP/IP service provider id. This is from defined in dxguid.lib or the including INITGUID above. We set the second element to the address string. You can get all these information from the MSDN, and which parameters to pass to create other types of connection.
Then we get the size of buffer required for the connection by passing in a NULL parameter. The size required will be stored in the variable Address_Size. Note this method will return DPERR_BUFFERTOOSMALL since we are getting the size. Do not interpret this as an error condition. We then allocate memory from the system heap using GlobalAllocPtr, rather than using malloc. We then create the address information by passing the allocated buffer to the function again. Using that we call the DirectPlay Initialize method and we would have created a TCP/IP connection. If InitalizeConnection returns DPERR_UNAVAILABLE, it means the computer cannot make such a connection and it's time to inform the user no such protocol exists on the computer.
People may wonder why I chose to do things the hard way when I could have used DirectPlayCreate and be happy. Well, the main thing is I want to override the default dialog boxes that would pop up to ask the user to enter the information. You don't see that in StarCraft, do you? (Sorry, love Blizzard)
Do note that you should test every function call and return the appropriate error. I do not do that here because of space and also I'm lazy. Also as for how this function works, you pass "" as the string if you are the hosting the session, else you pass the IP of the machine you are connecting to. So in the "Getting user information", you should have enough information to call this function like:
if (gameServer) // if host machine
Create_TCP_Connection(""); // empty string is enough
else
Create_TCP_Connection(tcp_address); // passed the global ip from user
If you think this is the end of connecting, think again. Remember, we need to have a session and a player before we send messages. But before that let's close the connection.
int DirectPlay_Shutdown()
{
if (lpdp) // if connection already up, so it won't be null
{
if (lpdplobby)
lpdplobby->Release();
Page : << Previous 2 Next >>